当前位置:网站首页>Clojure Web Development -- ring user guide
Clojure Web Development -- ring user guide
2022-07-26 16:13:00 【Flying watermelon】
stay Clojure Many Web In the frame ,Ring With its simple and unified HTTP Abstract models stand out .Ring It fully embodies the idea of functional programming —— Through the combination of a series of functions, an easy to understand 、 Extended HTTP Processing chain .
This article first introduces Ring Core concepts and their implementation principles , Then it introduces how to base on Ring + Compojure Achieve one RESTful service .
Ring SPEC
Ring The specification includes the following 5 A core concept :
- handlers, The main unit of application logic processing , By an ordinary Clojure Function implementation
- middleware, by handler Add extra features
- adapter, take HTTP Request to Clojure Inside map, take Clojure Inside map To HTTP The corresponding
- request map,HTTP Requested map Express
- response map,HTTP Corresponding map Express
this 5 The relationship between components can be represented by the following figure (By Ring author ):
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
(-> handler
middleware))
(defn -main [& _]
(run-jetty app {:port 3000})) Run the above program , You can start a Web application , Then you can return to Hello World, At the same time, the request will be printed in the console uri.
run-jetty yes Ring Based on jetty Of adapter, Easy to develop and test . Its main function is two transformations :
HttpServletRequest-→request mapresponse 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 optional
AsyncContext."
([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 It uses Middleware Mode to extend handler The function of , This is actually a common skill in functional programming , Use higher-order functions to combine functions , Implement more complex functions . stay Clojure Inside , Function combinations are more common with comp, such as
((comp #(* % 2) inc) 1)
;; 4This is very suitable for some simple functions , But if the logic is complex ,Middleware The mode is more appropriate . For example, you can make some logical judgments to decide whether you need to call a function :
(defn middleware-comp [handler]
(fn [x]
(if (zero? 0)
(handler (inc x))
(handler x))))
((-> #(* 2 %)
middleware-comp) 1)
;; 4
((-> #(* 2 %)
middleware-comp) 0)
;; 2although Middleware Easy to use , But there is one caveat : Multiple middleware Order of combination . I'll explain later RESTful Examples will demonstrate different sequences middleware Impact on request .
Middleware This pattern is very common in functional programming ,Clojure New construction tools in the ecosystem boot-clj Inside task It is also combined through this mode .
$ 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 0RESTful actual combat
because Ring It just provides a Web The most basic abstract function of service , Many other functions , image url Routing rules , Parameter analysis, etc. need to be realized through other modules .Compojure yes Ring The default router in the ecosystem , Equally short and vigorous , Powerful . The basic usage is as follows :
(def handlers
(routes
(GET "/" [] "Hello World")
(GET "/about" [] "about page")
(route/not-found "Page not found!"))) Use the... Here handlers Instead of the above Hello World In the example of handler You can get one with 2 Of routing rules Web application , At the same time, return for other routes Page not found!.
Compojure A lot of macros are used to simplify the definition of routing , Like in the example above GET、not-found etc. .Compojure Bottom use clout This library implements , and clout Itself is based on a parser generator(instaparse) Defined “ route ” Domain specific language . Core rules as follows :
(def ^:private route-parser
(insta/parser
"route = (scheme / part) part*
scheme = #'(https?:)?//'
<part> = literal | escaped | wildcard | param
literal = #'(:[^\\p{L}_*{}\\\\]|[^:*{}\\\\])+'
escaped = #'\\\\.'
wildcard = '*'
param = key pattern?
key = <':'> #'([\\p{L}_][\\p{L}_0-9-]*)'
pattern = '{' (#'(?:[^{}\\\\]|\\\\.)+' | pattern)* '}'"
:no-slurp true))Compojure The way of matching the middle road is also very clever , Here is a detailed introduction .
Compojure Routing distribution
Compojure adopt routes Put a series of handler encapsulated , Its internal call routing How to find the right handler. The code of these two methods is very concise :
(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 Inside, by calling some Function returns the first non nil call , This solves the problem of route matching . As can be seen from this example Clojure The expressive power of language .
In the use of GET Such macro definitions handler when , Would call wrap-route-matches To wrap the real processing logic , The logic is as follows :
(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))))))Only in url And http method When all match , To call handler Handle http request , Other situations return directly nil, This is the same as what I said before some Together, a complete routing function is formed .
because routes The return value of is the same as handler equally , It's an acceptance request map return response map Function of , So it can be combined arbitrarily like building blocks , The implementation is similar to Flask in blueprints Modular function of . for example :
;; 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 Carry out inside handler The combination of
(defroutes handlers
(GET "/" [] "index")
(stu/handlers)
(tea/handlers))Middleware Function extension
Argument parsing
Compojure Solved the routing problem , Parameter acquisition is impossible through customization middleware Realized ,compojure.handler Namespaces provide common middleware The combination of , in the light of RESTful have access to api This combinatorial function , It will bring QueryString The parameters in are resolved to request map Medium :query-params key in , The parameters in the form are resolved to request map Medium :form-params.
(def app
(-> handlers
handler/api))JSON serialize
because RESTful In service , The requested data and the returned data are usually JSON Format , So we need to add two additional functions to realize JSON Serialization .
;; First quote ring.middleware.json
(def app
(-> handlers
wrap-json-response
wrap-json-body
handler/api))Record the request time
Usually , We need to record the processing time of each request , It's very simple , Realize a record-response-time that will do :
(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
(-> handlers
wrap-json-response
wrap-json-body
handler/api
record-response-time)) It should be noted that record-response-time Need to put in middleware Outermost layer , In this way, it can record a request through all middleware + handler Processing time .
Encapsulation exception
secondly , Another common requirement is to encapsulate exceptions , When there is an error on the server, it returns a client-side friendly error message , Instead of the error stack on the server .
(defn wrap-exception
[handler]
(fn [request]
(try
(handler request)
(catch Throwable e
(response {:code 20001
:msg "inner error})))))
(def app
(-> handlers
wrap-json-response
wrap-json-body
handler/api
wrap-exception
record-response-time))The order ! The order ! The order !
One App Medium middleware Call order is very important , Because it's different middleware Between request map And response map It's interdependent , So in defining middleware Be sure to pay attention to the order . A picture is worth a thousand words :
summary
stay Java EE in , To write Web Projects are usually configured with various XML file , There are a lot of configurations before the code is written jar Packet dependency , these jar Packages are likely to conflict , Then you need to spend a lot of time dealing with these dependency conflicts , Really troublesome .
Ring It's not so much a framework , Rather, it is composed of various short and concise functions lib, It fully shows Clojure The power of language , Through the combination of functions, a complete set of HTTP Abstract mechanism , Through the macro to achieve “ route ” Domain specific languages , It greatly simplifies the definition of routing , It facilitates the decomposition of modules .
In addition to the introduction above ,Ring There is also lein-ring , It can reload the modified namespace without restarting the service ( And its impact ), Development has never been smooth .
Ring + Compojure + lein-ring You deserve it .
边栏推荐
- Pat grade a 1050 string subtraction
- 山西阳泉一煤矿发生致1人死亡安全事故,被责令停产整顿
- 《硅谷之谜》读后感
- Understanding weight sharing in convolutional neural networks
- First knowledge of OpenGL (3) fragment shader
- I would like to ask you guys, how to specify the character set of MySQL CDC tables? I can't find the corresponding connector parameters on the official website. I read one
- 朋友圈如何测试(思维导图)
- Clojure Web 开发-- Ring 使用指南
- Yushenle's learning record: the first project of SOC FPGA -hello world
- JVM 的类初始化机制
猜你喜欢

大型仿人机器人整机构型研究与应用

A comprehensive review of image enhancement technology in deep learning

可信隐私计算框架“隐语”开源专家观点集锦

parker泵PV140R1K1T1PMMC

Pandora IOT development board learning (RT thread) - Experiment 17 esp8266 experiment (learning notes)

Google Earth engine - merra-2 m2t1nxlv: 1980 present global pressure, temperature, wind and other data sets

My brother created his own AI anti procrastination system, and he was "blinded" when playing with his mobile phone | reddit was hot

Collection of open source expert opinions on trusted privacy computing framework "argot"

Yushenle's learning record: the first project of SOC FPGA -hello world

Delta controller rmc200
随机推荐
Development and implementation of campus epidemic prevention and control management system based on SSM
Bugku login2
ROS problems and Solutions - relying on package installation and unable to correct errors
Jmeter快速上手之接口测试
什么是GPIO,它有什么用
2022年最新西藏建筑施工架子工(建筑特种作业)模拟考试试题及答案
博途PLC顺序开关机功能块(SCL)
parker电磁阀D1VW020DNYPZ5
Research and application of the whole configuration of large humanoid robot
PAT甲级 1044 Shopping in Mars
Alibaba cloud DMS MySQL cloud database report error, solve!!
Pat grade a 1046 shortest distance
C# 给Word每一页设置不同文字水印
2022 latest Beijing Construction Safety Officer simulation question bank and answers
hawe螺旋插装式单向阀RK4
This article explains in detail the discovery and processing of bigkey and hotkey in redis
Parker pump pv140r1k1t1pmmc
JVM 的类初始化机制
.net get injection object manually
13年资深开发者分享一年学习Rust经历:从必备书目到代码练习一网打尽