本节书摘来自异步社区《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")
(GET "/bar/:id foo" [id] (bar-handler id))))
定义路由是一种很常见的操作,Compojure还提供了defroutes宏,通过给定的路由生成一个Ring处理程序:(defroutes handler (GET "/foo" [] (foo-handler)) (GET "/bar/:id" [id] (bar-handler 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] (display-profile id)) (GET "/user/:id/settings" [id] (display-settings id)) (GET "/user/:id/change-password" [id] (change-password-page 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装备上函数式这种强劲的武器,我们就能轻易创建页面,并为站点提供路由。但是,完善的应用还需要许多其他的特性,比如页面缓存、会话管理、输入验证,面对这些任务,我们使用最棒的适配器库来逐个击破。