我正在为Ring编写一些中间件,我真的很困惑为什么我必须颠倒中间件的顺序。
我发现了这个blog post,但它没有解释为什么我要扭转它。
以下是博客文章的快速摘录:
(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"}}
有效。
有人可以解释为什么你必须颠倒中间件的顺序吗?
答案 0 :(得分: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)))
那对我来说几乎就是那个时刻。
乍一看令人困惑的是,中间件并未应用于请求,这正是您的想法。
回想一下,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
示例,希望它更有意义。
答案 1 :(得分: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)")))
打印并返回:
(this was the input), pre-around, pre-wrapped, post-wrapped, post-around
nil
答案 2 :(得分:5)
您可能知道响铃app
实际上只是一个接收request
地图并返回response
地图的函数。
在第一种情况下,应用函数的顺序是:
request -> [wrap-keyword-params -> wrap-params -> my-handler] -> response
wrap-keyword-params
在:params
中查找密钥request
,但它不存在,因为wrap-params
是根据“ urlencoded参数添加该密钥的人来自查询字符串和表单正文“。
当您颠倒这两个的顺序时:
request -> [wrap-params -> wrap-keyword-params -> my-handler] -> response
您获得了所需的结果,因为一旦request
到达wrap-keyword-params
,wrap-params
已经添加了相应的密钥。