日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Clojure Web 开发 -- Ring 使用指南

發布時間:2024/4/14 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Clojure Web 开发 -- Ring 使用指南 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在 Clojure 眾多的 Web 框架中,Ring 以其簡單統一的 HTTP 抽象模型脫穎而出。Ring 充分體現了函數式編程的思想——通過一系列函數的組合形成了一個易于理解、擴展的 HTTP 處理鏈。

本篇文章首先介紹 Ring 核心概念及其實現原理,然后介紹如何基于 Ring + Compojure 實現一 RESTful 服務。

Ring SPEC

Ring 規范里面有如下5個核心概念:

  • handlers,應用邏輯處理的主要單元,由一個普通的 Clojure 函數實現
  • middleware,為 handler 增加額外功能
  • adapter,將 HTTP 請求轉為 Clojure 里的 map,將 Clojure 里的 map 轉為 HTTP 相應
  • request map,HTTP 請求的 map 表示
  • response map,HTTP 相應的 map 表示
  • 這5個組件的關系可用下圖表示(By Ring 作者):

    +---------------+| Middleware || +---------+ | +---------+ +--------+| | |<-- request ----| | | || | Handler | | | Adapter |<---->| Client || | |--- response -->| | | || +---------+ | +---------+ +--------++---------------+復制代碼

    Hello World

    (ns learn-ring.core(:require [ring.adapter.jetty :refer [run-jetty]]))(defn handler [req]{:headers {}:status 200:body "Hello World"})(defn middleware [handler]"Audit a log per request"(fn [req](println (:uri req))(handler req)))(def app(-> handlermiddleware))(defn -main [& _](run-jetty app {:port 3000}))復制代碼

    運行上面的程序,就可以啟動一 Web 應用,然后在瀏覽器訪問就可以返回Hello World,同時在控制臺里面會打印出請求的 uri。

    run-jetty 是 Ring 提供的基于 jetty 的 adapter,方便開發測試。其主要功能是兩個轉換:

  • HttpServletRequest ---> request map
  • response map ---> HttpServletResponse
  • ;; ring.adapter.jetty (defn- ^AbstractHandler proxy-handler [handler](proxy [AbstractHandler] [](handle [_ ^Request base-request request response](let [request-map (servlet/build-request-map request)response-map (handler request-map)](servlet/update-servlet-response response response-map)(.setHandled base-request true)))));; ring.util.servlet;; HttpServletRequest --> request map(defn build-request-map"Create the request map from the HttpServletRequest object."[^HttpServletRequest request]{:server-port (.getServerPort request):server-name (.getServerName request):remote-addr (.getRemoteAddr request):uri (.getRequestURI request):query-string (.getQueryString request):scheme (keyword (.getScheme request)):request-method (keyword (.toLowerCase (.getMethod request) Locale/ENGLISH)):protocol (.getProtocol request):headers (get-headers request):content-type (.getContentType request):content-length (get-content-length request):character-encoding (.getCharacterEncoding request):ssl-client-cert (get-client-cert request):body (.getInputStream request)});; response map --> HttpServletResponse(defn update-servlet-response"Update the HttpServletResponse using a response map. Takes an optionalAsyncContext."([response response-map](update-servlet-response response nil response-map))([^HttpServletResponse response context response-map](let [{:keys [status headers body]} response-map](when (nil? response)(throw (NullPointerException. "HttpServletResponse is nil")))(when (nil? response-map)(throw (NullPointerException. "Response map is nil")))(when status(.setStatus response status))(set-headers response headers)(let [output-stream (make-output-stream response context)](protocols/write-body-to-stream body response-map output-stream)))))復制代碼

    Middleware

    Ring 里面采用 Middleware 模式去擴展 handler 的功能,這其實是函數式編程中常用的技巧,用高階函數去組合函數,實現更復雜的功能。在 Clojure 里面,函數組合更常見的是用 comp,比如

    ((comp #(* % 2) inc) 1) ;; 4復制代碼

    這對一些簡單的函數非常合適,但是如果邏輯比較復雜,Middleware 模式就比較合適了。例如可以進行一些邏輯判斷決定是否需要調用某函數:

    (defn middleware-comp [handler](fn [x](if (zero? 0)(handler (inc x))(handler x))))((-> #(* 2 %)middleware-comp) 1) ;; 4 ((-> #(* 2 %)middleware-comp) 0) ;; 2復制代碼

    雖然 Middleware 使用非常方便,但是有一點需要注意:多個 middleware 組合的順序。后面在講解 RESTful 示例時會演示不同順序的 middleware 對請求的影響。

    Middleware 這一模式在函數式編程中非常常見,Clojure 生態里面新的構建工具 boot-clj 里面的 task 也是通過這種模式組合的。

    $ cat build.boot (deftask inc-if-zero-else-dec[n number NUM int "number to test"](fn [handler](fn [fileset](if (zero? number)(handler (merge fileset {:number (inc number)}))(handler (merge fileset {:number (dec number)}))))))(deftask printer[](fn [handler](fn [fileset](println (str "number is " (:number fileset)))fileset)))$ boot inc-if-zero-else-dec -n 0 printer number is 1 $ boot inc-if-zero-else-dec -n 1 printer number is 0復制代碼

    RESTful 實戰

    由于 Ring 只是提供了一個 Web 服務最基本的抽象功能,很多其他功能,像 url 路由規則,參數解析等均需通過其他模塊實現。Compojure 是 Ring 生態里面默認的路由器,同樣短小精悍,功能強大。基本用法如下:

    (def handlers(routes(GET "/" [] "Hello World")(GET "/about" [] "about page")(route/not-found "Page not found!")))復制代碼

    使用這里的 handlers 代替上面 Hello World 的示例中的 handler 即可得到一個具有2條路由規則的 Web 應用,同時針對其他路由返回 Page not found!。

    Compojure 里面使用了大量宏來簡化路由的定義,像上面例子中的GET、not-found等。Compojure 底層使用 clout 這個庫實現,而 clout 本身是基于一個 parser generator(instaparse) 定義的“路由”領域特定語言。核心規則如下:

    (def ^:private route-parser(insta/parser"route = (scheme / part) part*scheme = #'(https?:)?//'<part> = literal | escaped | wildcard | paramliteral = #'(:[^\\p{L}_*{}\\\\]|[^:*{}\\\\])+'escaped = #'\\\\.'wildcard = '*'param = key pattern?key = <':'> #'([\\p{L}_][\\p{L}_0-9-]*)'pattern = '{' (#'(?:[^{}\\\\]|\\\\.)+' | pattern)* '}'":no-slurp true))復制代碼

    Compojure 中路由匹配的方式也非常巧妙,這里詳細介紹一下。

    Compojure 路由分發

    Compojure 通過 routes 把一系列 handler 封裝起來,其內部調用 routing 方法找到正確的 handler。這兩個方法代碼非常簡潔:

    (defn routing"Apply a list of routes to a Ring request map."[request & handlers](some #(% request) handlers))(defn routes"Create a Ring handler by combining several handlers into one."[& handlers]#(apply routing % handlers))復制代碼

    routing 里面通過調用 some 函數返回第一個非 nil 調用,這樣就解決了路由匹配的問題。由這個例子可以看出 Clojure 語言的表達力。

    在使用 GET 等這類宏定義 handler 時,會調用wrap-route-matches 來包裝真正的處理邏輯,邏輯如下:

    (defn- wrap-route-matches [handler method path](fn [request](if (method-matches? request method)(if-let [request (route-request request path)](-> (handler request)(head-response request method))))))復制代碼

    這里看到只有在 url 與 http method 均匹配時,才會去調用 handler 處理 http 請求,其他情況直接返回 nil,這與前面講的 some 聯合起來就形成了完整的路由功能。

    由于 routes 的返回值與 handler 一樣,是一個接受 request map 返回 response map 的函數,所以可以像堆積木一樣進行任意組合,實現類似于 Flask 中 blueprints 的模塊化功能。例如:

    ;; cat student.clj (ns demo.student(:require [compojure.core :refer [GET POST defroutes context]])(defroutes handlers(context "/student" [](GET "/" [] "student index")));;cat demo.teacher (ns demo.teacher(:require [compojure.core :refer [GET POST defroutes context]])(defroutes handlers(context "/teacher" [](GET "/" [] "teacher index")));; cat demo.core.clj (ns demo.core(:require [demo.student :as stu][demo.teacher :as tea]);; core 里面進行 handler 的組合 (defroutes handlers(GET "/" [] "index")(stu/handlers)(tea/handlers))復制代碼

    Middleware 功能擴展

    參數解析

    Compojure 解決了路由問題,參數獲取是通過定制不能的 middleware 實現的,compojure.handler 命名空間提供了常用的 middleware 的組合,針對 RESTful 可以使用 api 這個組合函數,它會把 QueryString 中的參數解析到 request map 中的:query-params key 中,表單中的參數解析到 request map 中的 :form-params。

    (def app(-> handlershandler/api))復制代碼

    JSON 序列化

    由于 RESTful 服務中,請求的數據與返回的數據通常都是 JSON 格式,所以需要增加兩個額外的功能來實現 JSON 的序列化。

    ;; 首先引用 ring.middleware.json(def app(-> handlerswrap-json-responsewrap-json-bodyhandler/api))復制代碼

    紀錄請求時間

    通常,我們需要紀錄每個請求的處理時間,這很簡單,實現個 record-response-time 即可:

    (defn record-response-time [handler](fn [req](let [start-date (System/currentTimeMillis)](handler req)(let [res-time (- (System/currentTimeMillis) start-date)](println (format "%s took %d ms" (:uri req) res-time))))))(def app(-> handlerswrap-json-responsewrap-json-bodyhandler/apirecord-response-time))復制代碼

    需要注意的是 record-response-time 需要放在 middleware 最外層,這樣它才能紀錄一個請求經過所有 middleware + handler 處理的時間。

    封裝異常

    其次,另一個很常見的需求就是封裝異常,當服務端出現錯誤時返回給客戶端友好的錯誤信息,而不是服務端的錯誤堆棧。

    (defn wrap-exception[handler](fn [request](try(handler request)(catch Throwable e(response {:code 20001:msg "inner error})))))(def app(-> handlerswrap-json-responsewrap-json-bodyhandler/apiwrap-exceptionrecord-response-time))復制代碼

    順序!順序!順序!

    一個 App 中的 middleware 調用順序非常重要,因為不同的 middleware 之間 request map 與 response map 是相互依賴的,所以在定義 middleware 時一定要注意順序。一圖勝千言:

    middleware 應用順序圖

    總結

    在 Java EE 中,編寫 Web 項目通常是配置各種 XML 文件,代碼還沒開始寫就配置了一大堆jar包依賴,這些 jar 包很有可能會沖突,然后需要花大量時間處理這些依賴沖突,真心麻煩。

    Ring 與其說是一個框架,不如說是由各個短小精悍的函數組成的 lib,充分展示了 Clojure 語言的威力,通過函數的組合定義出一套完整的 HTTP 抽象機制,通過宏來實現“路由”特定領域語言,極大簡化了路由的定義,方便了模塊的分解。

    除了上面的介紹,Ring 生態里面還有 lein-ring ,它可以在不重啟服務的情況下重新加載有修改的命名空間(以及其影響的),開發從未如何順暢。

    Ring + Compojure + lein-ring 你值得擁有。

    擴展閱讀

    • Boot, the Fancy Clojure Build Framework
    • www.lispcast.com/what-web-fr…
    • github.com/luminus-fra…

    總結

    以上是生活随笔為你收集整理的Clojure Web 开发 -- Ring 使用指南的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。