17

I'm writing some middleware for Ring and I'm really confused as to why I have to reverse the order of the middleware.

I've found this blog post but it doesn't explain why I have to reverse it.

Here's a quick excerpt from the blog post:

(def app
  (wrap-keyword-params (wrap-params my-handler)))

The response would be:

{; Trimmed for brevity
 :params {"my_param" "54"}}

Note that the wrap keyword params didn't get called on it because the params hash didn't exist yet. But when you reverse the order of the middleware like so:

(def app
  (wrap-params (wrap-keyword-params my-handler)))

{; Trimmed for brevity
 :params {:my_param "54"}}

It works.

Could somebody please explain why you have to reverse the order of the middleware?

4

3 回答 3

45

它有助于可视化中间件实际上是什么。

(defn middleware [handler]
  (fn [request]
    ;; ...
    ;; Do something to the request before sending it down the chain.
    ;; ...
    (let [response (handler request)]
      ;; ...
      ;; Do something to the response that's coming back up the chain.
      ;; ...
      response)))

那对我来说几乎是一个哈哈的时刻。

乍一看令人困惑的是中间件并未应用于请求,这就是您的想法。

回想一下,Ring 应用程序只是一个接受请求并返回响应的函数(这意味着它是一个处理程序):

((fn [request] {:status 200, ...}) request)  ;=> response

让我们缩小一点。我们得到另一个处理程序:

((GET "/" [] "Hello") request)  ;=> response

让我们再缩小一点。我们找到my-routes处理程序:

(my-routes request)  ;=> response

好吧,如果您想在将请求发送到my-routes处理程序之前做些什么呢?您可以用另一个处理程序包装它。

((fn [req] (println "Request came in!") (my-routes req)) request)  ;=> response

这有点难以阅读,所以为了清楚起见,让我们分开。我们可以定义一个返回该处理程序的函数。中间件是接受一个处理程序并将其包装到另一个处理程序的函数。它不返回响应。它返回一个可以返回响应的处理程序。

(defn println-middleware [wrapped-func]
  (fn [req]
    (println "Request came in!")
    (wrapped-func req)))

((println-middleware my-route) request)  ;=> response

如果我们需要在收到请求之前做一些事情println-middleware,那么我们可以再次包装它:

((outer-middleware (println-middleware my-routes)) request)  ;=> response

关键是my-routes,就像您的my-handler,是唯一实际将请求作为参数的命名函数。

最后一个演示:

(handler3 (handler2 (handler1 request)))  ;=> response
((middleware1 (middleware2 (middleware3 handler1))) request)  ;=> response

我写了这么多,因为我可以同情。但是回滚到我的第一个middleware例子,希望它更有意义。

于 2013-10-18T21:45:03.317 回答
13

环形中间件是一系列函数,当它们堆叠起来时会返回一个处理函数。

文章中回答您问题的部分:

对于 Ring 包装器,通常我们有“之前”装饰器,它们在调用“真正的”业务功能之前执行一些准备工作。由于它们是高阶函数而不是直接函数调用,因此它们以相反的顺序应用。如果一个人依赖另一个人,那么依赖的人需要在“内部”。

这是一个人为的例子:

(let [post-wrap (fn [handler]
                  (fn [request]
                    (str (handler request) ", post-wrapped")))
      pre-wrap (fn [handler]
                 (fn [request]
                   (handler (str request ", pre-wrapped"))))
      around (fn [handler]
               (fn [request]
                 (str (handler (str request ", pre-around")) ", post-around")))
      handler (-> (pre-wrap identity)
                  post-wrap
                  around)]
  (println (handler "(this was the input)")))

这将打印并返回:

(this was the input), pre-around, pre-wrapped, post-wrapped, post-around
nil
于 2013-10-18T18:45:08.693 回答
5

正如您可能知道的那样,环app实际上只是一个接收request地图并返回response地图的函数。

在第一种情况下,函数的应用顺序如下:

request -> [wrap-keyword-params -> wrap-params -> my-handler] -> response

wrap-keyword-params在中查找键:paramsrequest但它不存在,因为是根据“查询字符串和表单正文中的 urlencoded 参数wrap-params”添加该键的人。

当你颠倒这两个的顺序时:

request -> [wrap-params -> wrap-keyword-params -> my-handler] -> response

你得到了想要的结果,因为一旦 get requestwrap-keyword-params已经wrap-params添加了相应的键。

于 2013-10-18T18:45:35.813 回答