Pisanje Clojure Webapps-a s prstenom

1. Uvod

Ring je knjižnica za pisanje web aplikacija na Clojureu. Podržava sve što je potrebno za pisanje cjelovitih web aplikacija i ima uspješan ekosustav koji ga čini još snažnijim.

U ovom uputstvu dat ćemo uvod u Ring i pokazati neke stvari koje možemo postići s njim.

Ring nije okvir dizajniran za stvaranje REST API-ja, poput toliko modernih alata. To je okvir niže razine za obradu HTTP zahtjeva općenito, s naglaskom na tradicionalni web razvoj. Međutim, neke knjižnice grade na njemu kako bi podržale mnoge druge željene strukture aplikacija.

2. Ovisnosti

Prije nego što počnemo raditi s Ringom, moramo ga dodati našem projektu. Minimalne ovisnosti koje trebamo su:

  • prsten / prsten-jezgra
  • prsten / prsten-mlazni adapter

U naš projekt Leiningen možemo dodati sljedeće:

 : ovisnosti [[org.clojure / clojure "1.10.0"] [prsten / jezgra prstena "1.7.1"] [adapter prsten / prsten-jetty "1.7.1"]]

Zatim to možemo dodati minimalnom projektu:

(ns ring.core (: koristite ring.adapter.jetty)) (obrađivač defn [zahtjev] {: status 200: zaglavlja {"Tip sadržaja" "text / plain"}: tijelo "Hello World"}) (defn - glavni [& args] (rukovalac rute {: port 3000}))

Ovdje smo definirali funkciju rukovatelja - koju ćemo uskoro pokriti - koja uvijek vraća niz "Hello World". Također, dodali smo našu glavnu funkciju za upotrebu ovog rukovatelja - on će slušati zahtjeve na portu 3000.

3. Temeljni koncepti

Leiningen ima nekoliko temeljnih koncepata oko kojih se sve gradi: zahtjevi, odgovori, rukovatelji i Middleware.

3.1. Zahtjevi

Zahtjevi su prikaz dolaznih HTTP zahtjeva. Prsten predstavlja zahtjev kao mapu, omogućavajući našoj Clojure aplikaciji jednostavnu interakciju s pojedinačnim poljima. Na ovoj karti postoji standardni set ključeva, uključujući, ali ne ograničavajući se na:

  • : uri - Puni URI put.
  • : upit-niz - Puni niz upita.
  • : metoda-zahtjeva - Način zahtjeva, jedan od : get,: head,: post,: put,: delete ili : opcije.
  • : zaglavlja - Karta svih HTTP zaglavlja koja su dostavljena zahtjevu.
  • :tijelo - An InputStream koji predstavlja tijelo zahtjeva, ako je prisutan.

Middleware može dodati više ključeva i na ovu kartu po potrebi.

3.2. Odgovori

Slično tome, odgovori su prikaz odlaznih HTTP odgovora. Ring ih također predstavlja kao mape s tri standardne tipke:

  • :status - Statusni kôd za povratak
  • :zaglavlja - Karta svih HTTP zaglavlja za slanje natrag
  • :tijelo - Neobvezno tijelo za povratak

Kao prije, Middleware može to promijeniti između našeg voditelja koji ga izrađuje i konačnog rezultata koji se šalje klijentu.

Ring također nudi neke pomoćnike kako bi olakšali izgradnju odgovora.

Najosnovniji od njih je ring.util.response / response funkcija koja stvara jednostavan odgovor sa statusnim kodom od 200 OK:

ring.core => (ring.util.response / response "Hello") {: status 200,: zaglavlja {},: body "Hello"}

Postoji nekoliko drugih metoda koje idu uz ovo za uobičajene statusne kodove - na primjer, loš zahtjev, nije pronađeno i preusmjeriti:

ring.core => (ring.util.response / bad-request "Hello") {: status 400,: headers {},: body "Hello"} ring.core => (ring.util.response / created "/ post / 123 ") {: status 201,: headers {" Location "" / post / 123 "},: body nil} ring.core => (ring.util.response / redirect" //ring-clojure.github. io / ring / ") {: status 302,: headers {" Location "" //ring-clojure.github.io/ring/ "},: body" "}

Također imamo status metoda koja će pretvoriti postojeći odgovor u bilo koji proizvoljan statusni kôd:

