Почему необходимо изменить порядок промежуточного программного обеспечения Ring?

Я пишу промежуточное программное обеспечение для Ring, и я действительно не понимаю, почему я должен изменить порядок промежуточного программного обеспечения.

Я нашел это сообщение в блоге, но оно не объясняет почему я должен отменить это.

Вот краткий отрывок из сообщения в блоге:

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

Ответ будет таким:

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

Обратите внимание, что параметры ключевого слова wrap не вызывались, потому что хэш params еще не существовал. Но когда вы меняете порядок промежуточного программного обеспечения следующим образом:

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

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

Оно работает.

Может кто-нибудь объяснить, почему вы должны изменить порядок промежуточного программного обеспечения?


person Chris Bui    schedule 18.10.2013    source источник


Ответы (3)


Это помогает визуализировать, что такое промежуточное ПО на самом деле.

(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, надеюсь, он имеет больше смысла.

person danneu    schedule 18.10.2013
comment
Я бы дал вам 10 голосов, если бы мог, это очень помогло. Это похоже на перехватчики от Pedestal, но все в одной функции. - person trigoman; 11.10.2018

Кольцевое промежуточное ПО представляет собой ряд функций, которые при объединении возвращают функцию-обработчик.

Раздел статьи, отвечающий на ваш вопрос:

В случае оберток 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
person noisesmith    schedule 18.10.2013

Как вы, возможно, знаете, кольцо app на самом деле является просто функцией, которая получает карту request и возвращает карту response.

В первом случае порядок применения функций следующий:

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

wrap-keyword-params ищет ключ :params в request, но его там нет, поскольку wrap-params добавляет этот ключ на основе "URL-кодированные параметры из строки запроса и тела формы".

Когда вы инвертируете порядок этих двух:

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

Вы получаете желаемый результат, так как когда request достигает wrap-keyword-params, wrap-params уже добавил соответствующие ключи.

person juan.facorro    schedule 18.10.2013