Ring 提供了 web 開發所需的基礎構件,好比處理請求參數,cookie, session 等等。經過向 http-kit 或 jetty 註冊 handler 的方式來提供服務。handler 函數接收一個 request 參數,此參數由調用者傳遞(此處爲 http-kit 或 jetty)。然而 http-kit 或 jetty 傳遞過來的 request 參數只包含了基本標準鍵,Ring 將經過中間件的方式提供更高級的功能。web
如下爲 request 包含的基本標準鍵:服務器
:server-port ---------- 用於處理該請求的服務端口 :server-name ---------- 服務器的 IP 地址或是主機名 :remote-addr ---------- 客戶端的 IP 地址 :query-string --------- 請求的查詢字符串 :scheme --------------- 協議的類型,能夠是 HTTP 或者 HTTPS :request-method ------- 請求的方法,好比::get、:head、:options、:put、:post 或 :delete :request-string ------- 請求的查詢字符串 :content-type --------- 請求消息體的 MIME 類型 :content-length ------- 請求消息體的字節數 :character-encoding --- 請求採用的字符編碼名稱 :headers -------------- 包含了請求頭部的map :body ----------------- 可用於讀取請求消息體的輸入流 :context -------------- 當應用沒有做爲根來部署時,其所處的上下文 :uri ------------------ 服務端的 URI 全路徑,包含了 :context(若是存在)的部分 :ssl-client-cert ------ 客戶端的 SSL 證書 * 注意:此處列出的鍵,並不必定會出如今全部的請求中,好比 :ssl-client-cert
Ring 中間件的原理其實很簡單。鏈式執行,前一個的輸出做爲下一個的輸入,有點相似於管道。在深刻講解 Ring 的中間件前,咱們先來熟悉 Clojure 提供的一個方法:->。cookie
方法 -> 就支持鏈式執行,它接受任意多個參數。第一個參數能夠爲任意形式,剩下的參數必須爲函數,而且至少接受一個參數。這是由於方法 -> 實現的功能是:將前一個表達式的結果做爲下一個函數的第一個參數來調用。執行順序是先上後下,舉例以下:session
(defn test-a [msg] (let [_msg (str msg " -- a")] (println _msg) _msg)) (defn test-b [msg] (let [_msg (str msg " -- b")] (println _msg) _msg)) (defn test-c [msg] (let [_msg (str msg " -- c")] (println _msg) _msg)) (defn -main [& args] (-> (test-a "<testing>") test-b test-c))
在命令行執行:閉包
$ lein run <testing> -- a <testing> -- a -- b <testing> -- a -- b -- c
能夠看到,前一個表達式的值做爲下一個函數的第一個參數。這種內置的語法令鏈式執行看起來一目瞭然,不需像其它語言那樣層層閉包。固然若是 Clojure 沒有內置這樣的函數,也能夠經過定義宏的方式來實現。app
Ring 中間件其實就是經過 -> 來進行鏈式執行。還記得須要向 http-kit 或 jetty 註冊一個 handler 嗎?Ring 中間件的原理說白了就是對 handler 進行層層包裝,返回一個新的 handler。可是由於返回了一個新的 handler,也是就是閉包,對於真正的執行時機可能會有點不太同樣。接下來舉例說明:函數
(defn my-handler [req] (println "my-handler") "my-handler response") (defn test-a [handler] (fn [req] (println "test-a") (handler req))) (defn test-b [handler] (fn [req] (println "test-b") (handler req))) (defn test-c [handler] (fn [req] (println "test-c") (handler req))) (def app (-> my-handler test-a test-b test-c)) (defn -main [& args] (app nil))
在命令行執行:post
$ lein run test-c test-b test-a my-handler
結果看起來跟以前說的不太同樣。以前說的執行順序是先上後下,此次的結果明顯是先下後上,到底是哪裏出現問題了呢?其實兩次的執行結果都是正確的。前一次是簡單的鏈式執行,而這一次鏈式執行發生後,每一步都返回了一個閉包,致使了延遲執行。相似如下代碼:編碼
(-> my-handler test-a test-b test-c)
其實能夠當作:spa
(test-c (test-b (test-a my-handler)))
展開後:
(println "test-c") ; test-b 返回的閉包 (println "test-b") ; test-a 返回的閉包 (println "test-a") ; my-handler (println "my-handler")
至此想必你已經對 Ring 的中間件有所瞭解了。
Ring 提供了一些基礎的中間件,它們或多或少的對 request 和 response 進行修改,以達到特定目的。ring-devel 對開發環境提供了支持,好比 wrap-reload 容許你沒必要重啓便可在修改源碼後自動從新載入等等。ring-core 提供了更多標準中間件,有wrap-params、wrap-not-modified、wrap-content-type等等。
Ring 中間件的執行流程相當重要,將來不少時候都要跟中間件打交道,好比自定義攔截,實現公共處理(其它語言中多是經過繼承父控制器來實現)等等。
Ring 是一個很優秀的針對 web 開發的基礎構件。研究它的原理和熟讀它的源碼不論是在 web 開發上仍是對於 Clojure 的理解上都有莫大的好處。