2013-10-18 8 views
16

Sto scrivendo del middleware per Ring e sono davvero confuso sul motivo per cui devo invertire l'ordine del middleware.Perché l'ordine del middleware Ring deve essere invertito?

Ho trovato questo blog post ma non spiega perché devo invertirlo.

Ecco un breve estratto dal post del blog:

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

La risposta sarebbe:

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

Nota che i involucro di parola chiave params non vengono chiamati su di esso perché il params hash didn Esistono ancora. Ma quando si inverte l'ordine del middleware in questo modo:

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

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

Funziona.

Qualcuno potrebbe spiegare perché è necessario invertire l'ordine del middleware?

risposta

36

Aiuta a visualizzare quale middleware è effettivamente.

(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))) 

Quel giusto per me era praticamente il momento degli a-ha.

Ciò che confonde a prima vista è che il middleware non è applicato alla richiesta, che è ciò a cui stai pensando.

Ricordiamo che un'applicazione Ring è solo una funzione che prende una richiesta e restituisce una risposta (che significa che è un gestore):

((fn [request] {:status 200, ...}) request) ;=> response 

Facciamo rimpicciolire un po '. Otteniamo un altro gestore:

((GET "/" [] "Hello") request) ;=> response 

Esaminiamo un po 'di più. Troviamo la my-routes gestore:

(my-routes request) ;=> response 

Beh, e se si voleva fare qualcosa prima di inviare la richiesta al gestore di my-routes? Puoi avvolgerlo con un altro gestore.

((fn [req] (println "Request came in!") (my-routes req)) request) ;=> response 

Questo è un po 'difficile da leggere, quindi scoppiamo per chiarezza. Possiamo definire una funzione che restituisce quel gestore. Il middleware è una funzione che accetta un gestore e lo avvolge con un altro gestore. Non restituisce una risposta. Restituisce un gestore che può restituire una risposta.

(defn println-middleware [wrapped-func] 
    (fn [req] 
    (println "Request came in!") 
    (wrapped-func req))) 

((println-middleware my-route) request) ;=> response 

E se abbiamo bisogno di fare qualcosa prima ancora println-middleware riceve la richiesta, quindi siamo in grado di avvolgere di nuovo:

((outer-middleware (println-middleware my-routes)) request) ;=> response 

La chiave è che my-routes, proprio come il vostro my-handler, è l'unico nome funzione che in realtà accetta la richiesta come argomento.

Una dimostrazione finale:

(handler3 (handler2 (handler1 request))) ;=> response 
((middleware1 (middleware2 (middleware3 handler1))) request) ;=> response 

scrivo così tanto perché posso simpatizzare. Ma torna al mio primo esempio di middleware e speriamo che abbia più senso.

11

Il middleware ad anello è una serie di funzioni che, una volta impilate, restituiscono una funzione di gestore.

La sezione dell'articolo che risponde alla tua domanda:

In caso di involucri anello, di solito abbiamo “prima” decoratori che eseguire alcune preparazioni prima di chiamare la funzione di business “reale”. Poiché si tratta di funzioni di ordine superiore e non di chiamate con funzione diretta, vengono applicate in ordine inverso. Se l'una dipende dall'altra, il dipendente da deve trovarsi sul "dentro".

Ecco un esempio inventato:

(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)"))) 

Questa stampe e ritorni:

(this was the input), pre-around, pre-wrapped, post-wrapped, post-around 
nil 
5

Come forse sapete l'anello app è in realtà solo una funzione che riceve una mappa request e ritorna una mappa response.

Nel primo caso l'ordine in cui vengono applicate le funzioni è questo:

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

wrap-keyword-params cerca la chiave :params nel request ma non è lì dal wrap-params è colui che aggiunge quella chiave sulla base di i parametri "urlencoded" dalla stringa di query e dal corpo del modulo ".

Quando invertite l'ordine di questi due:

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

si ottiene il risultato desiderato dal momento che una volta che il request arriva a wrap-keyword-params, wrap-params ha già aggiunto i tasti corrispondenti.

Problemi correlati