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

Chr*_*Bui 16 clojure ring

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

我找到了这篇博文,但它没有解释为什么我要扭转它.

以下是博客文章的快速摘录:

(def app
  (wrap-keyword-params (wrap-params my-handler)))
Run Code Online (Sandbox Code Playgroud)

答复是:

{; Trimmed for brevity
 :params {"my_param" "54"}}
Run Code Online (Sandbox Code Playgroud)

请注意,没有调用wrap关键字params,因为params hash还不存在.但是当你颠倒中间件的顺序时:

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

{; Trimmed for brevity
 :params {:my_param "54"}}
Run Code Online (Sandbox Code Playgroud)

有用.

有人可以解释为什么你必须颠倒中间件的顺序?

dan*_*neu 39

它有助于可视化实际的中间件.

(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)))
Run Code Online (Sandbox Code Playgroud)

那对我来说几乎是那个时刻.

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

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

((fn [request] {:status 200, ...}) request)  ;=> response
Run Code Online (Sandbox Code Playgroud)

让我们稍微缩小一下.我们得到另一个处理程

((GET "/" [] "Hello") request)  ;=> response
Run Code Online (Sandbox Code Playgroud)

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

(my-routes request)  ;=> response
Run Code Online (Sandbox Code Playgroud)

那么,如果您想在将请求发送给my-routes处理程序之前做某事,该怎么办?你可以用另一个处理程序包装它.

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

这有点难以阅读,所以让我们为了清晰起见而突破.我们可以定义一个返回该处理程序的函数.中间件是带有处理程序并将其包装为另一个处理程序的函数.它不会返回响应.它返回一个可以返回响应的处理程序.

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

((println-middleware my-route) request)  ;=> response
Run Code Online (Sandbox Code Playgroud)

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

((outer-middleware (println-middleware my-routes)) request)  ;=> response
Run Code Online (Sandbox Code Playgroud)

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

最后一次演示:

(handler3 (handler2 (handler1 request)))  ;=> response
((middleware1 (middleware2 (middleware3 handler1))) request)  ;=> response
Run Code Online (Sandbox Code Playgroud)

我写得太多,因为我可以同情.但滚动回到我的第一个middleware例子,希望它更有意义.


noi*_*ith 12

环中间件是一系列函数,在堆叠时返回一个处理函数.

回答您问题的文章部分:

在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)")))
Run Code Online (Sandbox Code Playgroud)

这打印并返回:

(this was the input), pre-around, pre-wrapped, post-wrapped, post-around
nil
Run Code Online (Sandbox Code Playgroud)


jua*_*rro 5

您可能知道环app实际上只是一个接收request地图并返回response地图的函数.

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

request -> [wrap-keyword-params -> wrap-params -> my-handler] -> response
Run Code Online (Sandbox Code Playgroud)

wrap-keyword-params寻找密钥:params,request但它不存在,因为wrap-params是根据" 来自查询字符串和表单主体的urlencoded参数 "添加该密钥的人.

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

request -> [wrap-params -> wrap-keyword-params -> my-handler] -> response
Run Code Online (Sandbox Code Playgroud)

你得到了所需的结果,因为一旦request到达wrap-keyword-params,wrap-params已经添加了相应的键.