Clojure: Ring 中间件原理剖析

Ring 提供了 web 开发所需的基础构件,好比处理请求参数,cookie, session 等等。经过向 http-kit 或 jetty 注册 handler 的方式来提供服务。handler 函数接收一个 request 参数,此参数由调用者传递(此处为 http-kit 或 jetty)。然而 http-kit 或 jetty 传递过来的 request 参数只包含了基本标准键,Ring 将经过中间件的方式提供更高级的功能。web

前奏

如下为 request 包含的基本标准键:服务器

:server-port ---------- 用于处理该请求的服务端口
:server-name ---------- 服务器的 IP 地址或是主机名
:remote-addr ---------- 客户端的 IP 地址
:query-string --------- 请求的查询字符串
:scheme --------------- 协议的类型,能够是 HTTP 或者 HTTPS
:request-method ------- 请求的方法,好比::get、:head、:options、:put、:post 或 :delete
:request-string ------- 请求的查询字符串
:content-type --------- 请求消息体的 MIME 类型
:content-length ------- 请求消息体的字节数
:character-encoding --- 请求采用的字符编码名称
:headers -------------- 包含了请求头部的map
:body ----------------- 可用于读取请求消息体的输入流
:context -------------- 当应用没有做为根来部署时,其所处的上下文
:uri ------------------ 服务端的 URI 全路径,包含了 :context(若是存在)的部分
:ssl-client-cert ------ 客户端的 SSL 证书

* 注意:此处列出的键,并不必定会出如今全部的请求中,好比 :ssl-client-cert

 

Ring 中间件的原理其实很简单。链式执行,前一个的输出做为下一个的输入,有点相似于管道。在深刻讲解 Ring 的中间件前,咱们先来熟悉 Clojure 提供的一个方法:->。cookie

方法 -> 就支持链式执行,它接受任意多个参数。第一个参数能够为任意形式,剩下的参数必须为函数,而且至少接受一个参数。这是由于方法 -> 实现的功能是:将前一个表达式的结果做为下一个函数的第一个参数来调用。执行顺序是先上后下,举例以下:session

(defn test-a [msg]
  (let [_msg (str msg " -- a")]
    (println _msg)
    _msg))

(defn test-b [msg]
  (let [_msg (str msg " -- b")]
    (println _msg)
    _msg))

(defn test-c [msg]
  (let [_msg (str msg " -- c")]
    (println _msg)
    _msg))

(defn -main [& args]
  (->
    (test-a "<testing>")
    test-b
    test-c))

 

在命令行执行:闭包

$ lein run
<testing> -- a
<testing> -- a -- b
<testing> -- a -- b -- c

 

能够看到,前一个表达式的值做为下一个函数的第一个参数。这种内置的语法令链式执行看起来一目了然,不需像其它语言那样层层闭包。固然若是 Clojure 没有内置这样的函数,也能够经过定义宏的方式来实现。app

Ring 中间件机制

Ring 中间件其实就是经过 -> 来进行链式执行。还记得须要向 http-kit 或 jetty 注册一个 handler 吗?Ring 中间件的原理说白了就是对 handler 进行层层包装,返回一个新的 handler。可是由于返回了一个新的 handler,也是就是闭包,对于真正的执行时机可能会有点不太同样。接下来举例说明:函数

(defn my-handler [req]
  (println "my-handler")
  "my-handler response")

(defn test-a [handler]
  (fn [req]
    (println "test-a")
    (handler req)))

(defn test-b [handler]
  (fn [req]
    (println "test-b")
    (handler req)))

(defn test-c [handler]
  (fn [req]
    (println "test-c")
    (handler req)))

(def app
  (->
    my-handler
    test-a
    test-b
    test-c))

(defn -main [& args]
  (app nil))

 

在命令行执行:post

$ lein run
test-c
test-b
test-a
my-handler

 

结果看起来跟以前说的不太同样。以前说的执行顺序是先上后下,此次的结果明显是先下后上,到底是哪里出现问题了呢?其实两次的执行结果都是正确的。前一次是简单的链式执行,而这一次链式执行发生后,每一步都返回了一个闭包,致使了延迟执行。相似如下代码:编码

(->
    my-handler
    test-a
    test-b
    test-c)

 

其实能够当作:spa

(test-c (test-b (test-a my-handler)))

 

展开后:

(println "test-c")
; test-b 返回的闭包
(println "test-b")
; test-a 返回的闭包
(println "test-a")
; my-handler
(println "my-handler")

 

至此想必你已经对 Ring 的中间件有所了解了。

中件间能干什么

Ring 提供了一些基础的中间件,它们或多或少的对 request 和 response 进行修改,以达到特定目的。ring-devel 对开发环境提供了支持,好比 wrap-reload 容许你没必要重启便可在修改源码后自动从新载入等等。ring-core 提供了更多标准中间件,有wrap-params、wrap-not-modified、wrap-content-type等等。

Ring 中间件的执行流程相当重要,将来不少时候都要跟中间件打交道,好比自定义拦截,实现公共处理(其它语言中多是经过继承父控制器来实现)等等。

结束语

Ring 是一个很优秀的针对 web 开发的基础构件。研究它的原理和熟读它的源码不论是在 web 开发上仍是对于 Clojure 的理解上都有莫大的好处。

相关文章
相关标签/搜索