Compojure açıkladı (bir dereceye kadar)
NB. Compojure 0.4.1 ile çalışıyorum ( GitHub'daki 0.4.1 sürüm kaydı burada ).
Neden?
En tepesinde compojure/core.clj
, Compojure'un amacının şu yararlı özeti var:
Halka işleyicileri oluşturmak için kısa bir sözdizimi.
Yüzeysel bir düzeyde, "neden" sorusunun hepsi bu kadar. Biraz daha derine inmek için, Ring tarzı bir uygulamanın nasıl çalıştığına bir göz atalım:
Ring spesifikasyonuna göre bir istek gelir ve Clojure haritasına dönüştürülür.
Bu harita, bir yanıt üretmesi beklenen (aynı zamanda bir Clojure haritasıdır) "işleyici işlevi" olarak adlandırılan bir işleve dönüştürülür.
Yanıt haritası, gerçek bir HTTP yanıtına dönüştürülür ve istemciye geri gönderilir.
Yukarıdaki 2. Adım en ilginç olanıdır, çünkü istekte kullanılan URI'yi incelemek, herhangi bir çerezi incelemek vb. Ve nihayetinde uygun bir yanıta ulaşmak işleyicinin sorumluluğundadır. Açıkça görülüyor ki, tüm bu çalışmaların iyi tanımlanmış parçalardan oluşan bir koleksiyona dahil edilmesi gerekiyor; bunlar normalde bir "temel" işleyici işlevi ve onu saran bir ara yazılım işlevleri koleksiyonudur. Compojure'un amacı, temel işleyici işlevinin oluşturulmasını basitleştirmektir.
Nasıl?
Compojure, "rotalar" kavramı etrafında inşa edilmiştir. Bunlar aslında Clout kitaplığı (Compojure projesinin bir yan ürünü - 0.3.x -> 0.4.x geçişinde birçok şey ayrı kitaplıklara taşındı) Clout kitaplığı tarafından daha derin bir düzeyde uygulanır . Bir rota, (1) bir HTTP yöntemi (GET, PUT, HEAD ...), (2) bir URI modeli (Webby Rubyistlerine aşina olacak olan sözdizimi ile belirtilir), (3) kullanılan bir yıkıcı form ile tanımlanır. istek eşlemesinin kısımlarını gövdede bulunan isimlere bağlamak, (4) geçerli bir Halka yanıtı üretmesi gereken bir ifade gövdesi (önemsiz olmayan durumlarda bu genellikle sadece ayrı bir işleve çağrıdır).
Bu, basit bir örneğe bakmak için iyi bir nokta olabilir:
(def example-route (GET "/" [] "<html>...</html>"))
Bunu REPL'de test edelim (aşağıdaki istek haritası, minimum geçerli Halka istek haritasıdır):
user> (example-route {:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "<html>...</html>"}
Eğer :request-method
vardı :head
bunun yerine, tepki olacaktır nil
. nil
Bir dakika içinde burada ne anlama geldiği sorusuna döneceğiz (ancak bunun geçerli bir Yüzük tepkisi olmadığına dikkat edin!).
Bu örnekten de anlaşılacağı gibi, example-route
sadece bir fonksiyondur ve bunda çok basittir; o, istek bakar o (inceleyerek kullanırken bazı ilgilendiği belirler :request-method
ve :uri
böylece, temel bir tepki harita dönerse,) ve.
Aynı zamanda aşikar olan şey, yolun gövdesinin gerçekten uygun bir yanıt haritasına değerlendirmeye ihtiyaç duymamasıdır; Compojure, dizeler (yukarıda görüldüğü gibi) ve bir dizi başka nesne türü için mantıklı varsayılan işlem sağlar; ayrıntılar için compojure.response/render
çoklu yönteme bakın (kod burada tamamen kendi kendini belgelemektedir).
Şimdi kullanmayı deneyelim defroutes
:
(defroutes example-routes
(GET "/" [] "get")
(HEAD "/" [] "head"))
Yukarıda görüntülenen örnek talebe ve varyantına verilen yanıtlar :request-method :head
beklendiği gibidir.
İç işleyişi example-routes
öyle ki, her bir yol sırayla denenecek; bunlardan biri nil
yanıt vermeyen döndürür döndürmez , bu yanıt tüm example-routes
işleyicinin dönüş değeri olur . Ek bir kolaylık olarak, defroutes
-defined işleyicileri sarılır wrap-params
ve wrap-cookies
dolaylı.
İşte daha karmaşık bir rota örneği:
(def echo-typed-url-route
(GET "*" {:keys [scheme server-name server-port uri]}
(str (name scheme) "://" server-name ":" server-port uri)))
Önceden kullanılan boş vektörün yerine yıkıcı forma dikkat edin. Buradaki temel fikir, rotanın gövdesinin taleple ilgili bazı bilgilerle ilgilenebileceğidir; bu her zaman bir harita biçiminde geldiği için, talepten bilgi çıkarmak ve onu yolun gövdesinde kapsam dahilinde olacak yerel değişkenlere bağlamak için ilişkisel bir yok etme formu sağlanabilir.
Yukarıdakilerin bir testi:
user> (echo-typed-url-route {:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/foo/bar"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "http://127.0.0.1:80/foo/bar"}
Yukarıdakilere yönelik parlak takip fikri, daha karmaşık rotaların assoc
, eşleştirme aşamasında isteğe ek bilgi verebilmesidir :
(def echo-first-path-component-route
(GET "/:fst/*" [fst] fst))
Bu , önceki örnekteki isteğe bir :body
/ ile yanıt verir "foo"
.
Bu son örnekte iki şey yenidir: "/:fst/*"
ve boş olmayan bağlanma vektörü [fst]
. Birincisi, URI modelleri için yukarıda bahsedilen Rails ve Sinatra benzeri sözdizimidir. Yukarıdaki örnekten anlaşılandan biraz daha karmaşıktır, çünkü URI segmentlerindeki düzenli ifade kısıtlamaları desteklenir (örneğin ["/:fst/*" :fst #"[0-9]+"]
, rotanın sadece :fst
yukarıdakinin tüm rakamlı değerlerini kabul etmesini sağlamak için sağlanabilir ). İkincisi, :params
kendisi bir harita olan istek eşlemesindeki girdiyi eşleştirmenin basitleştirilmiş bir yoludur ; istek, sorgu dizesi parametreleri ve form parametrelerinden URI segmentlerini çıkarmak için kullanışlıdır. İkinci noktayı açıklamak için bir örnek:
(defroutes echo-params
(GET "/" [& more]
(str more)))
user> (echo-params
{:server-port 80
:server-name "127.0.0.1"
:remote-addr "127.0.0.1"
:uri "/"
:query-string "foo=1"
:scheme :http
:headers {}
:request-method :get})
{:status 200,
:headers {"Content-Type" "text/html"},
:body "{\"foo\" \"1\"}"}
Soru metnindeki örneğe bir göz atmak için iyi bir zaman olabilir:
(defroutes main-routes
(GET "/" [] (workbench))
(POST "/save" {form-params :form-params} (str form-params))
(GET "/test" [& more] (str "<pre>" more "</pre>"))
(GET ["/:filename" :filename #".*"] [filename]
(response/file-response filename {:root "./static"}))
(ANY "*" [] "<h1>Page not found.</h1>"))
Her rotayı sırayla inceleyelim:
(GET "/" [] (workbench))
- ile bir GET
istekle uğraşırken :uri "/"
, işlevi çağırın workbench
ve döndürdüğü şeyi bir yanıt haritasına işleyin. (Dönüş değerinin bir harita, aynı zamanda bir dize vb. Olabileceğini hatırlayın.)
(POST "/save" {form-params :form-params} (str form-params))
- ara yazılım :form-params
tarafından sağlanan istek eşlemesindeki bir giriştir wrap-params
(örtük olarak tarafından dahil edildiğini hatırlayın defroutes
). Yanıtı standart olacak {:status 200 :headers {"Content-Type" "text/html"} :body ...}
ile (str form-params)
ikame ...
. (Biraz alışılmadık bir POST
işleyici, bu ...)
(GET "/test" [& more] (str "<pre> more "</pre>"))
- bu, örneğin {"foo" "1"}
kullanıcı aracısı isterse haritanın dizgi temsilini geri yansıtır "/test?foo=1"
.
(GET ["/:filename" :filename #".*"] [filename] ...)
- :filename #".*"
parça hiçbir şey yapmaz (çünkü #".*"
her zaman eşleşir). ring.util.response/file-response
Yanıtını üretmek için Ring yardımcı işlevi işlevini çağırır ; {:root "./static"}
bölüm nerede dosyaya bakmak için söyler.
(ANY "*" [] ...)
- tümünü kapsayan bir rota. Compojure uygulaması defroutes
, tanımlanmakta olan işleyicinin her zaman geçerli bir Halka yanıt haritası döndürmesini sağlamak için bir formun sonuna her zaman böyle bir yol eklemek iyi bir yöntemdir (bir yol eşleştirme başarısızlığının neden olduğunu hatırlayın nil
).
Neden bu şekilde?
Ring ara yazılımının bir amacı, istek haritasına bilgi eklemektir; böylece tanımlama bilgisi işleme ara yazılımı :cookies
isteğe bir anahtar wrap-params
ekler , ekler :query-params
ve / veya:form-params
bir sorgu dizesi / form verisi varsa vb. (Daha doğrusu, ara yazılım işlevlerinin eklediği tüm bilgiler istek haritasında zaten mevcut olmalıdır, çünkü geçtikleri şey budur; görevleri, onu, sardıkları işleyicilerle çalışmak için daha uygun hale getirmektir.) Nihayetinde, "zenginleştirilmiş" istek, ara yazılım tarafından eklenen güzel bir şekilde önceden işlenmiş tüm bilgilerle istek haritasını inceleyen ve bir yanıt üreten temel işleyiciye iletilir. (Ara yazılım, bundan daha karmaşık şeyler yapabilir - birkaç "iç" işleyiciyi sarmak ve aralarında seçim yapmak, sarmalanmış işleyicileri çağırıp çağırmamaya karar vermek vb. Ancak, bu yanıtın kapsamı dışındadır.)
Temel işleyici, genellikle (önemsiz olmayan durumlarda) istek hakkında yalnızca bir avuç bilgi öğesine ihtiyaç duyan bir işlevdir. (Örneğin ring.util.response/file-response
, talebin çoğu umurunda değildir; yalnızca bir dosya adına ihtiyaç duyar.) Bu nedenle, bir Ring isteğinin yalnızca ilgili kısımlarını çıkarmanın basit bir yoluna ihtiyaç vardır. Compojure, tam da bunu yapan özel amaçlı bir kalıp eşleştirme motoru sağlamayı amaçlamaktadır.