V minulém článku jsme se podívali na úplně nejzákladnější základy webového vývoje v Clojure - jak zpracovat HTTP request a response pomocí knihovny Ring. Tu nejzajímavější část Ringu - Middleware - jsme ale zmínili jen letmo a byla by škoda se do tohoto zajímavého konceptu trochu více neponořit.
Pokud se podíváme na obecnou podobu middlewaru, má často následující strukturu (kudos to StackOverflow):
Způsob, jak middlewary do sebe zabalit je dvojí - buď klasické zanořené funkce, nebo častější způsob je pomocí thread-first makra (->):
A jen pro úplnost - zabalit se anglicky řekne "wrap", proto se používá konvence, že middlewary začínají prefixem wrap-.
Aby middlewary byly malé a znovupoužitelné, rozdělíme si každou funkčnost, popsanou v předešlých odrážkách, do samostatné funkce:
Teď už stačí jen dát tyto dva middlewary dohromady, abychom dostali požadované chování:
Připomenutí základních konceptů
Než zrychlíme z 0 na 100, připomenu čtyři základní komponenty Ringu:- Handler - funkce, která přijímá mapu reprezentující HTTP request a vrací mapu představující HTTP response.
- Request - mapa reprezentující HTTP request, která obsahuje sadu "standardních" klíčů, které nám budou povědomé ze Servletu: :server-port, :server-name, :remote-address, :request-method ad.
- Response - mapa, představující HTTP response, která obsahuje tři klíče: :status, :header, :body.
- Middleware - funkce, která handleru přidá dodatečné funkcionality. Věci jako session, cookies, či parametry jsou řešené middlewarem.
Anatomie Middlewaru
Middleware je funkce vyššího řádu, která přijímá jako parameter handler a vrací jiný handler (jako anonymní funkci), který nějakým způsobem obohatil buď request, nebo response. V posdtatě se dá na middleware nahlížet jako na takový funkcionální decorator.Pokud se podíváme na obecnou podobu middlewaru, má často následující strukturu (kudos to StackOverflow):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defn wrap-blank-middleware | |
"Blank middleware demonstrating common structure." | |
[handler] | |
(fn [request] | |
; Do some magic with the request | |
(let [response (handler request)] | |
; Do some magic with the response | |
; and return the response. | |
response))) |
Balení middlewaru
Middleware většinou bývá jenom velmi jednoduchá funkce, takže komplexnější chování dostaneme zřetězením jednotlivých middlewarů a to tak, že je postupně zabalujeme do sebe:Způsob, jak middlewary do sebe zabalit je dvojí - buď klasické zanořené funkce, nebo častější způsob je pomocí thread-first makra (->):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; "Classical" middleware wrapping | |
(middleware-3 (middleware-2 (middleware-1 handler))) | |
; Middleware wrapping via threading macro | |
(-> handler (middleware-1) (middleware-2) (middleware-3)) |
A jen pro úplnost - zabalit se anglicky řekne "wrap", proto se používá konvence, že middlewary začínají prefixem wrap-.
Ilustrační příklad
Řekněme, že bysme psali RESTovou službu, která přijímá data pomocí PUT. Pomineme funkční logiku, zpracování dat i routování a budeme se soustředit jen na middleware, který se bude chovat následovně:- Pokud request metoda není PUT, vrátí middleware status 405 a text "Method Not Allowed".
- Pokud je request metoda PUT, vrátí status 204 (No Content) a prázdné body.
Aby middlewary byly malé a znovupoužitelné, rozdělíme si každou funkčnost, popsanou v předešlých odrážkách, do samostatné funkce:
- wrap-no-content bude vracet status 204 a prázdné body.
- wrap-put-allowed vrátí buď 405 (a popis v body), pokud metoda není PUT, nebo jenom zavolá původní handler.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defn wrap-no-content | |
"Middleware that returns a 204 No Content | |
from the wrapped handler." | |
[handler] | |
(fn [request] | |
(let [response (handler request)] | |
(-> response | |
(assoc :status 204) | |
(dissoc :body))))) | |
(defn wrap-put-allowed | |
"Middleware that returns a 405 Method Not Allowed | |
if the request doesn't have :put method." | |
[handler] | |
(fn [request] | |
(if (= (:request-method request) :put) | |
(handler request) | |
(let [response (handler request)] | |
(-> response | |
(assoc :status 405) | |
(assoc :body "Method Not Allowed")))))) |
Teď už stačí jen dát tyto dva middlewary dohromady, abychom dostali požadované chování:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defn wrap-put-no-content | |
"Middleware that returns a 204 No Content | |
if the request method is PUT, otherwise | |
a 405 Method Not Allowed." | |
[handler] | |
(-> handler | |
(wrap-no-content) | |
(wrap-put-allowed))) |
GitHub projekt
Pokud vám výše popsané principy neštymují dohromady, mrkněte na GitHub, kde je malý spustitelný projektík, plus pár middleware unit testů:Co příště?
O Ringu a jeho middlewarech by se dalo psát ještě dlouho, ale je čas pokročit. Logicky následným tématem je Compojure - routovací knihovna pro Ring webové aplikace.Související články
- Clojure web development: Ring
- Clojure web development: Compojure (TBD)
- Clojure web development: Hiccup (TBD)