Obsah
1. Leiningen: nástroj pro správu projektů napsaných v Clojure (5)
2. První demonstrační příklad – webová aplikace s jednoduchým handlerem
3. Zpracování parametrů požadavku
4. Druhý demonstrační příklad – použití ring.middleware.params/wrap-params
6. Třetí demonstrační příklad – použití ring.middleware.session/wrap-session
7. Implementace čítače uloženého v session
8. Čtvrtý demonstrační příklad – uložení hodnoty čítače do session
9. Pátý demonstrační příklad – uložení hodnoty čítače do session (zjednodušená varianta)
10. Šestý demonstrační příklad – zobrazení hodnoty čítače na WWW stránce
12. Sedmý demonstrační příklad – použití ring.middleware.cookies/wrap-cookies
13. Přečtení hodnoty z cookie a nastavení nové hodnoty
1. Leiningen: nástroj pro správu projektů napsaných v Clojure (5)
Ve čtvrté části článku o nástroji Leiningen jsme si popsali některé základní koncepty, na níž je postavena knihovna Clojure Ring, kterou je možné využít pro tvorbu jednodušších i složitějších webových aplikací v programovacím jazyku Clojure. Připomeňme si, že tato knihovna je abstrakcí mezi webovým kontejnerem (kterým je typicky Jetty či Tomcat) a aplikační logikou. Základem knihovny Clojure Ring je takzvaný handler, což je funkce zavolaná ve chvíli příchodu HTTP požadavku od klienta. Handler musí zajistit vytvoření HTTP odpovědi, přičemž je Ring postaven do značné míry na základních principech funkcionálního programování: handler dostane jako parametr neměnnou (immutable) mapu představující požadavek klienta (request) a výsledkem jeho práce je jiná neměnná mapa reprezentující odpověď serveru (response). Handler lze tedy snadno testovat, rozšiřovat apod.
Webová aplikace využívající knihovnu Ring může být v nejjednodušším případě složena pouze ze tří vrstev:
- Aplikační logika
- Ring Adapter
- Webový server/servlet kontejner/vestavěný kontejner
Navíc se ještě v knihovně Clojure Ring objevuje další zajímavý koncept. Jedná se o takzvané middleware, což jsou v pojetí Ringu makra a funkce vkládané mezi Ring a vlastní implementaci webové aplikace. Tyto funkce a makra tedy například mohou z původní mapy s požadavkem vytvořit novou mapu, v níž již budou předpřipraveny parametry požadavku, cookies, objekty uložené na session atd. My si v dnešním článku popíšeme trojici již připravených middleware:
ring.middleware.params/wrap-params |
ring.middleware.session/wrap-session |
ring.middleware.cookies/wrap-cookies |
2. První demonstrační příklad – webová aplikace s jednoduchým handlerem
Dnešní první demonstrační příklad vznikl nepatrnou úpravou příkladů, které jsme si již popsali v předchozí části tohoto článku. Tento příklad uvádím zejména z toho důvodu, že na jeho zdrojovém kódu bude postaveno i šest ostatních demonstračních příkladů, takže si v dalších kapitolách již nebudeme popisovat tvorbu základní kostry webové aplikace, její spuštění apod. Vytvoření nového projektu nám zajistí notoricky známý příkaz:
lein new app webapp5
Následně upravíme soubor project.clj takovým způsobem, aby se projekt odkazoval na všechny potřebné moduly knihovny Clojure Ring (zvýrazněné řádky):
(defproject webapp3 "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://example.com/FIXME" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :dependencies [[org.clojure/clojure "1.6.0"] [ring/ring-core "1.3.2"] [ring/ring-jetty-adapter "1.3.2"]] :main ^:skip-aot webapp3.core :target-path "target/%s" :profiles {:uberjar {:aot :all}})
Změní se samozřejmě i soubor src/webapp5/core.clj, a to následujícím způsobem:
(ns webapp6.core (:gen-class)) (require '[ring.adapter.jetty :as jetty]) (require '[ring.util.response :as response]) (def html-page " <html> <head> <title>Powered by Ring!</title> </head> <body> <h1>Powered by Ring!</h1> </body> </html> ") (defn handler "Zpracovani pozadavku." [request] (-> (response/response html-page) (response/content-type "text/html; charset=utf-8"))) (def app "Datova struktura predstavujici kostru webove aplikace." (-> handler)) (defn -main "Spusteni webove aplikace na portu 8080." [& args] (jetty/run-jetty app {:port 8080}))
Ve zdrojovém textu vidíme definice čtyř symbolů:
# | Symbol | Význam |
---|---|---|
1 | html-page | navázaný na řetězec představující HTML stránku poslanou klientovi |
2 | handler | navázaný na funkci reprezentující handler pro zpracování požadavku |
3 | app | datová struktura představující kostru webové aplikace |
4 | -main | navázaný na funkci spuštěnou nástrojem Leiningen |
Aplikaci si můžeme jednoduše otestovat:
lein run
Následně se k aplikaci připojíme z CLI klienta:
curl -v localhost:8080
* Rebuilt URL to: localhost:8080/ * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8080 (#0) > GET / HTTP/1.1 > User-Agent: curl/7.35.0 > Host: localhost:8080 > Accept: */* > < HTTP/1.1 200 OK < Date: Tue, 10 Mar 2015 17:34:31 GMT < Content-Type: text/html;charset=UTF-8 < Content-Length: 174 * Server Jetty(7.6.13.v20130916) is not blacklisted < Server: Jetty(7.6.13.v20130916) < <html> <head> <title>Powered by Ring!</title> </head> <body> <h1>Powered by Ring!</h1> </body> </html> * Connection #0 to host localhost left intact
3. Zpracování parametrů požadavku
Jednou ze základních funkcí prakticky každého webového serveru je rozpoznání a zpracování parametrů požadavků, které server přijme od klienta. Připomeňme si, že protokol HTTP rozlišuje několik možností, jak pametry serveru přidat: buď je možné parametry zakódovat přímo do URL (metoda GET) nebo je alternativně možné parametry předat v těle požadavku (metoda POST). Aby bylo možné parametry získat a zpracovat nezávisle na použité metodě, lze v případě knihovny Clojure Ring použít middleware ring.middleware.params/wrap-params. Tento middleware zajistí vytvoření mapy obsahující původní požadavek (request) i již zpracované parametry. Datová struktura, kterou je definována kostra webové aplikace, bude vypadat následovně:
(def app "Datova struktura predstavujici kostru webove aplikace." (-> handler ring.middleware.params/wrap-params))
Makro -> zajišťuje především lepší čitelnost s menším množstvím závorek. Namísto tohoto makra by bylo možné provést i tento zápis:
(def app "Datova struktura predstavujici kostru webove aplikace." (ring.middleware.params/wrap-params handler))
To se však se zvyšujícím se počtem middleware stává méně srozumitelné.
4. Druhý demonstrační příklad – použití ring.middleware.params/wrap-params
Podívejme se nyní na postup úpravy dnešního prvního demontračního příkladu takovým způsobem, aby se mohly jednoduše zpracovávat parametry požadavku (requestu). První úprava spočívá v odlišné deklaraci app, což již bylo naznačeno v předchozí kapitole (pouze se použije zkrácené jméno díky importu a vytvoření aliasu):
(def app "Datova struktura predstavujici kostru webove aplikace." (-> handler http-params/wrap-params))
Dále se v handleru přečtou parametry uložené pod klíčem :params. Následně se celá struktura nesoucí informace o všech parametrech jednoduše vytiskne na standardní výstup:
(defn handler "Zpracovani pozadavku." [request] (let [params (:params request)] (println "Params: " params)) (-> (response/response html-page) (response/content-type "text/html; charset=utf-8")))
Následuje výpis zdrojového kódu celého dnešního druhého demonstračního příkladu:
(ns webapp7.core (:gen-class)) (require '[ring.adapter.jetty :as jetty]) (require '[ring.middleware.params :as http-params]) (require '[ring.util.response :as response]) (def html-page " <html> <head> <title>Powered by Ring!</title> </head> <body> <h1>Powered by Ring!</h1> </body> </html> ") (defn handler "Zpracovani pozadavku." [request] (let [params (:params request)] (println "Params: " params)) (-> (response/response html-page) (response/content-type "text/html; charset=utf-8"))) (def app "Datova struktura predstavujici kostru webove aplikace." (-> handler http-params/wrap-params)) (defn -main "Spusteni webove aplikace na portu 8080." [& args] (jetty/run-jetty app {:port 8080}))
Opět můžeme provést několik testů. V následujících výpisech je nejdříve zobrazen požadavek od CLI klienta a na dalších řádcích pak text vypsaný serverem/webovou aplikací (klient se samozřejmě spouští z jiného terminálu):
curl "localhost:8080" Params: {}
curl "localhost:8080?param1=Hello¶m2=World" Params: {param1 Hello, param2 World}
curl localhost:8080?message="Message%20with%20spaces" Params: {message Message with spaces}
5. Práce se session
Zpracování parametrů požadavku je poměrně triviální, ovšem zajímavější je již práce se session. Knihovna Clojure Ring samozřejmě obsahuje možnost ukládat stav (libovolné objekty) na session a při dalším požadavku tyto objekty opět získat, ovšem musíme mít na paměti, že mapa představující session je neměnná (immutable), což může být především zpočátku poněkud matoucí. Pokud potřebujeme pracovat se session, lze použít middleware ring.middleware.session/wrap-session, který upraví původní mapu s daty požadavku takovým způsobem, že nová mapa bude obsahovat i klíč :session, na nějž je navázána mapa s objekty uloženými v sezení. Nově upravený handler může vypadat následovně:
(def app "Datova struktura predstavujici kostru webove aplikace." (-> handler ring.middleware.session/wrap-session ring.middleware.params/wrap-params))
(zde se již ukazuje přednost použití makra ->)
6. Třetí demonstrační příklad – použití ring.middleware.session/wrap-session
V dnešním třetím demonstračním příkladu je ukázán základní způsob práce se session. Datová struktura app je upravena způsobem popsaným v předchozí kapitole, takže si ji již nemusíme znovu uvádět. Pokud je skutečně aplikován middleware ring.middleware.session/wrap-session, obsahuje nově vytvořená mapa request i klíč :session navázaný na další mapu, ve které mohou (ale nemusí) být uloženy libovolné objekty. Následuje výpis úplného zdrojového kódu dnešního třetího demonstračního příkladu:
(ns webapp8.core (:gen-class)) (require '[ring.adapter.jetty :as jetty]) (require '[ring.middleware.params :as http-params]) (require '[ring.middleware.session :as http-session]) (require '[ring.util.response :as response]) (def html-page " <html> <head> <title>Powered by Ring!</title> </head> <body> <h1>Powered by Ring!</h1> </body> </html> ") (defn handler "Zpracovani pozadavku." [request] (let [params (:params request) session (:session request)] (println "Params: " params) (println "Session: " session)) (-> (response/response html-page) (response/content-type "text/html; charset=utf-8"))) (def app "Datova struktura predstavujici kostru webove aplikace." (-> handler http-session/wrap-session http-params/wrap-params)) (defn -main "Spusteni webove aplikace na portu 8080." [& args] (jetty/run-jetty app {:port 8080}))
Aplikaci si můžeme otestovat:
lein run 2015-03-10 19:07:15.369:INFO:oejs.Server:jetty-7.6.13.v20130916 2015-03-10 19:07:15.405:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:8080
wget localhost:8080 Params: {} Session: {}
7. Implementace čítače uloženého v session
Nyní se již konečně dostáváme k praktickému příkladu. Bude se jednat o jednoduchou webovou aplikaci, která bude mít na session uložený čítač. Pokud klient bude aplikaci posílat více požadavků, bude se čítač zvyšovat o jedničku. Jak lze však tohoto chování dosáhnout? Základem je, že v odpovědi (response), což je běžná mapa, musí být obsažen i klíč :session navázaný na další pod-mapu, v níž jsou uloženy jednotlivé objekty, které se mají na session zachovat pro další požadavek. Stačí tedy upravit odpověď (vracenou handlerem) následujícím způsobem:
(-> (response/response html-page) (response/content-type "text/html; charset=utf-8") (assoc :session new-session)))))
Připomeňme si, že funkce assoc vezme svůj první parametr (což musí být mapa) a vrátí novou mapu, která obsahuje jak mapu původní, tak i novou dvojici klíč-hodnota (druhý a třetí parametr). Z výše uvedeného zápisu by se mohlo zdát, že assoc voláme jen se dvěma parametry, ve skutečnosti to však není pravda, protože je celé volání uzavřeno v makru → (je skutečně dobré si chování tohoto makra nastudovat a důsledně ho používat :-). Výše uvedený zápis tedy vede k pouhému přidání nové dvojice :session+new_session do mapy reprezentující odpověď serveru (response).
Poznámka: dokumentace k funkci assoc:
user=> (doc assoc) ------------------------- clojure.core/assoc ([map key val] [map key val & kvs]) assoc[iate]. When applied to a map, returns a new map of the same (hashed/sorted) type, that contains the mapping of key(s) to val(s). When applied to a vector, returns a new vector that contains val at index. Note - index must be <= (count vector).
8. Čtvrtý demonstrační příklad – uložení hodnoty čítače do session
Se znalostí toho, jak se čte původní session i jak se nová session přidává do odpovědi serveru (response), již můžeme v handleru realizovat jednoduchý čítač:
(defn handler "Zpracovani pozadavku." [request] (let [params (:params request) old-session (:session request) counter (:counter old-session 0)] (println "Params: " params) (println "Old session: " old-session) (println "Counter: " counter) (let [new-session (assoc old-session :counter (inc counter))] (println "New session: " new-session) (-> (response/response html-page) (response/content-type "text/html; charset=utf-8") (assoc :session new-session)))))
Stručný popis funkce handleru:
- Získají se parametry požadavku a uloží se do lokální proměnné params.
- Získá se session požadavku a uloží se do lokální proměnné old-session.
- Získá se hodnota čítače (pokud neexistuje tak 0) ze session a uloží se do proměnné counter.
- Všechny tři údaje se vytisknou na standardní výstup.
- Vytvoří se proměnná new-session obsahující v prvku :counter novou hodnotu čítače.
- Nová session se vytiskne na standardní výstup.
- Vygeneruje se mapa obsahující odpověď serveru (response).
Následuje výpis celého zdrojového kódu demonstračního příkladu:
(ns webapp9.core (:gen-class)) (require '[ring.adapter.jetty :as jetty]) (require '[ring.middleware.params :as http-params]) (require '[ring.middleware.session :as http-session]) (require '[ring.util.response :as response]) (def html-page " <html> <head> <title>Powered by Ring!</title> </head> <body> <h1>Powered by Ring!</h1> </body> </html> ") (defn handler "Zpracovani pozadavku." [request] (let [params (:params request) old-session (:session request) counter (:counter old-session 0)] (println "Params: " params) (println "Old session: " old-session) (println "Counter: " counter) (let [new-session (assoc old-session :counter (inc counter))] (println "New session: " new-session) (-> (response/response html-page) (response/content-type "text/html; charset=utf-8") (assoc :session new-session))))) (def app "Datova struktura predstavujici kostru webove aplikace." (-> handler http-session/wrap-session http-params/wrap-params)) (defn -main "Spusteni webove aplikace na portu 8080." [& args] (jetty/run-jetty app {:port 8080}))
Opět si můžeme aplikaci otestovat a to otevřením stránky z adresy localhost:8080 v prohlížeči a několikerým stlačením F5/Reload. Webová aplikace by měla na standardní výstup vypsat následující zprávy:
lein run 2015-03-10 19:33:34.809:INFO:oejs.Server:jetty-7.6.13.v20130916 2015-03-10 19:33:34.845:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:8080
Params: {} Old session: {} Counter: 0 New session: {:counter 1} Params: {} Old session: {:counter 1} Counter: 1 New session: {:counter 2} Params: {} Old session: {:counter 2} Counter: 2 New session: {:counter 3} Params: {} Old session: {:counter 3} Counter: 3 New session: {:counter 4} Params: {} Old session: {:counter 4} Counter: 4 New session: {:counter 5} Params: {} Old session: {:counter 5} Counter: 5 New session: {:counter 6}
Vidíme, že skutečně vše pracuje korektně.
9. Pátý demonstrační příklad – uložení hodnoty čítače do session (zjednodušená varianta)
V předchozím demonstračním příkladu jsme vytvářeli novou mapu se session, která se ukládala do nové lokální proměnné (přesněji řečeno se navázala na lokální symbol). To však ve skutečnosti není nutné, protože novou session lze jednoduše vytvořit ze session staré na jediném řádku, což je patrné z následujícího úryvku kódu:
... ... ... (-> (response/response html-page) (response/content-type "text/html; charset=utf-8") (assoc :session {:counter (inc counter)}))))
Přičemž hodnotu navázanou na lokální symbol counter přečteme z původní session (a když hodnota neexistuje, inicializuje se čítač na nulu):
(let [session (:session request) counter (:counter session 0)] ... ... ...
Úplný zdrojový kód dnešního pátého demonstračního příkladu je nepatrně kratší než kód předchozí:
(ns webapp10.core (:gen-class)) (require '[ring.adapter.jetty :as jetty]) (require '[ring.middleware.params :as http-params]) (require '[ring.middleware.session :as http-session]) (require '[ring.util.response :as response]) (def html-page " <html> <head> <title>Powered by Ring!</title> </head> <body> <h1>Powered by Ring!</h1> </body> </html> ") (defn handler "Zpracovani pozadavku." [request] (let [params (:params request) session (:session request) counter (:counter session 0)] (println "Params: " params) (println "Session: " session) (println "Counter: " counter) (-> (response/response html-page) (response/content-type "text/html; charset=utf-8") (assoc :session {:counter (inc counter)})))) (def app "Datova struktra predstavujici kostru webove aplikace." (-> handler http-session/wrap-session http-params/wrap-params)) (defn -main "Spusteni webove aplikace na portu 8080." [& args] (jetty/run-jetty app {:port 8080}))
Kontrola korektnosti aplikace otevřením stránky localhost:8080 a několikerým stlačením F5/Reload:
lein run 2015-03-10 19:58:00.456:INFO:oejs.Server:jetty-7.6.13.v20130916 2015-03-10 19:58:00.488:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:8080
Params: {} Session: {} Counter: 0 Params: {} Session: {:counter 1} Counter: 1 Params: {} Session: {:counter 2} Counter: 2 Params: {} Session: {:counter 3} Counter: 3 Params: {} Session: {:counter 4} Counter: 4
(výstup je nepatrně odlišný, protože již nemáme k dispozici původní i novou mapu session).
10. Šestý demonstrační příklad – zobrazení hodnoty čítače na WWW stránce
V šestém (již předposledním) demonstračním příkladu je ukázáno, jak lze hodnotu čítače jednoduše zobrazit přímo na HTML stránce. Úprava předchozího demonstračního příkladu je vlastně pouze minimální – namísto řetězce navázaného na symbol html-page se použije funkce create-html-page, které se předá hodnota čítače. Povšimněte si použití univerzální funkce str, která relativně čitelným způsobem dokáže spojit řetězcové literály s hodnotou čítače:
(defn create-html-page "Vygenerovani HTML stranky." [counter] (str " <html> <head> <title>Powered by Ring!</title> </head> <body> <h1>Powered by Ring!</h1> <p>Counter: " counter "</p> </body> </html> "))
Samotný handler je upraven pouze na jediném (zvýrazněném) řádku:
(defn handler "Zpracovani pozadavku." [request] (let [params (:params request) session (:session request) counter (:counter session 0)] (println "Params: " params) (println "Session: " session) (println "Counter: " counter) (-> (response/response (create-html-page counter)) (response/content-type "text/html; charset=utf-8") (assoc :session {:counter (inc counter)}))))
Opět si uveďme úplný zdrojový kód šestého demonstračního příkladu:
(ns webapp11.core (:gen-class)) (require '[ring.adapter.jetty :as jetty]) (require '[ring.middleware.params :as http-params]) (require '[ring.middleware.session :as http-session]) (require '[ring.util.response :as response]) (defn create-html-page "Vygenerovani HTML stranky." [counter] (str " <html> <head> <title>Powered by Ring!</title> </head> <body> <h1>Powered by Ring!</h1> <p>Counter: " counter "</p> </body> </html> ")) (defn handler "Zpracovani pozadavku." [request] (let [params (:params request) session (:session request) counter (:counter session 0)] (println "Params: " params) (println "Session: " session) (println "Counter: " counter) (-> (response/response (create-html-page counter)) (response/content-type "text/html; charset=utf-8") (assoc :session {:counter (inc counter)})))) (def app "Datova struktra predstavujici kostru webove aplikace." (-> handler http-session/wrap-session http-params/wrap-params)) (defn -main "Spusteni webove aplikace na portu 8080." [& args] (jetty/run-jetty app {:port 8080}))

Obrázek 1: Webová aplikace s čítačem.
11. Základy práce s cookies
Kromě sezení (session) lze v knihovně Clojure Ring přímo pracovat i s cookies, ostatně session bývá obvykle realizováno právě přes cookies. K tomu, aby se k mapě představující původní požadavek přidaly i informace o cookies, lze použít middleware se jménem ring.middleware.cookies/wrap-cookies, které k mapě požadavku přidá další dvojici klíč-hodnota, kde klíč má (podle očekávání) jméno :cookies. Kostra webové aplikace může vypadat následovně:
(def app "Datova struktra predstavujici kostru webove aplikace." (-> handler ring.middleware.cookies/wrap-cookies ring.middleware.params/wrap-params))
Pokud se mají zpracovat parametry požadavku, session i cookies, dostaneme následující strukturu:
(def app "Datova struktra predstavujici kostru webove aplikace." (-> handler ring.middleware.cookies/wrap-cookies ring.middleware.session/wrap-session ring.middleware.params/wrap-params))
U třech middleware aplikovaných za sebou a postupně obalujících původní mapu s informacemi o požadavku se již v plné kráse ukazují možnosti makra ->.
To však není vše – kromě přečtení cookies je nutné umět i vytvořit nové cookie. Nejjednodušším způsobem je předat další parametry funkci ring.util.response, například:
(ring.util.response/set-cookie :user-name "Pavel")
Popř. při požadavku na nastavení dalších vlastností cookie (zde konkrétně životnosti):
(ring.util.response/set-cookie :user-name "Pavel" {:max-age 36000000})
12. Sedmý demonstrační příklad – použití ring.middleware.cookies/wrap-cookies
Dnešní sedmý a současně i poslední demonstrační příklad je (alespoň prozatím) nejsložitější, ovšem můžeme si na něm ukázat jak přečtení parametrů požadavku, tak i základy práce se session. Tato webová aplikace zobrazí na straně klienta formulář, v němž se nachází textové políčko pro zadání jména a tlačítko nadepsané „Remember me“ pro odeslání formuláře na server. O tuto část se stará funkce nazvaná create-html-page. Za povšimnutí stojí fakt, že se formulář správně zobrazí i za předpokladu, že parametr user-name bude mít hodnotu nil:
(defn create-html-page "Vygenerovani HTML stranky." [user-name] (str " <html> <head> <title>Powered by Ring!</title> </head> <body> <h1>Powered by Ring!</h1> <form method='post' action='/'> User name: <input type='text' name='user-name' value='" user-name "'/><br /> <input type='submit' value='Remember me' /> </form> </body> </html> "))
Vzhledem k tomu, že potřebujeme číst parametry požadavku a současně i pracovat s cookies, musí kostra aplikace vypadat takto:
(def app "Datova struktra predstavujici kostru webove aplikace." (-> handler cookies/wrap-cookies http-params/wrap-params))
Handler musí přečíst parametr se jménem user-name i stejně pojmenovaný objekt z cookies. Ani jeden z těchto objektů nemusí být nalezen, typicky při prvním přístupu na stránku webové aplikace, což však nevadí, protože or dokáže bez problémů zpracovat i hodnoty nil (žádné NullPointerException nenastane :-). Pokud je na formuláři zadáno nové jméno, tak or vrátí právě toto jméno, pokud však formulář žádné jméno neobsahuje (nově spuštěný prohlížeč), přečte a vrátí se jméno z cookie:
(defn handler "Zpracovani pozadavku." [request] (let [params (:params request) cookies (:cookies request) new-user-name (get params "user-name") old-user-name (get (get cookies "user-name") :value) user-name (or new-user-name old-user-name)] (println "New user name " new-user-name) (println "Old user name " old-user-name) (println "Incoming cookies: " cookies) (let [response (generate-response user-name)] (println "Outgoing cookies: " (get response :cookies)) response)))
Odpověď serveru je vytvořena v nové funkci, a to především z toho důvodu, že potřebujeme na standardní výstup vypsat jak starou, tak i novou hodnotu cookie(s):
(defn generate-response "Vytvoreni odpovedi." [user-name] (let [html-output (create-html-page user-name)] (if user-name (-> (http-response/response html-output) (http-response/set-cookie :user-name user-name {:max-age 36000000}) (http-response/content-type "text/html")) (-> (http-response/response html-output) (http-response/content-type "text/html")))))
Opět se podívejme na úplný zdrojový kód aplikace:
(ns webapp12.core (:gen-class)) (require '[ring.adapter.jetty :as jetty]) (require '[ring.middleware.params :as http-params]) (require '[ring.util.response :as http-response]) (require '[ring.middleware.cookies :as cookies]) (defn create-html-page "Vygenerovani HTML stranky." [user-name] (str " <html> <head> <title>Powered by Ring!</title> </head> <body> <h1>Powered by Ring!</h1> <form method='post' action='/'> User name: <input type='text' name='user-name' value='" user-name "'/><br /> <input type='submit' value='Remember me' /> </form> </body> </html> ")) (defn generate-response "Vytvoreni odpovedi." [user-name] (let [html-output (create-html-page user-name)] (if user-name (-> (http-response/response html-output) (http-response/set-cookie :user-name user-name {:max-age 36000000}) (http-response/content-type "text/html")) (-> (http-response/response html-output) (http-response/content-type "text/html"))))) (defn handler "Zpracovani pozadavku." [request] (let [params (:params request) cookies (:cookies request) new-user-name (get params "user-name") old-user-name (get (get cookies "user-name") :value) user-name (or new-user-name old-user-name)] (println "New user name " new-user-name) (println "Old user name " old-user-name) (println "Incoming cookies: " cookies) (let [response (generate-response user-name)] (println "Outgoing cookies: " (get response :cookies)) response))) (def app "Datova struktra predstavujici kostru webove aplikace." (-> handler cookies/wrap-cookies http-params/wrap-params)) (defn -main "Spusteni webove aplikace na portu 8080." [& args] (jetty/run-jetty app {:port 8080}))
13. Přečtení hodnoty z cookie a nastavení nové hodnoty
Podívejme se, jak se bude aplikace chovat při testování. Předpokládejme, že v prohlížeči se nejdříve zobrazí prázdný formulář, posléze se zadá jméno „Pavel“, prohlížeč se vypne, zapne a jméno se změní na „tisnik“:
lein run 2015-03-10 20:29:49.330:INFO:oejs.Server:jetty-7.6.13.v20130916 2015-03-10 20:29:49.382:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:8080
První zobrazení formuláře: New user name nil Old user name nil Incoming cookies: {} Outgoing cookies: nil Zadání jména a odeslání formuláře: New user name Pavel Old user name nil Incoming cookies: {} Outgoing cookies: {:user-name {:max-age 36000000, :value Pavel}} Reload: New user name Pavel Old user name Pavel Incoming cookies: {user-name {:value Pavel}} Outgoing cookies: {:user-name {:max-age 36000000, :value Pavel}} Zadání nového jména: New user name tisnik Old user name Pavel Incoming cookies: {user-name {:value Pavel}} Outgoing cookies: {:user-name {:max-age 36000000, :value tisnik}}

Obrázek 2: Formulář zobrazený ve webovém prohlížeči.

Obrázek 3: Aplikace skutečně vytvořila cookie.

Obrázek 4: Informace o cookie: jméno, hodnota, doba platnosti.
14. Odkazy na Internetu
- Clojure Ring na GitHubu
https://github.com/ring-clojure/ring - A brief overview of the Clojure web stack
https://brehaut.net/blog/2011/ring_introduction - Getting Started with Ring
http://www.learningclojure.com/2013/01/getting-started-with-ring.html - Getting Started with Ring and Compojure – Clojure Web Programming
http://www.myclojureadventure.com/2011/03/getting-started-with-ring-and-compojure.html - Leiningen: nástroj pro správu projektů napsaných v Clojure
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (2)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-2/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (3)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-3/ - Leiningen: nástroj pro správu projektů napsaných v Clojure (4)
http://www.root.cz/clanky/leiningen-nastroj-pro-spravu-projektu-napsanych-v-clojure-4/ - Unit Testing in Clojure
http://nakkaya.com/2009/11/18/unit-testing-in-clojure/ - Testing in Clojure (Part-1: Unit testing)
http://blog.knoldus.com/2014/03/22/testing-in-clojure-part-1-unit-testing/ - API for clojure.test – Clojure v1.6 (stable)
https://clojure.github.io/clojure/clojure.test-api.html - Leiningen: úvodní stránka
http://leiningen.org/ - Leiningen: Git repository
https://github.com/technomancy/leiningen - leiningen-win-installer
http://leiningen-win-installer.djpowell.net/ - Clojure 1: Úvod
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm/ - Clojure 2: Symboly, kolekce atd.
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-2-cast/ - Clojure 3: Funkcionální programování
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-3-cast-funkcionalni-programovani/ - Clojure 4: Kolekce, sekvence a lazy sekvence
http://www.root.cz/clanky/clojure-aneb-jazyk-umoznujici-tvorbu-bezpecnych-vicevlaknovych-aplikaci-pro-jvm-4-cast-kolekce-sekvence-a-lazy-sekvence/ - Clojure 5: Sekvence, lazy sekvence a paralelní programy
http://www.root.cz/clanky/clojure-a-bezpecne-aplikace-pro-jvm-sekvence-lazy-sekvence-a-paralelni-programy/ - Clojure 6: Podpora pro paralelní programování
http://www.root.cz/clanky/programovaci-jazyk-clojure-6-futures-nejsou-jen-financni-derivaty/ - Clojure 7: Další funkce pro paralelní programování
http://www.root.cz/clanky/programovaci-jazyk-clojure-7-dalsi-podpurne-prostredky-pro-paralelni-programovani/ - Clojure 8: Identity, stavy, neměnné hodnoty a reference
http://www.root.cz/clanky/programovaci-jazyk-clojure-8-identity-stavy-nemenne-hodnoty-a-referencni-typy/ - Clojure 9: Validátory, pozorovatelé a kooperace s Javou
http://www.root.cz/clanky/programovaci-jazyk-clojure-9-validatory-pozorovatele-a-kooperace-mezi-clojure-a-javou/ - Clojure 10: Kooperace mezi Clojure a Javou
http://www.root.cz/clanky/programovaci-jazyk-clojure-10-kooperace-mezi-clojure-a-javou-pokracovani/ - Clojure 11: Generátorová notace seznamu/list comprehension
http://www.root.cz/clanky/programovaci-jazyk-clojure-11-generatorova-notace-seznamu-list-comprehension/ - Clojure 12: Překlad programů z Clojure do bajtkódu JVM I
http://www.root.cz/clanky/programovaci-jazyk-clojure-12-preklad-programu-z-clojure-do-bajtkodu-jvm/ - Clojure 13: Překlad programů z Clojure do bajtkódu JVM II
2) http://www.root.cz/clanky/programovaci-jazyk-clojure-13-preklad-programu-z-clojure-do-bajtkodu-jvm-pokracovani/ - Clojure 14: Základy práce se systémem maker
http://www.root.cz/clanky/programovaci-jazyk-clojure-14-zaklady-prace-se-systemem-maker/ - Clojure 15: Tvorba uživatelských maker
http://www.root.cz/clanky/programovaci-jazyk-clojure-15-tvorba-uzivatelskych-maker/ - Clojure 16: Složitější uživatelská makra
http://www.root.cz/clanky/programovaci-jazyk-clojure-16-slozitejsi-uzivatelska-makra/ - Clojure 17: Využití standardních maker v praxi
http://www.root.cz/clanky/programovaci-jazyk-clojure-17-vyuziti-standardnich-maker-v-praxi/ - Clojure 18: Základní techniky optimalizace aplikací
http://www.root.cz/clanky/programovaci-jazyk-clojure-18-zakladni-techniky-optimalizace-aplikaci/ - Clojure 19: Vývojová prostředí pro Clojure
http://www.root.cz/clanky/programovaci-jazyk-clojure-19-vyvojova-prostredi-pro-clojure/ - Clojure 20: Vývojová prostředí pro Clojure (Vimu s REPL)
http://www.root.cz/clanky/programovaci-jazyk-clojure-20-vyvojova-prostredi-pro-clojure-integrace-vimu-s-repl/ - Clojure 21: ClojureScript aneb překlad Clojure do JS
http://www.root.cz/clanky/programovaci-jazyk-clojure-21-clojurescript-aneb-preklad-clojure-do-javascriptu/