Clojure: Ring 中间件原理剖析

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

前奏

以下为 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 提供的一个方法:->。

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

(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 没有内置这样的函数,也可以通过定义宏的方式来实现。

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))

 

在命令行执行:

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

 

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

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

 

其实可以看成:

(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 的理解上都有莫大的好处。

 

转载于:https://my.oschina.net/goal/blog/639695

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值