कंपोजर समझाया (कुछ हद तक)
एनबी। मैं कॉम्पोज्योर 0.4.1 के साथ काम कर रहा हूं ( यहां GitHub पर 0.4.1 रिलीज कमिट है)।
क्यों?
सबसे ऊपर compojure/core.clj
, कॉम्पोज्योर के उद्देश्य का यह सहायक सारांश है:
रिंग संचालकों के निर्माण के लिए एक संक्षिप्त वाक्य रचना।
सतही स्तर पर, यह "सवाल" क्यों है। थोड़ा और गहराई में जाने के लिए, आइए एक नजर डालते हैं कि रिंग-स्टाइल ऐप कैसे कार्य करता है:
एक अनुरोध आता है और रिंग स्पेस के अनुसार क्लोजर मैप में बदल जाता है।
यह नक्शा एक तथाकथित "हैंडलर फ़ंक्शन" में फ़नल है, जिसे एक प्रतिक्रिया (जो कि क्लीजुर मैप भी है) का उत्पादन करने की उम्मीद है।
प्रतिक्रिया मानचित्र वास्तविक HTTP प्रतिक्रिया में बदल जाता है और क्लाइंट को वापस भेज दिया जाता है।
चरण 2. उपरोक्त में सबसे दिलचस्प है, क्योंकि यह अनुरोध में उपयोग किए गए यूआरआई की जांच करने, किसी भी कुकीज़ आदि की जांच करने और अंततः एक उचित प्रतिक्रिया पर पहुंचने की जिम्मेदारी है। स्पष्ट रूप से यह आवश्यक है कि यह सब काम अच्छी तरह से परिभाषित टुकड़ों के संग्रह में निहित हो; ये आम तौर पर एक "बेस" हैंडलर फ़ंक्शन और इसे लपेटने वाले मिडलवेयर फ़ंक्शंस का एक संग्रह है। कॉम्पोजर का उद्देश्य बेस हैंडलर फ़ंक्शन की पीढ़ी को सरल बनाना है।
कैसे?
कॉम्पोज्योर "मार्गों" की धारणा के आसपास बनाया गया है। ये वास्तव में से एक गहरे स्तर पर लागू किया जाता है प्रभाव पुस्तकालय (Compojure परियोजना के एक spinoff - बहुत सी बातें 0.3.x पर अलग पुस्तकालयों में ले जाया गया -> 0.4.x संक्रमण)। एक मार्ग को (1) एक HTTP विधि (GET, PUT, HEAD ...), (2) एक URI पैटर्न (वाक्यविन्यास के साथ निर्दिष्ट जो स्पष्ट रूप से Webby Rubyists से परिचित होगा), (3) एक विनाशकारी रूप में उपयोग किया जाता है। शरीर में उपलब्ध नामों के लिए अनुरोध मानचित्र के कुछ हिस्सों को बाँधना, (4) अभिव्यक्तियों का एक निकाय जो एक वैध रिंग प्रतिक्रिया का उत्पादन करने की आवश्यकता है (गैर-तुच्छ मामलों में यह आमतौर पर एक अलग फ़ंक्शन के लिए केवल एक कॉल है)।
यह एक साधारण उदाहरण पर एक नज़र डालने के लिए एक अच्छा बिंदु हो सकता है:
(def example-route (GET "/" [] "<html>...</html>"))
आइए, REPL में इसका परीक्षण करें (नीचे दिया गया अनुरोध नक्शा न्यूनतम मान्य रिंग अनुरोध मानचित्र है):
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>"}
तो :request-method
थे :head
बजाय, प्रतिक्रिया होगी nil
। हम इस सवाल पर लौटेंगे nil
कि यहां एक मिनट में क्या मतलब है (लेकिन ध्यान दें कि यह एक मान्य रिंग प्रतिक्रिया नहीं है!)।
जैसा कि इस उदाहरण से स्पष्ट है, example-route
केवल एक कार्य है, और उस पर एक बहुत ही सरल; यह, अनुरोध पर लग रहा है निर्धारित करता है कि यह इसके रख-रखाव (परीक्षण करके में रुचि है :request-method
और :uri
) और, यदि हां, एक बुनियादी प्रतिक्रिया नक्शा प्राप्त होगा।
यह भी स्पष्ट है कि मार्ग के शरीर को वास्तव में उचित प्रतिक्रिया मानचित्र के मूल्यांकन की आवश्यकता नहीं है; कंपोजर स्ट्रिंग्स (जैसा कि ऊपर देखा गया है) और कई अन्य ऑब्जेक्ट प्रकारों के लिए सेंस डिफॉल्ट हैंडलिंग प्रदान करता है; देखने के compojure.response/render
विवरण के लिए multimethod (कोड पूरी तरह से स्वयं का दस्तावेजीकरण यहाँ है)।
आइए defroutes
अब प्रयोग करके देखें :
(defroutes example-routes
(GET "/" [] "get")
(HEAD "/" [] "head"))
ऊपर दिखाए गए उदाहरण अनुरोध के जवाब और इसके वैरिएंट के साथ :request-method :head
उम्मीद की तरह हैं।
आंतरिक कामकाज example-routes
ऐसे हैं कि प्रत्येक मार्ग को बारी-बारी से करने की कोशिश की जाती है; जैसे ही उनमें से कोई एक गैर- nil
प्रतिक्रिया देता है , वह प्रतिक्रिया पूरे example-routes
हैंडलर का रिटर्न मान बन जाती है । एक अतिरिक्त सुविधा के रूप में defroutes
-Defined हैंडलर अंदर wrap-params
और wrap-cookies
स्पष्ट रूप से लिपटे हुए हैं ।
यहाँ एक और अधिक जटिल मार्ग का उदाहरण दिया गया है:
(def echo-typed-url-route
(GET "*" {:keys [scheme server-name server-port uri]}
(str (name scheme) "://" server-name ":" server-port uri)))
पहले इस्तेमाल किए गए खाली वेक्टर के स्थान पर विनाशकारी रूप पर ध्यान दें। यहां मूल विचार यह है कि मार्ग के निकाय को अनुरोध के बारे में कुछ जानकारी में रुचि हो सकती है; चूंकि यह हमेशा एक नक्शे के रूप में आता है, एक साहचर्य विनाशकारी रूप को अनुरोध से जानकारी निकालने और स्थानीय चर में बांधने के लिए आपूर्ति की जा सकती है जो मार्ग के शरीर में गुंजाइश होगी।
उपरोक्त का एक परीक्षण:
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"}
ऊपर दिए गए शानदार अनुवर्ती विचार यह है कि अधिक जटिल मार्ग assoc
मिलान चरण में अनुरोध पर अतिरिक्त जानकारी दे सकते हैं :
(def echo-first-path-component-route
(GET "/:fst/*" [fst] fst))
यह एक साथ प्रतिक्रिया :body
के "foo"
पिछले उदाहरण से अनुरोध करने के लिए।
इस नवीनतम उदाहरण के बारे में दो बातें नई हैं: "/:fst/*"
गैर-खाली बाध्यकारी वेक्टर [fst]
। पहला URI पैटर्न के लिए पूर्वोक्त रेल और सिनात्रा जैसा सिंटैक्स है। यूआरआई सेगमेंट पर रेगेक्स बाधाओं का समर्थन किया गया है (उदाहरण के लिए उपरोक्त में ["/:fst/*" :fst #"[0-9]+"]
केवल सभी अंकीय मूल्यों को स्वीकार करने के लिए आपूर्ति की जा सकती है) से ऊपर के उदाहरण से यह स्पष्ट है कि यह थोड़ा अधिक परिष्कृत है :fst
। दूसरा :params
अनुरोध मानचित्र में प्रविष्टि पर मेल खाने का एक सरल तरीका है , जो स्वयं एक मानचित्र है; यह अनुरोध, क्वेरी स्ट्रिंग पैरामीटर और प्रपत्र पैरामीटर से URI सेगमेंट निकालने के लिए उपयोगी है। बाद के बिंदु को स्पष्ट करने के लिए एक उदाहरण:
(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\"}"}
यह प्रश्न पाठ से उदाहरण पर एक अच्छा समय होगा:
(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>"))
चलो बदले में प्रत्येक मार्ग का विश्लेषण करते हैं:
(GET "/" [] (workbench))
- GET
अनुरोध के साथ काम करते समय :uri "/"
, फ़ंक्शन को कॉल करें workbench
और जो कुछ भी प्रतिक्रिया मानचित्र में देता है उसे प्रस्तुत करें। (याद रखें कि वापसी मूल्य एक नक्शा हो सकता है, लेकिन यह भी एक स्ट्रिंग आदि)
(POST "/save" {form-params :form-params} (str form-params))
- मिडलवेयर :form-params
द्वारा प्रदान किए गए अनुरोध मानचित्र में एक प्रविष्टि है wrap-params
(याद रखें कि यह अंतर्निहित रूप से शामिल है defroutes
)। प्रतिक्रिया मानक {:status 200 :headers {"Content-Type" "text/html"} :body ...}
के लिए (str form-params)
प्रतिस्थापित किया जाएगा ...
। (थोड़ा असामान्य POST
हैंडलर, यह ...)
(GET "/test" [& more] (str "<pre> more "</pre>"))
- {"foo" "1"}
अगर उपयोगकर्ता एजेंट के लिए कहा जाता है, तो यह मानचित्र के स्ट्रिंग प्रतिनिधित्व को प्रतिध्वनित करेगा "/test?foo=1"
।
(GET ["/:filename" :filename #".*"] [filename] ...)
- :filename #".*"
भाग कुछ भी नहीं करता है (चूंकि #".*"
हमेशा मेल खाता है)। यह ring.util.response/file-response
अपनी प्रतिक्रिया उत्पन्न करने के लिए रिंग यूटिलिटी फ़ंक्शन को कॉल करता है ; {:root "./static"}
भाग यह बताता है जहां फ़ाइल देखने के लिए।
(ANY "*" [] ...)
- एक कैच-ऑल रूट। यह अच्छा कॉम्पोज्योर अभ्यास है कि इस तरह के मार्ग को defroutes
फॉर्म के अंत में हमेशा शामिल करें ताकि यह सुनिश्चित हो सके कि जिस हैंडलर को परिभाषित किया जा रहा है वह हमेशा एक वैध रिंग रिस्पॉन्स मैप (यह याद रखता है कि रूट मिलान विफलता परिणाम है nil
)।
इस तरह क्यों?
रिंग मिडलवेयर का एक उद्देश्य अनुरोध मानचित्र में जानकारी जोड़ना है; इस प्रकार कुकी-हैंडलिंग मिडलवेयर :cookies
अनुरोध की एक कुंजी wrap-params
जोड़ता है , जोड़ता है :query-params
और / या:form-params
यदि कोई क्वेरी स्ट्रिंग / प्रपत्र डेटा मौजूद है और इसी तरह। (सख्ती से बोलते हुए, सभी सूचनाएँ जो मिडलवेयर फ़ंक्शंस जोड़ रही हैं, उन्हें पहले से ही अनुरोध मानचित्र में मौजूद होना चाहिए, क्योंकि वे जो वे पास कर रही हैं, उनका काम यह है कि वे इसे लपेटने वाले हैंडलर में काम करने के लिए इसे और अधिक सुविधाजनक बनाने के लिए बदल दें।) अंततः "समृद्ध" अनुरोध बेस हैंडलर को दिया जाता है, जो मिडलवेयर द्वारा जोड़े गए सभी अच्छी तरह से प्रीप्रोस्ड जानकारी के साथ अनुरोध मानचित्र की जांच करता है और प्रतिक्रिया उत्पन्न करता है। (मिडलवेयर इससे अधिक जटिल चीजें कर सकता है - जैसे कई "आंतरिक" हैंडलर को लपेटना और उनके बीच चयन करना, यह तय करना कि क्या लपेटे गए हैंडलर को सभी को कॉल करना है आदि, हालांकि, इस उत्तर के दायरे से बाहर है।)
बेस हैंडलर, बदले में, आम तौर पर (गैर-तुच्छ मामलों में) एक फ़ंक्शन है जो अनुरोध के बारे में जानकारी के केवल एक मुट्ठी भर वस्तुओं की आवश्यकता होती है। (जैसे ring.util.response/file-response
अनुरोध के अधिकांश के बारे में परवाह नहीं है; यह केवल एक फ़ाइल नाम की जरूरत है।) इसलिए एक रिंग अनुरोध के प्रासंगिक भागों को निकालने के एक सरल तरीके की आवश्यकता है। कॉम्पोज्योर का उद्देश्य एक विशेष-उद्देश्य पैटर्न मिलान इंजन प्रदान करना है, जैसा कि यह था, जो कि बस यही करता है।