Clojure China

Ring Middleware

middleware
ring
#1

原文

先来创建个handler

(ns middleware-example
  (:use ring.adapter.jetty
        ring.middleware.params))

(defn handler [request]
    {:headers {}
     :status 200
     :body (str "Hello word!" )})

(def app
  handler)

;; Start the server if it hasn't already been started
(defonce server (ring.adapter.jetty/run-jetty #'app {:port 7071 :join? false}))

我们看到,handler会接收一个request参数,并返回一个hash-map,hash-map包括headers、status和body,这个hash-map就是一个response。
党我们访问 http://localhost:7071/ ,就会看到 Hello word!

用middleware处理response


(defn shout
  [handler]
  (fn [request]
    (let [response (handler request)]
      (update-in response [:body] clojure.string/upper-case)))) ;; 将response中的body转换成大写

(def app
  (-> handler
    shout))

当我们刷新页面的时候,结果就变成了 HELLO WORD!
这个例子说明,我们可以通过middleware改变response。

用middleware处理request

我们可以用middle更好的解析、处理request,请看下面的例子

(ns middleware-example
  (:use ring.adapter.jetty
        ring.middleware.params))

(defn print-query-params [handler]
  (fn [request]
    (println "In print-query-params, the params is:" (:query-params request))
    (handler request)))

(defn handler [request]
  (println "In handler, the params is:" (:query-params request))
  (let [name ((:query-params request) "name")]
    {:headers {}
     :status 200
     :body (str "Hello " name "!")}))

(def app
  (-> handler
      wrap-params
      print-query-params))

;; Start the server if it hasn't already been started
(defonce server (ring.adapter.jetty/run-jetty #'app {:port 7071 :join? false}))

访问 http://localhost:7071?name=mike 后,在console里我们将看到

In print-query-params, the params is: nil
In handler, the params is: {name mike}

这段代码可以说明,params是被wrap-params所解析出来的,request在创给wrap-paramshandler前,先被传给了print-query-params

(在浏览器内,我们实际上会看到两次输出,是因为浏览器在请求url的时候,还请求了favoicon,chrome每次刷新都是两个请求,而safari只有第一次请求了favoicon,之后刷新都没有再次请求)

Middleware

首先,我们将handler这个参数传给middleware,这个middleware会返回一个方法,
这个方法会接收一个request参数,我们可以在这个方法里生成新的request,并把这个request传给handler(handler new-request),得到返回后,我们又可以对response进行操作。

通过app的定义(-> handler wrap-params print-query-params),我们知道,middleware就像洋葱皮一样,一层层包裹handler。

最后面的包裹在最外层,request一层层通过middleware,每一层都可以对request进行操作,最后handler 会根据最终的request生成response,然后response又一层层传出middleware,每一层middleware有有机会对生成的response做处理。

就像下图一样(注意,后面的middleware,处于最外层)。

其他框架的中间件

rack也是使用一样的模型处理中间件。rack使用use方法注册middleware,并将注册的middleware放入@use这个实例变量里。
rack有一个to_app的方法,在这个方法内@use.reverse.inject(app) { |a,e| e[a] },把最先注册的middleware放在最外层,并包裹好。

ring、rack、koa的middleware都是使用洋葱模型,但express的middleware却不一样,middleware只是一层一层传下去的,比如要记录请求时间,就需要写两个中间件

var app = express()

app.use(function (req, res, next) {
  console.log('Time:', Date.now())
  next()
})

小结

Middleware可以用来对response和request进行处理(比如解析params),也可以中断执行(比如过滤ip)等。
但需要注意middleware的顺序,最后的middleware被放在了最外层。最外层,最先处理request,最后处理response。

参考

https://codenoble.com/blog/understanding-rack-middleware
https://expressjs.com/en/guide/using-middleware.html
http://liujiacai.net/blog/2017/04/02/clojure-web-dev-ring-usage

1赞