ring.core => (ring.util.response / status (ring.util.response / response "Hello") 409) {: status 409,: zaglavlja {},: body "Hello"}

Tada imamo neke metode za slično podešavanje ostalih značajki odgovora - na primjer, vrsta sadržaja, zaglavlje ili set-cookie:

ring.core => (ring.util.response / content-type (ring.util.response / response "Hello") "text / plain") {: status 200,: zaglavlja {"Content-Type" "text / plain "},: body" Hello "} ring.core => (ring.util.response / header (ring.util.response / response" Hello ")" X-Tutorial-For "" Baeldung ") {: status 200, : zaglavlja {"X-Tutorial-For" "Baeldung"},: body "Hello"} ring.core => (ring.util.response / set-cookie (ring.util.response / response "Hello") "Korisnik "" 123 ") {: status 200,: headers {},: body" Hello ",: cookies {" User "{: value" 123 "}}}}

Imajte na umu da the set-cookie metoda dodaje potpuno novi unos na kartu odgovora. Ovo treba omot-kolačići međuoprema da ga ispravno obradim da bi mogao raditi.

3.3. Rukovatelji

Sad kad razumijemo zahtjeve i odgovore, možemo početi pisati svoju funkciju rukovatelja kako bismo je povezali.

Rukovatelj je jednostavna funkcija koja uzima dolazni zahtjev kao parametar i vraća odlazni odgovor. Što ćemo raditi u ovoj funkciji, u potpunosti ovisi o našoj aplikaciji, ukoliko odgovara ovom ugovoru.

Najjednostavnije bismo mogli napisati funkciju koja uvijek vraća isti odgovor:

(defn rukovatelj [zahtjev] (ring.util.response / response "Hello"))

Po potrebi možemo komunicirati i sa zahtjevom.

Na primjer, mogli bismo napisati obrađivač za vraćanje dolazne IP adrese:

(defn check-ip-handler [zahtjev] (ring.util.response / content-type (ring.util.response / response (: remote-addr request)) "text / plain"))

3.4. Middleware

Middleware je ime koje je uobičajeno u nekim jezicima, ali manje u svijetu Jave. Konceptualno su slični filtrima servleta i presretačima opruge.

U programu Ring, posrednički softver odnosi se na jednostavne funkcije koje omotavaju glavni rukovatelj i na neki način prilagođavaju neke njegove aspekte. To bi moglo značiti mutiranje dolaznog zahtjeva prije nego što se obradi, mutiranje odlaznog odgovora nakon što je generiran ili potencijalno ne raditi ništa drugo osim bilježenja koliko je vremena trebalo za obradu.

Općenito, funkcije međuopreme uzimaju prvi parametar rukovatelja za umotavanje i vraćaju novu funkciju rukovatelja s novom funkcionalnošću.

Međuprodukt može koristiti onoliko drugih parametara koliko je potrebno. Na primjer, mogli bismo koristiti sljedeće za postavljanje Vrsta sadržaja zaglavlje na svaki odgovor umotanog rukovatelja:

(defn wrap-content-type [handler content-type] (fn [zahtjev] (neka [odgovor (zahtjev rukovatelja)] (pridruženi odgovor [: zaglavlja "Content-Type"] vrsta sadržaja)))))

Čitajući kroz njega možemo vidjeti da vraćamo funkciju koja uzima zahtjev - ovo je novi rukovatelj. Tada će se nazvati pruženi rukovatelj, a zatim će se vratiti mutirana verzija odgovora.

Ovo možemo koristiti za stvaranje novog rukovatelja jednostavnim povezivanjem:

(def app-handler (wrap-content-type handler "text / html"))

Clojure također nudi način da mnoge povežete na prirodniji način - pomoću Threading Macros. To su način za pružanje popisa funkcija za pozivanje, svaka s izlazom prethodne.

Konkretno, želimo makronaredbu Thread First, ->. To će nam omogućiti da svaki međuprogram s navedenom vrijednošću pozovemo kao prvi parametar:

(def app-handler (-> rukovatelj (wrap-content-type "text / html") wrap-keyword-params wrap-params))

Tada je proizveden obrađivač koji je izvorni obrađivač umotan u tri različite funkcije međuopreme.

