Clojure 为什么环中间件的顺序需要颠倒?

Clojure 为什么环中间件的顺序需要颠倒?,clojure,ring,Clojure,Ring,我正在为Ring编写一些中间件,我真的很困惑,为什么我必须颠倒中间件的顺序 我发现了这个,但它不能解释我为什么要反转它 下面是这篇博文的简短摘录: (def app (wrap-keyword-params (wrap-params my-handler))) 答复是: {; Trimmed for brevity :params {"my_param" "54"}} 请注意,由于params散列尚不存在,因此没有对其调用wrap关键字params。但当您颠倒中间件的顺序时,如下所示:

我正在为Ring编写一些中间件,我真的很困惑,为什么我必须颠倒中间件的顺序

我发现了这个,但它不能解释我为什么要反转它

下面是这篇博文的简短摘录:

(def app
  (wrap-keyword-params (wrap-params my-handler)))
答复是:

{; Trimmed for brevity
 :params {"my_param" "54"}}
请注意,由于params散列尚不存在,因此没有对其调用wrap关键字params。但当您颠倒中间件的顺序时,如下所示:

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

{; Trimmed for brevity
 :params {:my_param "54"}}
它起作用了


有人能解释一下为什么要颠倒中间件的顺序吗?

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

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

在环包装的情况下,通常我们有“之前”的装饰 在调用“真正的”业务函数之前执行一些准备工作。 因为它们是高阶函数,而不是直接函数调用, 它们是按相反的顺序应用的。如果一个依赖另一个,那么 一个人需要依靠“内在”

下面是一个人为的例子:

(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

您可能知道,ring
app
实际上只是一个函数,它接收
请求
映射并返回
响应
映射

在第一种情况下,应用功能的顺序如下:

request -> [wrap-keyword-params -> wrap-params -> my-handler] -> response
请求
中查找键
:params
,但它不在那里,因为有人根据“查询字符串和表单正文中的URL编码参数”添加该键

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

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

因为一旦
请求
到达
wrap关键字参数
wrap参数
已经添加了相应的键,因此可以得到所需的结果。

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

(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应用程序只是一个接受请求并返回响应的函数(这意味着它是一个处理程序):

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

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

我写这么多是因为我能同情。但是,回到我的第一个
中间件
示例,希望它更有意义。

如果可以的话,我会给你10票,这很有帮助。这与底座类似,但功能一应俱全。
(handler3 (handler2 (handler1 request)))  ;=> response
((middleware1 (middleware2 (middleware3 handler1))) request)  ;=> response