Compojure的解释(一定程度上)
注意 我正在使用Compojure 0.4.1(这是GitHub上的0.4.1版本提交)。
为什么?
在的最上方compojure/core.clj
,是Compojure目的的有用摘要:
生成Ring处理程序的简洁语法。
从表面上看,这就是“为什么”问题的全部。更深入一点,让我们看一下环形应用程序的功能:
根据Ring规范,一个请求到达并转换为Clojure映射。
该映射被集中到所谓的“处理函数”中,该函数有望产生响应(也是Clojure映射)。
响应映射被转换为实际的HTTP响应,然后发送回客户端。
上面的第2步是最有趣的,因为处理程序有责任检查请求中使用的URI,检查任何cookie等并最终获得适当的响应。显然,有必要将所有这些工作分解为一系列定义明确的作品;这些通常是“基本”处理程序函数,是包装它的中间件函数的集合。 Compojure的目的是简化基本处理函数的生成。
怎么样?
Compojure是围绕“路线”概念构建的。这些实际上是由Clout库(在Compojure项目的衍生产品中,在0.3.x-> 0.4.x过渡时移至单独的库)在更深层次上实现的。路由的定义是:(1)HTTP方法(GET,PUT,HEAD ...),(2)URI模式(指定的语法显然是Webby Rubyists所熟悉的语法),(3)在将请求映射的部分绑定到主体中可用的名称,(4)需要产生有效Ring响应的表达式主体(在不平凡的情况下,这通常只是对单独函数的调用)。
看一个简单的例子可能是个好主意:
(def example-route (GET "/" [] "<html>...</html>"))
让我们在REPL上进行测试(以下请求映射是最小的有效Ring请求映射):
user> (example-route {:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "<html>...</html>"}
如果:request-method
是:head
相反,他们的回应是nil
。nil
一分钟后,我们将回到这里是什么意思的问题(但请注意,这不是有效的Ring重置!)。
从该示例可以明显看出,example-route
它只是一个函数,而在那是一个非常简单的函数。它查看该请求,确定是否对处理该请求感兴趣(通过检查:request-method
和:uri
),如果是,则返回一个基本的响应图。
同样明显的是,路线主体实际上不需要评估为适当的响应图;Compojure为字符串(如上所示)和许多其他对象类型提供了合理的默认处理。compojure.response/render
有关详细信息,请参见多方法(此处的代码完全是自记录的)。
让我们defroutes
现在尝试使用:
(defroutes example-routes
(GET "/" [] "get")
(HEAD "/" [] "head"))
对上面显示的示例请求及其变体的响应与:request-method :head
预期的一样。
的内部运作方式example-routes
是依次尝试每条路线;一旦其中一个返回非nil
响应,该响应即成为整个example-routes
处理程序的返回值。作为一个额外的便利,defroutes
-defined处理程序被包裹在wrap-params
和wrap-cookies
隐式。
这是更复杂的路线的示例:
(def echo-typed-url-route
(GET "*" {:keys [scheme server-name server-port uri]}
(str (name scheme) "://" server-name ":" server-port uri)))
请注意,销毁形式代替了以前使用的空向量。这里的基本思想是,路由主体可能会对有关请求的某些信息感兴趣;由于这总是以地图的形式到达,因此可以提供关联的解构形式以从请求中提取信息,并将其绑定到将在路线主体范围内的局部变量。
以上测试:
user> (echo-typed-url-route {:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/foo/bar"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "http://127.0.0.1:80/foo/bar"}
上述出色的后续想法是,更复杂的路由可能会assoc
在匹配阶段将更多信息添加到请求中:
(def echo-first-path-component-route
(GET "/:fst/*" [fst] fst))
这与响应:body
的"foo"
,从前面的例子请求。
关于此最新示例,有两件事是新的:"/:fst/*"
和空绑定向量[fst]
。第一种是前面提到的用于URI模式的类似于Rails and Sinatra的语法。与上面的示例相比,它更加复杂,因为它支持URI段上的正则表达式约束(例如,["/:fst/*" :fst #"[0-9]+"]
可以提供以使路由仅接受:fst
上面的全数字值)。第二种是:params
在请求映射中匹配条目的简化方法,请求映射本身就是一个映射;对于从请求,查询字符串参数和表单参数中提取URI段非常有用。举例说明后一点:
(defroutes echo-params
(GET "/" [& more]
(str more)))
user> (echo-params
{:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/"
:query-string "foo=1"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "{\"foo\" \"1\"}"}
现在是时候看看问题文本中的示例了:
(defroutes main-routes
(GET "/" [] (workbench))
(POST "/save" {form-params :form-params} (str form-params))
(GET "/test" [& more] (str "<pre>" more "</pre>"))
(GET ["/:filename" :filename #".*"] [filename]
(response/file-response filename {:root "./static"}))
(ANY "*" [] "<h1>Page not found.</h1>"))
让我们依次分析每条路线:
(GET "/" [] (workbench))
-使用处理GET
请求时:uri "/"
,调用函数workbench
并将其返回的任何内容渲染到响应映射中。(回想一下,返回值可能是一个映射,但也可能是一个字符串等。)
(POST "/save" {form-params :form-params} (str form-params))
- :form-params
是wrap-params
中间件提供的请求映射中的一个条目(请记住,它隐式包含在中defroutes
)。该响应将是标准{:status 200 :headers {"Content-Type" "text/html"} :body ...}
带(str form-params)
取代...
。(有点不寻常的POST
处理程序,这个...)
(GET "/test" [& more] (str "<pre> more "</pre>"))
- {"foo" "1"}
如果用户代理要求,这将例如回显地图的字符串表示形式"/test?foo=1"
。
(GET ["/:filename" :filename #".*"] [filename] ...)
-该:filename #".*"
部分什么也不做(因为#".*"
总是匹配)。它调用Ring实用程序函数ring.util.response/file-response
以产生其响应。该{:root "./static"}
部分告诉它在哪里寻找文件。
(ANY "*" [] ...)
-全面路线。最好在defroutes
表单末尾包含这样的路由,以确保定义的处理程序始终返回有效的Ring响应图(请注意,路由匹配失败会导致nil
),这是Compojure的良好做法。
为什么这样呢?
Ring中间件的目的之一是向请求映射中添加信息。因此cookie处理中间件:cookies
向请求添加了密钥,wrap-params
添加:query-params
和/或:form-params
如果存在查询字符串/表单数据,等等。(严格来说,中间件功能要添加的所有信息必须已经存在于请求映射中,因为这是它们传递的内容;它们的工作是将其转换为使其在包装的处理程序中使用更方便。)最终,“丰富的”请求被传递到基本处理程序,该处理程序检查请求映射以及由中间件添加的所有经过很好预处理的信息,并产生响应。(中间件可以做的比这更复杂的事情-例如包装多个“内部”处理程序并在它们之间进行选择,确定是否完全调用被包装的处理程序等。但是,这不在此答案的范围内。)
反过来,基本处理程序通常是(在不平凡的情况下)一个函数,该函数往往只需要少量有关请求的信息。(例如,ring.util.response/file-response
它并不关心大多数请求;它只需要一个文件名。)因此,需要一种简单的方法来仅提取Ring请求的相关部分。Compojure的目的是提供一个专用的模式匹配引擎,它就是这样做的。