4. Obrađivači pisanja

Sad kad razumijemo komponente koje čine aplikaciju Ring, moramo znati što možemo učiniti sa stvarnim rukovateljima. To su srce cijele aplikacije i tamo će ići većina poslovne logike.

U te rukovatelje možemo staviti bilo koji kod koji želimo, uključujući pristup bazi podataka ili pozivanje drugih usluga. Ring nam daje neke dodatne mogućnosti za izravan rad s dolaznim zahtjevima ili odlaznim odgovorima koji su također vrlo korisni.

4.1. Posluživanje statičkih resursa

Jedna od najjednostavnijih funkcija koju bilo koja web aplikacija može izvesti je služenje statičkim resursima. Ring nudi dvije funkcije međuopreme da bi to bilo lako - omot-datoteka i omot-resurs.

The omot-datoteka middleware uzima direktorij na datotečnom sustavu. Ako se dolazni zahtjev podudara s datotekom u ovom direktoriju, tada se ta datoteka vraća umjesto pozivanja funkcije rukovatelja:

(upotrijebite 'ring.middleware.file) 
(def app-handler (omot-datoteka your-handler "/ var / www / public"))

Na vrlo sličan način, the omot-resurs middleware uzima prefiks putopisa u kojem traži datoteke:

(upotrijebite 'ring.middleware.resource) 
(def app-handler (wrap-resource vaš-rukovatelj "javni"))

U oba slučaja, funkcija omotanog rukovatelja poziva se samo ako se ne pronađe datoteka koja se vraća klijentu.

Ring također nudi dodatni međuoprema za poboljšanje upotrebe ovih čistijih preko HTTP API-ja:

(koristite 'ring.middleware.resource' ring.middleware.content-type 'ring.middleware.not-Modified) (def app-handler (-> your-handler (wrap-resource "public") wrap-content-type wrap -neizmijenjeno)

The wrap-content-type middleware će automatski odrediti Vrsta sadržaja zaglavlje za postavljanje na temelju tražene ekstenzije datoteke. The omot-nije modificiran middleware uspoređuje Ako se ne mijenja zaglavlje do Zadnja promjena vrijednost koja podržava HTTP predmemoriranje, vraća datoteku samo ako je potrebna.

4.2. Pristup parametrima zahtjeva

Prilikom obrade zahtjeva postoji nekoliko važnih načina na koje klijent može pružiti informacije poslužitelju. To uključuje parametre niza upita - uključene u parametre URL-a i obrasca - predane kao korisni teret zahtjeva za POST i PUT zahtjeve.

Prije nego što možemo koristiti parametre, moramo koristiti wrap-parami međuprodukt za umotavanje rukovatelja. Ovim se pravilno raščlanjuju parametri koji podržavaju kodiranje URL-a i čine ih dostupnima zahtjevu. To prema želji može odrediti kodiranje znakova koje će se koristiti, a prema UTF-8 je zadano ako nije navedeno:

(def app-handler (-> your-handler (wrap-params {: kodiranje "UTF-8"})))

Kad završite, zahtjev će se ažurirati kako bi parametri bili dostupni. Oni idu u odgovarajuće ključeve u dolaznom zahtjevu:

  • : query-params - Parametri su raščlanjeni iz niza upita
  • : form-params - Parametri su raščlanjeni iz tijela obrasca
  • : params - Kombinacija oba : query-params i : form-params

To možemo iskoristiti u našem obrađivaču zahtjeva točno onako kako smo očekivali.

(defn echo-handler [{params: params}] (ring.util.response / content-type (ring.util.response / response (get params "input")) "text / plain"))

Ovaj rukovatelj vratit će odgovor koji sadrži vrijednost iz parametra ulazni.

Parametri se preslikavaju u jedan niz ako je prisutna samo jedna vrijednost ili na popis ako je prisutno više vrijednosti.

Na primjer, dobivamo sljedeće karte parametara:

// / echo? input = hello {"input" hello "} // / echo? input = hello & name = Fred {" input "hello" "name" "Fred"} // / echo? input = hello & input = world {" input ["hello" "world"]}

4.3. Primanje prijenosa datoteka

Često želimo biti u mogućnosti pisati web aplikacije u koje korisnici mogu prenijeti datoteke. U HTTP protokolu to se obično rješava pomoću višedijelnih zahtjeva. Oni omogućuju da jedan zahtjev sadrži i parametre obrasca i skup datoteka.

Ring dolazi s posredničkim softverom pod nazivom wrap-multipart-params za rješavanje ove vrste zahtjeva. Ovo je slično načinu na koji wrap-parami analizira jednostavne zahtjeve.

wrap-multipart-params automatski dekodira i pohranjuje sve učitane datoteke u datotečni sustav i govori obrađivaču gdje su da bi mogao raditi s njima:

(def app-handler (-> vaš rukovatelj wrap-params wrap-multipart-params))

Prema zadanim postavkama, učitane datoteke pohranjuju se u privremeni direktorij sustava i automatski brišu nakon sat vremena. Imajte na umu da ovo zahtijeva da JVM i dalje radi sljedećih sat vremena kako bi izvršio čišćenje.

Ako je poželjno, tu je i trgovina u memoriji, premda očito, riskira se ponestajanje memorije ako se prenose velike datoteke.

Po potrebi možemo pisati i svoje mehanizme za pohranu podataka ukoliko ispunjavaju API zahtjeve.

(def app-handler (-> vaš rukovatelj wrap-params (wrap-multipart-params {: store ring.middleware.multipart-params.byte-array / byte-array-store})))

Jednom kada se ovaj međuprodukt postavi, učitane datoteke su dostupne na dolaznom objektu zahtjeva pod parametarima ključ. To je isto kao i korištenje wrap-parami međuoprema. Ovaj unos je karta koja sadrži detalje potrebne za rad s datotekom, ovisno o korištenoj trgovini.

Na primjer, zadana privremena pohrana datoteka vraća vrijednosti:

 {"datoteka" {: naziv datoteke "words.txt": vrsta sadržaja "text / plain": tempfile #object [java.io.File ...]: veličina 51}}

Gdje je : tempfile unos je a java.io.Datoteka objekt koji izravno predstavlja datoteku na datotečnom sustavu.

4.4. Rad s kolačićima

Kolačići su mehanizam u kojem poslužitelj može pružiti malu količinu podataka koje će klijent i dalje vraćati na sljedeće zahtjeve. To se obično koristi za ID-ove sesija, tokene za pristup ili trajne korisničke podatke kao što su konfigurirane postavke lokalizacije.

Ring ima posrednički softver koji će nam omogućiti lak rad s kolačićima. To će automatski raščlaniti kolačiće na dolazne zahtjeve, a omogućit će nam i stvaranje novih kolačića na odlaznim odgovorima.

Konfiguriranje ovog međuopreme slijedi iste obrasce kao i prije:

(def app-handler (-> omot-kolačići vašeg rukovatelja)))

U ovom trenutku, za sve dolazne zahtjeve kolačići će se raščlaniti i staviti u : kolačići unesite zahtjev. Sadržavat će mapu naziva i vrijednosti kolačića:

{"session_id" {: vrijednost "session-id-hash"}}

Tada odlaznim odgovorima možemo dodati kolačiće dodavanjem znaka : kolačići ključ za odlazni odgovor. To možemo učiniti izravnim stvaranjem odgovora:

{: status 200: zaglavlja {}: kolačići {"session_id" {: vrijednost "session-id-hash"}}: tijelo "Postavljanje kolačića."}

Tu je i pomoćna funkcija koju možemo koristiti za dodavanje kolačića u odgovore, na sličan način na koji smo ranije mogli postaviti statusne kodove ili zaglavlja:

(ring.util.response / set-cookie (ring.util.response / response "Postavljanje kolačića.") "session_id" "session-id-hash")

Kolačići također mogu imati postavljene dodatne opcije, prema potrebi za HTTP specifikacijom. Ako koristimo set-cookie onda ih dajemo kao parametar karte nakon ključa i vrijednosti. Ključevi ove karte su:

  • :domena - Domena na koju je ograničen kolačić
  • :staza - Put do kojeg je ograničen kolačić
  • :siguranpravi za slanje kolačića samo na HTTPS vezama
  • : samo httppravi kako bi kolačić bio nedostupan JavaScript-u
  • : max-dob - Broj sekundi nakon kojih preglednik briše kolačić
  • : istječe - Određena vremenska oznaka nakon koje preglednik briše kolačić
  • : isto mjesto - Ako je postavljeno na :strog, tada preglednik neće vratiti ovaj kolačić sa zahtjevima za više web lokacija.
(ring.util.response / set-cookie (ring.util.response / response "Setting a cookie.") "session_id" "session-id-hash" {: secure true: http-only true: max-age 3600} )

4.5. Sjednice

Kolačići nam daju mogućnost pohrane bitova podataka koje klijent na svaki zahtjev šalje natrag poslužitelju. Moćniji način da se to postigne je korištenje sesija. Oni se u potpunosti pohranjuju na poslužitelj, ali klijent održava identifikator koji određuje koju će sesiju koristiti.

Kao i kod svega ostalog ovdje, sesije se provode pomoću funkcije međuopreme:

(def app-handler (-> sesija premotavanja vašeg rukovatelja))

Prema zadanim postavkama, ovo pohranjuje podatke sesije u memoriju. To možemo promijeniti ako je potrebno, a Ring dolazi s alternativnom trgovinom koja koristi kolačiće za pohranu svih podataka sesije.

Kao i kod prijenosa datoteka, po potrebi možemo pružiti našu funkciju pohrane.

(def app-handler (-> your-handler wrap-cookies (wrap-session {: store (cookie-store {: key "a 16-byte secret)})}))))

Također možemo prilagoditi detalje kolačića koji se koristi za pohranu ključa sesije.

Na primjer, kako bismo napravili da kolačić sesije traje jedan sat, mogli bismo učiniti:

(def app-handler (-> your-handler wrap-cookies (wrap-session {: cookie-attrs {: max-age 3600}}))))

Atributi kolačića ovdje su isti kao što ih podržava omot-kolačići međuoprema.

Sesije često mogu poslužiti kao spremišta podataka za rad. To ne funkcionira uvijek dobro u funkcionalnom programskom modelu, pa ih Ring implementira malo drugačije.

Umjesto toga, pristupamo podacima sesije iz zahtjeva i vraćamo mapu podataka koje ćemo pohraniti u nju kao dio odgovora. Ovo je cijelo stanje sesije za pohranu, a ne samo promijenjene vrijednosti.

Na primjer, sljedeće vodi tekući broj koliko je puta zatražen rukovatelj:

(defn handler [{session: session}]] (let [count (: count session 0) session (assoc session: count (inc count))]] (-> (response (str "Ovoj ste stranici pristupili" count "puta." )) (izv. sjednica sesije))))

Radeći na ovaj način, možemo uklonite podatke iz sesije jednostavno ne uključujući ključ. Također možemo vratiti cijelu sesiju povratkom nula za novu kartu.

(defn rukovatelj [zahtjev] (-> (odgovor "Sesija je izbrisana.") (pridruživanje: sesija nije potrebna)))

5. Dodatak Leiningen

Ring pruža dodatak za alat za izgradnju Leiningen koji pomaže i razvoju i proizvodnji.

Dodatak smo postavili dodavanjem točnih detalja o dodatku u projekt.clj datoteka:

 : dodaci [[lein-ring "0.12.5"]]: ring {: handler ring.core / handler}

Važno je da verzija lein-prsten je točna za verziju Ringa. Ovdje smo koristili Ring 1.7.1, što znači da trebamo lein-prsten 0,12,5. Općenito, najsigurnije je samo upotrijebiti najnoviju verziju obje verzije, kako se vidi na Maven central ili s lein pretraga naredba:

$ lein search ring-core Pretraživanje klojara ... [ring / ring-core "1.7.1"] Biblioteke jezgre prstena. $ lein search lein-ring Traženje clojara ... [lein-ring "0.12.5"] Dodatak Leiningen Ring

The : rukovatelj parametar za :prsten poziv je potpuno kvalificirano ime rukovatelja koje želimo koristiti. To može uključivati ​​bilo koji međuprodukt koji smo definirali.

Korištenje ovog dodatka znači da nam više nije potrebna glavna funkcija. Možemo koristiti Leiningen za pokretanje u razvojnom načinu, ili možemo izgraditi proizvodni artefakt u svrhe postavljanja. Naš se kod sada svodi točno na našu logiku i ništa više.

5.1. Izgradnja proizvodnog artefakta

Jednom kad se ovo postavi, sada možemo izgraditi WAR datoteku koju možemo rasporediti u bilo koji standardni spremnik servleta:

$ lein ring uberwar 2019-04-12 07: 10: 08.033: INFO :: main: Prijava pokrenuta @ 1054ms na org.eclipse.jetty.util.log.StdErrLog Created ./clojure/ring/target/uberjar/ring-0.1 .0-SNAPSHOT-standalone.war

Također možemo izraditi samostalnu JAR datoteku koja će pokretati naš rukovatelj točno onako kako se očekivalo:

$ lein ring uberjar Kompajliranje ring.core 2019-04-12 07: 11: 27.669: INFO :: main: Prijava inicijalizirana @ 3016ms na org.eclipse.jetty.util.log.StdErrLog Created ./clojure/ring/target/uberjar /ring-0.1.0-SNAPSHOT.jar stvoreno ./clojure/ring/target/uberjar/ring-0.1.0-SNAPSHOT-standalone.jar

Ova JAR datoteka sadržavat će glavnu klasu koja će pokrenuti rukovatelj u ugrađenom spremniku koji smo uključili. Ovo će uvažiti i varijablu okoline od LUKA omogućujući nam lako pokretanje u proizvodnom okruženju:

PORT = 2000 java -jar ./clojure/ring/target/uberjar/ring-0.1.0-SNAPSHOT-standalone.jar 2019-04-12 07: 14: 08.954: INFO :: main: Prijava pokrenuta @ 1009ms u org. eclipse.jetty.util.log.StdErrLog UPOZORENJE: seqable? već se odnosi na: # 'clojure.core / seqable? u prostoru imena: clojure.core.incubator, zamjenjuje se s: # 'clojure.core.incubator / seqable? 2019-04-12 07: 14: 10.795: INFO: oejs.Server: main: jetty-9.4.z-SNAPSHOT; izgrađeno: 2018-08-30T13: 59: 14.071Z; git: 27208684755d94a92186989f695db2d7b21ebc51; jvm 1.8.0_77-b03 2019-04-12 07: 14: 10.863: INFO: oejs.AbstractConnector: main: Started [email protected] {HTTP / 1.1, [http / 1.1]} {0.0.0.0:2000} 2019- 04-12 07: 14: 10.863: INFO: oejs.Server: main: Started @ 2918ms Started server on port 2000

5.2. Trčanje u razvojnom načinu

U svrhu razvoja, obrađivač možemo pokrenuti izravno iz Leiningena bez potrebe za ručnom gradnjom i pokretanjem. To olakšava testiranje naše aplikacije u stvarnom pregledniku:

$ lein ring poslužitelj 2019-04-12 07: 16: 28.908: INFO :: main: Prijava je inicijalizirana @ 1403ms na org.eclipse.jetty.util.log.StdErrLog 2019-04-12 07: 16: 29.026: INFO: oejs .Server: glavni: jetty-9.4.12.v20180830; izgrađeno: 2018-08-30T13: 59: 14.071Z; git: 27208684755d94a92186989f695db2d7b21ebc51; jvm 1.8.0_77-b03 2019-04-12 07: 16: 29.092: INFO: oejs.AbstractConnector: main: Started [email protected] {HTTP / 1.1, [http / 1.1]} {0.0.0.0:3000} 2019- 04-12 07: 16: 29.092: INFO: oejs.Server: main: Započeto @ 1587ms

Ovo također časti LUKA varijabla okoline ako smo to postavili.

Dodatno, postoji biblioteka za razvoj prstena koju možemo dodati našem projektu. Ako je ovo dostupno, onda razvojni poslužitelj pokušat će automatski ponovo učitati sve otkrivene promjene izvora. To nam može pružiti učinkovit tijek rada promjene koda i prikazivanja uživo u našem pregledniku. To zahtijeva prsten-razvoj dodavanje ovisnosti:

[prsten / razvijanje prstena "1.7.1"]

6. Zaključak

U ovom smo članku dali kratki uvod u biblioteku Ring kao sredstvo za pisanje web aplikacija na Clojureu. Zašto ne probati na sljedećem projektu?

Primjeri nekih koncepata koje smo ovdje obradili mogu se vidjeti na GitHubu.