逆旅过客:以下内容为本人学习笔记,是官方文档的缩略版,部分内容或有个人阐释。
一、Getting Started
1、创建一个 project
lein new compojure hello-world
2、启动服务 ,默认端口 3000。源文件如果修改,服务会自动重新加载
cd hello-world
lein ring server-headless
二、Routes In Detail
1、compojure 中的 routes 类似如下:
-
(GET "/user/:id" [id] (str "
Hello user "
id ""))
routes 返回值为 Ring-handler 函数。
2、匹配 HTTP 方法 -- GET
compojure 定义了一系列 route 宏,包括 POST,PUT,DELETE,OPTIONS,PATCH 和 HEAD。如果要匹配任何一种 HTTP request ,可以使用 ANY。注意:这些宏全部都是大写。
3、匹配 URI -- "/user/:id"
这个字符串用于定义 routing(语法来自于 Clout 模块),很多语法格式同 Ruby on Rails 和 Sinatra 一致。
:id 将匹配 /user/ 后的所有余下部分,结果存入 "id" 参数。
如果想定义的更为复杂,可以引入正则表达式:
-
["/user/:id", :id #"[0-9]+"]
compojure 1.3.0 以后引入一种直接嵌入正则表达式的写法(正则表达式用花括号扩起来):
如果 URI 不匹配定义的路径,route 函数将返回 nil。
4、解构 request -- [id]
[id]用于接收数据,这是 GET 宏的第二个参数 ,你可以使用 vector ,也可以使用 Clojure 本身的各种解构数据的方式。
Clout route 字符串的语法格式会自动将符号 id 绑定到 request map 中的 "id"参数。
我们当然也可以使用标准的 Clojure 解构 form:
另外任何时候如果你想要捕获 request map,都可以如下操作:
-
(GET "/" request (str request))
以上将 request map 转化成字符串作为 response 返回。
更多内容参考:Destructing Syntax part。
5、Returning a response
一旦 HTTP 请求匹配上,数据解构完成后,route 函数最后需要的就是一个执行代码块了,同普通函数一样:
-
(str "
Hello user "
id "")
返回值会被自动处理,上面代码返回一个字符串,它会被转化成一个标准的响应:
-
{:status 200
-
:headers {"Content-Type" "text/html; charset=utf-8"}
-
:body "
Hello user 1"
}
compojure.response/Renderable protocol 会将上面任意数据(String,map,File 等)转化成合适的 response。你也可以覆写它提供一个你自己类型的定制的渲染器( rendering)。
6、Combining routes
Compojure 使用 compojure.core/routes 函数来连接 routes.。Each route is attempted in order, until a route is found that returns a non-nil response to the request.
-
(def my-routes
-
(routes
-
(GET "/foo" [] "Hello Foo")
-
(GET "/bar" [] "Hello Bar")))
以上为通用模式, Compojure 还提供了一个 defroutes 宏:
-
(defroutes my-routes
-
(GET "/foo" [] "Hello Foo")
-
(GET "/bar" [] "Hello Bar"))
Compojure 提供两种方式进行解构:
-
Clojure 样式 ,就是 let 中使用的那种
-
Compojue 特定样式,用于从 request map 中解析参数。
常规性的 Clojure 解构
大多数情况下我们会直接捕获 request map:
-
(GET "/" request
-
(str request))
如果你提供了一个 map 或是 symbol,那么就将对 Ring request map 运用Clojure 风格的解构语法 。如下例,展示了如何使用 Clojure 语法将制定参数绑定到一个变量中:
-
(GET "/:foo" {{foo :foo} :params}
-
(str "Foo = " foo))
Compojure 特定样式解构
常规性的解构过于琐碎,Compojure 定制了一种更方便的样式。如果你提供了一个 vector,Compojure 就会使用这种定制语法。下面这个例子就展示了这种更为简洁的写法:
-
(GET "/:foo" [foo]
-
(str "Foo = " foo))
Compojure 的参数解构语法有三部分。第一部分直接将参数绑定到同名的 symbols 上,举个例子,假设我们有如下 request map:
-
{:request-method :get
-
:uri "/foobar"
-
:headers {}
-
:params {:x "foo", :y "bar", :z "baz", :w "qux"}}
我们可以用一个 symbols 组成的 vector 来绑定每一个参数:
-
[x y z]
-
x -> "foo"
-
y -> "bar"
-
z -> "baz"
对于剩下的所有未指定参数,我们可以使用 "&" symbol 加一个变量名来进行绑定:
-
[x y & z]
-
x -> "foo"
-
y -> "bar"
-
z -> {:z "baz", :w "qux"}
这种方式非常类似于 Clojure 中 "&" 的绑定方式。不同之处是 Clojure 中是绑定 list 中余下的部分,得到的还是 list,Compojure 中是绑定 map 中余下的部分,得到的还是 map。
最后,你还可以使用 :as 关键字将整个 request map 绑定到一个 symbol:
-
[x y :as r]
-
x -> "foo"
-
y -> "bar"
-
r -> {:request-method :get
-
:uri "/foobar"
-
:headers {}
-
:params {:x "foo", :y "bar", :z "baz", :w "qux"}}
你还可以通过 Clojure 结构 map 后绑定到 :as 关键字:
-
[x y :as {u :uri}]
-
x -> "foo"
-
y -> "bar"
-
u -> "/foobar"}
四、Nesting routes -- 路由嵌套
context 宏用来给一组 routes 统一加上前缀:
-
(defroutes user-routes
-
(context "/user/current" []
-
(GET "/" [] ...) ; 这个 route 实际对应到 "/user/current"
-
(GET "/profile" [] ...)
-
(GET "/posts" [] ...)))
类似于普通 route ,我们可以给 context 添加 route 参数:
-
(defroutes user-routes
-
(context "/user/:user-id" [user-id]
-
(GET "/profile" [] ...)
-
(GET "/posts" [] ...)))
因为 routes 是闭包,因此 user_id symbol 对于下面两个子 routes 都是可用的。
但是如果你内部的 routes 是在别处定义的,你就需要手工将 context 中的绑定参数传入进去。示例:
-
(defn inner-routes [user-id]
-
(routes
-
(GET "/profile" [] ...)
-
(GET "/posts" [] ...)))
-
-
(defroutes user-routes
-
(context "/user/:user-id" [user-id]
-
(inner-routes user-id)))
这是因为参数绑定是基于词法,而不是基于动态作用域的。
五、R
esponse Rendering -- 响应渲染
Compojure route 的响应会直接使用 compojure.response/render 方法进行处理。
六、常见问题
1、一个中间件试图读取 request 中的 :body 部分,但得到内容为空,怎么回事?
在 request 中, :body 的值是一个 InputStream。InputStream 是可变的(mutable)因此只能被读取一次。 在 InputStream 已被读取后,试图再次读取,操作都会失败。如果你发现 :body 值不是其实际值而是为空,那么多半是栈中更高一层的某个中间件已经读取过 InputStream 了。
Ex: 比如你在同一个栈中有两组 routes ,每一组都用到了某种 wrap-params 中间件。那么只有首先被调用的 routes 会获取到参数。
2、如何才能让 route 能够处理末尾为斜线的 URI ?
Compojure 还没法自动将 URI为斜线结尾的 requests 重定向到非斜线结尾的 routes handlers。变通的解决方案是使用一个中间件函数来去除 URI 末尾的斜线,或者调用一个 handlers 然后再重定向。
Ex: trailing-slash-middleware
阅读(1767) | 评论(0) | 转发(0) |