Почему необходимо заменить порядок промежуточного ПО Ring?
Я пишу несколько промежуточных программ для Ring, и я очень смущен тем, почему мне приходится менять порядок промежуточного программного обеспечения.
Я нашел этот пост в блоге, но это не объясняет, почему я должен его отменить.
Вот краткий отрывок из сообщения в блоге:
(def app
(wrap-keyword-params (wrap-params my-handler)))
Ответ будет следующим:
{; Trimmed for brevity
:params {"my_param" "54"}}
Обратите внимание, что параметры ключевого слова wrap не вызывались, потому что hash параметров еще не существовал. Но когда вы меняете порядок промежуточного ПО следующим образом:
(def app
(wrap-params (wrap-keyword-params my-handler)))
{; Trimmed for brevity
:params {:my_param "54"}}
Он работает.
Может кто-нибудь объяснить, почему вам нужно изменить порядок промежуточного ПО?
Ответы
Ответ 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 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
и, надеюсь, это имеет смысл.
Ответ 2
Кольцо промежуточного программного обеспечения представляет собой ряд функций, которые при укладке возвращают функцию обработчика.
Раздел статьи, который отвечает на ваш вопрос:
В случае оберток 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
Ответ 3
Как вы знаете, кольцо app
на самом деле является просто функцией, которая получает карту request
и возвращает карту response
.
В первом случае порядок, в котором применяются функции, таков:
request -> [wrap-keyword-params -> wrap-params -> my-handler] -> response
wrap-keyword-params
ищет ключ :params
в request
, но он не существует с wrap-params
- это тот, кто добавляет этот ключ на основе "urlencoded parameters из строки запроса и тела формы".
Когда вы инвертируете порядок этих двух:
request -> [wrap-params -> wrap-keyword-params -> my-handler] -> response
Вы получаете желаемый результат, так как после того, как request
получает значение wrap-keyword-params
, wrap-params
уже добавлены соответствующие ключи.