【问题标题】:Why does the order of Ring middleware need to be reversed?为什么 Ring 中间件的顺序需要颠倒?
【发布时间】:2013-10-27 15:49:53
【问题描述】:

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

我找到了这个blog post,但它没有解释为什么我必须扭转它。

以下是博客文章的简短摘录:

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

响应是:

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

注意 wrap 关键字 params 没有被调用,因为 params 哈希还不存在。但是当你像这样颠倒中间件的顺序时:

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

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

有效。

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

【问题讨论】:

    标签: clojure ring


    【解决方案1】:

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

    (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 示例,希望它更有意义。

    【讨论】:

    • 如果可以的话,我会给你 10 票,这很有帮助。这与 Pedestal 的 interceptors 类似,但多合一功能。
    【解决方案2】:

    环中间件是一系列函数,当它们叠加时返回一个处理函数。

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

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

    这是一个人为的例子:

    (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
    

    【讨论】:

      【解决方案3】:

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

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

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

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

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-10
        • 2011-07-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多