本节书摘来自异步社区《Clojure Web开发实战》一书中的第2章,第2.2节定义Compojure路由,作者[美]Dmitri Sotnikov,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.2 定义Compojure路由
Compojure是构建在Ring之上的路由库,它提供的方式非常简洁,用来关联处理URL和HTTP方法。Compojure路由基本上是这样子的:(GET "/:id" [id](str "<p>the id is: " id "</p>" ))
其路由函数名与HTTP方法名直接对应,比如GET、POST、PUT、DELETE和HEAD。还有一个称为ANY的路由会响应客户端任何方法。URI是包含冒号的键名,对应的那些值可以用作路由参数,Rails12和Sinatra13就是使用类似的处理机制,而Compojure正是受到这种特性的启发。上面的Ring回应描述中会自动包含路由回应。
其实在我们的实际应用中,可能会存在多条路由,Compojure提供了路由功能,能从多条路由中创建一个Ring处理。假设我们有/foo路由和/:id项,那么我们可以使用单条处理进行如下合并:
(defn foo-handler []
"foo called")
(defn bar-handler [id]
(str "bar called, id is: " id))
(def handler
(routes
(GET "/foo"[](foo-handler))
(GET "/bar/:id foo" id)))
定义路由是一种很常见的操作,Compojure还提供了defroutes宏,通过给定的路由生成一个Ring处理程序:
`(defroutes handler
(GET "/foo" [] (foo-handler))
(GET "/bar/:id" id))`
使用Compojure路由,可以非常方便地将网站的每个URL映射到功能代码,并且提供Web应用的大部分核心功能。我们可以像前面那样,使用defroutes宏把这些路由组织起来。大致上,Compojure就是这样维护Ring处理的。
对基于路径共享的程序,Compojure也提供强大的过滤机制处理常见路由。假设我们现有多条路由来响应特定用户:
`(defn display-profile [id]
;;TODO: display user profile
)
(defn display-settings [id]
;;TODO: display user account settings
)
(defn change-password [id]
;;TODO: display the page for setting a new password
)
(defroutes user-routes
(GET "/user/:id/profile" id)
(GET "/user/:id/settings" id)
(GET "/user/:id/change-password" id)`
现在每条路由前段都是/user/:id,必然会有很多重复代码。我们可以使用context宏,来解析路由的相同部分。
`(def user-routes
(context "/user/:id" [id]
(GET "/profile" [](display-profile id))
(GET "/settings" [](display-settings id))
(GET "/change-password" [](change-password-page id))))`
这段代码中,路由定义了与/user/:id有关的内容,和前一个版本功能完全一样,都能使用id参数。context宏正是通过闭包来实现的。输出handler封装了通用参数,它们就可以在内部定义。
访问请求参数
有些路由,需要我们使用请求的map保存请求参数。我们通过以下这种方式声明map,并作为路由的第二参数:
`(GET "/foo" request (interpose ", " (keys request)))
此路由提取请求map的所有键名,并罗列出来,其输出如下。
:ssl-client-cert, :remote-addr, :scheme, :query-params, :session, :form-params,
:multipart-params, :request-method, :query-string, :route-params, :content-type,
:cookies, :uri, :server-name, :params, :headers, :content-length, :server-port,
:character-encoding, :body, :flash`
Compojure同样提供一些实用功能来处理请求map,包括格式化参数之类。例如,在留言簿程序中(第1章“起步”,第1页),我们看到如下路由定义:(POST "/" [name message](save-message name message))
这个路由从请求参数中提取了:name 和:message两个键,然后将它们绑定给同名变量。就像其他的声明变量一样,现在,我们在路由作用范围内就可以使用了。
常规的Clojure解构也可用于路由内部,假设给定一个包含如下参数的请求map:{:params {"name" "some value"}}
我们可以使用这种方式从参数中提取“name”关键字:
`(GET "/:foo" {{value "name"} :params}
(str "The value of name is " value))`
此外,Compojure 还提供解构形参子集,并用剩余部分创建一个map:
`[x y & z]
x -> "foo"
y -> "bar"
z -> {:v "baz", :w "qux"}`
以上代码中,参数x和y都绑定到变量,v和w被重命名为一个名为z的map。此外,如果我们需要完整的请求参数,我们还可以进行如下处理:
(GET "/" x y :as r)
这里,我们将形参绑定给x、y,还有完整的请求map绑定给变量r。Ring和Compojure装备上函数式这种强劲的武器,我们就能轻易创建页面,并为站点提供路由。但是,完善的应用还需要许多其他的特性,比如页面缓存、会话管理、输入验证,面对这些任务,我们使用最棒的适配器库来逐个击破。