文档章节

Clojure: Ring 中间件原理剖析

陈亦
 陈亦
发布于 2016/03/18 01:22
字数 1300
阅读 673
收藏 1

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

 

© 著作权归作者所有

陈亦
粉丝 241
博文 23
码字总数 53194
作品 0
浦东
高级程序员
私信 提问
clojure ring 让webapp变得毫无神秘感

首先要说明的是:clojure ring不是新思想,根据其git首页的描述,是从Python's WSGI and Ruby's Rack获得思想。最近写了几星期的clojure代码,觉得clojure ring实在是太简单了(这是褒义),...

jianglibo
2014/10/25
1K
2
Clojure Web 开发 -- Ring 使用指南

在 Clojure 众多的 Web 框架中,Ring 以其简单统一的 HTTP 抽象模型脱颖而出。Ring 充分体现了函数式编程的思想——通过一系列函数的组合形成了一个易于理解、扩展的 HTTP 处理链。 本篇文章...

jiacai2050
2017/04/05
0
0
Clojure: Web开发初体验 - 基于 Ring

Ring 的目标是把 HTTP 的细节抽象为简单且模块化的 API,它与 Python 的 WSGI 和 Ruby 的 Rake 非常类似。可以用来构建类型广泛的应用。 新建基于 default 模板的项目: $ lein new hello 修...

陈亦
2016/03/17
1K
0
Clojure 的 Web 框架 - Ring

Ring 是一个 Clojure 的 Web 框架,它与 Python 的 WSGI 和 Ruby 的 Rake 非常类似。通过把 HTTP 的细节抽象为简单且模块化的 API,Ring 允许 Web 应用程序由模块化组件构成,这些组件可以在...

匿名
05/21
1K
14
xfeep/nginx-clojure

Nginx-Clojure Nginx-Clojure is a Nginx module for embedding Clojure or Java or Groovy programs, typically those Ring based handlers. Core Features The latest release is v0.4.5, ......

xfeep
2015/02/01
0
0

没有更多内容

加载失败,请刷新页面

加载更多

只需一步,在Spring Boot中统一Restful API返回值格式与统一处理异常

统一返回值 在前后端分离大行其道的今天,有一个统一的返回值格式不仅能使我们的接口看起来更漂亮,而且还可以使前端可以统一处理很多东西,避免很多问题的产生。 比较通用的返回值格式如下:...

晓月寒丶
昨天
59
0
区块链应用到供应链上的好处和实际案例

区块链可以解决供应链中的很多问题,例如记录以及追踪产品。那么使用区块链应用到各产品供应链上到底有什么好处?猎头悬赏平台解优人才网小编给大家做个简单的分享: 使用区块链的最突出的优...

猎头悬赏平台
昨天
28
0
全世界到底有多少软件开发人员?

埃文斯数据公司(Evans Data Corporation) 2019 最新的统计数据(原文)显示,2018 年全球共有 2300 万软件开发人员,预计到 2019 年底这个数字将达到 2640万,到 2023 年达到 2770万。 而来自...

红薯
昨天
65
0
Go 语言基础—— 通道(channel)

通过通信来共享内存(Java是通过共享内存来通信的) 定义 func service() string {time.Sleep(time.Millisecond * 50)return "Done"}func AsyncService() chan string {retCh := mak......

刘一草
昨天
58
0
Apache Flink 零基础入门(一):基础概念解析

Apache Flink 的定义、架构及原理 Apache Flink 是一个分布式大数据处理引擎,可对有限数据流和无限数据流进行有状态或无状态的计算,能够部署在各种集群环境,对各种规模大小的数据进行快速...

Vincent-Duan
昨天
60
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部