2013-10-18 10 views
16

Ich schreibe etwas Middleware für Ring und ich bin wirklich verwirrt, warum ich die Reihenfolge der Middleware umkehren muss.Warum muss die Reihenfolge der Ring-Middleware umgekehrt werden?

Ich habe diese blog post gefunden, aber es erklärt nicht, warum ich es umkehren muss.

Hier ist ein kurzer Auszug aus dem Blog-Post:

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

Die Antwort wäre:

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

Beachten Sie, dass die Wickel Schlüsselwort params auf sie nicht genannt, weil die params-Hash didn existiere noch nicht. Aber wenn Sie die Reihenfolge der Middleware wie folgt umkehren:

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

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

Es funktioniert.

Könnte jemand bitte erklären, warum Sie die Reihenfolge der Middleware umkehren müssen?

Antwort

36

Es hilft zu visualisieren, was Middleware eigentlich ist.

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

Das war genau der a-ha-Moment für mich.

Was auf den ersten Blick verwirrend ist, ist, dass Middleware nicht auf die Anfrage angewendet wird, woran Sie denken.

Daran erinnern, dass ein Ring App ist nur eine Funktion, die eine Anforderung und gibt eine Antwort nimmt (was bedeutet, dass es einen Handler):

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

Lasst uns ein wenig verkleinern. Wir bekommen einen anderen Handler:

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

Lassen Sie uns ein wenig mehr herauszoomen. Wir finden die my-routes Handler:

(my-routes request) ;=> response 

Nun, was ist, wenn Sie etwas vor dem Senden der Anforderung an den my-routes Handler tun wollte? Sie können es mit einem anderen Handler einpacken.

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

Das ist ein wenig schwer zu lesen, also lasst uns aus Gründen der Klarheit ausbrechen. Wir können eine Funktion definieren, die diesen Handler zurückgibt. Middleware sind Funktionen, die einen Handler nehmen und einen anderen Handler umschließen. Es gibt keine Antwort zurück. Es gibt einen Handler zurück, der eine Antwort zurückgeben kann.

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

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

Und wenn wir etwas tun müssen, bevor auch println-middleware die Anfrage kommt, dann können wir es wickeln wieder:

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

Der Schlüssel ist, dass my-routes, genau wie Ihre my-handler, ist die einzige benannt Funktion, die die Anfrage tatsächlich als Argument akzeptiert.

Eine letzte Demonstration:

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

Ich schreibe so viel, weil ich mitfühlen kann. Aber scroll zurück zu meinem ersten middleware Beispiel und hoffentlich macht es mehr Sinn.

11

Die Ring-Middleware ist eine Reihe von Funktionen, die im Stapelbetrieb eine Handler-Funktion zurückgeben.

Der Abschnitt des Artikels, der Ihre Frage beantwortet:

Bei Ring-Wrapper, in der Regel haben wir „vor“ Dekorateure, dass einige Vorbereitungen durchführen, bevor die „echte“ Business-Funktion aufrufen. Da es sich um Funktionen höherer Ordnung und nicht um direkte Funktionsaufrufe handelt, werden sie in umgekehrter Reihenfolge angewendet. Kommt es auf den anderen an, muss der abhängige auf der "Innenseite" sein. Hier

ist ein konstruiertes Beispiel:

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

Diese Drucke und kehrt:

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

Wie Sie wissen kann der Ring app ist eigentlich nur eine Funktion, die eine request Karte und kehrt erhält eine response Karte.

Im ersten Fall, in dem die Reihenfolge der Funktionen angewendet werden, ist dies:

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

wrap-keyword-params sucht nach dem Schlüssel :params im request aber es ist nicht da, da wrap-params derjenige ist, der den Schlüssel basiert fügt auf Die "urlencoded Parameter aus der Abfragezeichenfolge und Formularkörper".

Wenn Sie den Auftrag der beiden letztgenannten invertieren:

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

Sie das gewünschte Ergebnis erhalten, da, sobald die request zu wrap-keyword-params bekommt, hat wrap-params bereits hinzugefügt, um die entsprechenden Tasten.

Verwandte Themen