Zobrazují se příspěvky se štítkemweb. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemweb. Zobrazit všechny příspěvky

11. července 2017

Clojure web development: Ring Middleware

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.

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


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


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.


Teď už stačí jen dát tyto dva middlewary dohromady, abychom dostali požadované chování:


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


1. května 2017

Clojure web development: Ring

Webový vývoj v Clojure je dobře etablovaný. Nebylo by to ale Clojure, kdyby si věci nedělalo trochu po svém. A tak nabízí, místo rozsáhlých aplikačních frameworků, množinu knihoven, které se dají pospojovat dohromady. Trochu to připomíná unixovou filozofii - malé, jednoúčelové prográmky, které lze propojovat do komplexnějších řešení.

Když jde o web, tak jde v první řadě o HTTP. Clojure na to jde od podlahy a jeho odpovědí je Ring - "Clojure HTTP server abstraction". Možná teď nebudu úplně přesný: Ring je Clojure implementací abstrakcí HTTP protokolu a zároveň je částečně kompatibilní s Java Servlety.

Ring vládne čtyřem komponentám

Každá Ring aplikace se skládá ze čtyř základních částí:
  • 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.

Ring Hello, world!

Začneme tradičně, vytvořením Leiningen projektu:
$ lein new blog-ring
a v souboru project.clj doplníme dependency na Ring:


Dále upravíme soubor src/blog_ring.core.clj, aby obsahoval náš Hello, world handler. Přidáme také funkci -main, abychom mohli aplikaci rovnou spustit:


Aplikaci spustíme příkazem:
$ lein run
a můžeme si ji prohlédnout v browseru na URL http://localhost:3000.

Request

Práce s requestem je přímočará - request je v Ringu prezentován mapou, takže stačí pomocí klíče vytáhnout požadovanou hodnotu a nějak ji zpracovat.


Předešlý handler nám vrátí následující stránku:

Response

V hello world příkladu jsme si celou response mapu sestavili sami. Ring nabízí řadu funkcí, soustředěných v namespace ring.util.response, které práci s response usnadňují:


Middleware

Tak, to bylo triviální. Co si teď střihnout nějaký middlewérek? Middleware je, podle definice, "funkce vyššího řádu, která přidává handleru dodatečné funkcionality. Prvním argumentem midddleware funkce je handler a její návratovou hodnotou je nový handler, který bude volat handler původní."

Seznam standard middleware funkcí se dá najít na wiki stránce Ringu, nebo na stránce API (všechno co je ring.middleware).

Pro potřebu článku jsem si vybral dva middlewary:
  • wrap-params, který rozparsuje parametry z formuláře a query stringu a přidá do requestu klíče :query-params, :form-params a :params
  • wrap-keyword-params, který zkonvertuje stringové klíče v mapě :params na keyword klíče (protože všichni přece máme rádi keyword klíče v Clojure mapách).
Middleware funkce se dají dobře testovat pomocí funkce identity, kdy není potřeba funkci podstrčit celý handler a následně request, ale jen část (request) mapy, která nás z hlediska middlewaru zajímá.

Tady je ukázka, co s requestem dělají uvedené funkce wrap-params a wrap-keyword-params:


Jak naznačuje definice, midddlewary jsou navržený tak, aby šly pospojovat za sebe. Je to v podstatě analogie Java (servlet) filtrů. Jak middlewary zřetězíme? Můžeme funkce jednoduše zanořit do sebe. Ale čitelnější, i používanější, je použití thread-first makra:


Kompletní kruh

Tak, a jsme na konci. Probrali jsme všechny čtyři komponenty, ze kterých se Ring skládá - handler, request, response a middleware. Pokdu je všechny spojíme do jednoduché, spustitelné aplikace, může to vypadat takto:


A výsledek v browseru:

Co příště?

Můj původní záměr o příštím článku bylo napsat o routování pomocí Compojure. Když jsem se ale ponořil do psaní sekce o middleware a připravoval si příklady, začalo se tohle pod-téma dost rozrůstat. Middleware je hodně zajímavý koncept - jednoduchý a velmi silný - a proto by bylo škoda se mu nevěnovat samostatně.

GitHub projekt

Pokud vám výše popsané principy neštymují dohromady, mrkněte na GitHub, kde je malý spustitelný projektík:

Související články

12. února 2011

Uzávěr v laboratoři (jak začít)

Dneska bych tady měl dvě "drobnosti" pro ty, kdo chtějí s Clojure začít. Prvně to kratší: Chcete zkusit Clojure a přitom nic neinstalovat, jen si vyzkoušet jazyk? Na stránce Try Clojure je k dispozici online REPL, včetně krátkého tutoriálu. Jako perlička - k dispozici je Google Chrome rozšíření try-clojure-in-chrome, které zmíněnou stránku využívá pro zobrazení REPLu v Chrome.

[Updated: Zdá se, že posledních pár dní je stránka Try Clojure mrtvá. :-/ ]

Hlavní věc, o které bych ale dnes chtěl psát, je výborný Clojure tutoriál labrepl. Tutoriál vytvořil Stuart Halloway, autor vůbec první knihy o Clojure Programming Clojure a také jeden z hlavních contributorů do Clojure samotného.

Musím říct, že labrepl mě vyloženě nadchnul a to jak obsahem, tak implementací. Celý tutoriál je totiž v Clojure jak napsaný (HTML je generovaný pomocí Clojure web frameworku Compojure), tak zbuildovaný (konrétně Leiningenem, o kterém bych chtěl něco napsat příště). Tutoriál se spustí jako REPL, přičemž v rámci něho na pozadí běží web server Jetty s HTML tutoriálem a v již spuštěném REPLu se dají zkoušet jednotlivé příklady. Tento neohrabaný popis asi není moc názorný, takže radši doporučuji přímo vyzkoušet.

Nebudu tady popisovat obsah tutoriálu, jen bych zmínil věc, na kterou jsem zatím online nenarazil. A to je dokumentace přímo v REPLu. To je další vlastnost, která mne nadchla - zatím jsem se nesetkal s jazykem (s částečnou výjimkou v Pythonu), který by tak snadno a v takovém rozsahu něco podobného poskytoval.

Dokumentace funkce:
(doc zipmap)
; -------------------------
; clojure.core/zipmap
; ([keys vals])
;   Returns a map with the keys mapped to the corresponding vals.

Vyhledání dokumentace:
(find-doc "zip")
; -------------------------
; clojure.zip/seq-zip
; ([root])
;   Returns a zipper for nested sequences, given a root sequence
; -------------------------
; clojure.zip/vector-zip
; ([root])
;   Returns a zipper for nested vectors, given a root vector
; -------------------------
; clojure.zip/xml-zip
; ([root])
;   Returns a zipper for xml elements (as from xml/parse),
;   given a root element

Funkce javadoc otevře stránku Javadocu v prohlížeči (buď lokální, nebo vzdálenou):
(javadoc java.util.Date)
; "http://java.sun.com/javase/6/docs/api/java/util/Date.html"
; Created new window in existing browser session.

A nakonec funkce, která je nejvíc cool! source zobrazí implementaci dané funkce:
(source drop)
; (defn drop
;   "Returns a lazy sequence of all but the first n items in coll."
;   {:added "1.0"}
;   [n coll]
;   (let [step (fn [n coll]
;                (let [s (seq coll)]
;                  (if (and (pos? n) s)
;                    (recur (dec n) (rest s))
;                    s)))]
;     (lazy-seq (step n coll))))