clojure - ¿Por qué es necesario invertir el orden de middleware de Ring?
(3)
Ayuda a visualizar qué es realmente el middleware.
(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)))
Ese momento fue prácticamente el momento de a-ha para mí.
Lo que es confuso a primera vista es que el middleware no se aplica a la solicitud, que es lo que está pensando.
Recuerde que una aplicación de Ring es solo una función que toma una solicitud y devuelve una respuesta (lo que significa que es un controlador):
((fn [request] {:status 200, ...}) request) ;=> response
Vamos a alejarnos un poco. Conseguimos otro manejador:
((GET "/" [] "Hello") request) ;=> response
Vamos a alejarnos un poco más. Encontramos el manejador de my-routes
:
(my-routes request) ;=> response
Bueno, ¿qué sucede si desea hacer algo antes de enviar la solicitud al manejador de my-routes
? Puedes envolverlo con otro manejador.
((fn [req] (println "Request came in!") (my-routes req)) request) ;=> response
Eso es un poco difícil de leer, así que salgamos a la luz. Podemos definir una función que devuelve ese manejador. El middleware son funciones que toman un controlador y lo envuelven otro controlador. No devuelve una respuesta. Devuelve un manejador que puede devolver una respuesta.
(defn println-middleware [wrapped-func]
(fn [req]
(println "Request came in!")
(wrapped-func req)))
((println-middleware my-route) request) ;=> response
Y si necesitamos hacer algo antes de que incluso println-middleware
reciba la solicitud, podemos envolverla nuevamente:
((outer-middleware (println-middleware my-routes)) request) ;=> response
La clave es que my-routes
, al igual que my-handler
, es la única función con nombre que toma la solicitud como un argumento.
Una demostración final:
(handler3 (handler2 (handler1 request))) ;=> response
((middleware1 (middleware2 (middleware3 handler1))) request) ;=> response
Escribo mucho porque puedo simpatizar. Pero vuelva a mi primer ejemplo de middleware
y espero que tenga más sentido.
Estoy escribiendo algo de middleware para Ring y estoy realmente confundido en cuanto a por qué tengo que invertir el orden del middleware.
He encontrado esta publicación de blog pero no explica por qué tengo que revertirla.
Aquí hay un extracto rápido de la publicación del blog:
(def app
(wrap-keyword-params (wrap-params my-handler)))
La respuesta sería:
{; Trimmed for brevity
:params {"my_param" "54"}}
Tenga en cuenta que los parámetros de la palabra clave de ajuste no fueron activados porque el hash de parámetros aún no existía. Pero cuando se invierte el orden del middleware así:
(def app
(wrap-params (wrap-keyword-params my-handler)))
{; Trimmed for brevity
:params {:my_param "54"}}
Funciona.
¿Alguien podría explicar por qué tiene que invertir el orden del middleware?
Como puede saber, la app
anillo es en realidad solo una función que recibe un mapa de request
y devuelve un mapa de response
.
En el primer caso, el orden en que se aplican las funciones es el siguiente:
request -> [wrap-keyword-params -> wrap-params -> my-handler] -> response
wrap-keyword-params
busca la clave :params
en la request
pero no está allí ya que wrap-params
es la que agrega esa clave en función de los " parámetros codificados con url desde la cadena de consulta y el cuerpo del formulario ".
Cuando inviertes el orden de esos dos:
request -> [wrap-params -> wrap-keyword-params -> my-handler] -> response
Obtendrá el resultado deseado ya que una vez que la request
llega a wrap-keyword-params
, wrap-params
ya ha agregado las claves correspondientes.
El middleware de anillo es una serie de funciones que, cuando se apilan, devuelven una función de controlador.
La sección del artículo que responde a tu pregunta:
En el caso de las envolturas de anillo, normalmente tenemos decoradores "antes" que realizan algunas preparaciones antes de llamar a la función empresarial "real". Como son funciones de orden superior y no llamadas de función directas, se aplican en orden inverso. Si uno depende del otro, el dependiente debe estar en el "interior".
Aquí hay un ejemplo artificial:
(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)")))
Esto imprime y devuelve:
(this was the input), pre-around, pre-wrapped, post-wrapped, post-around
nil