264 Views
September 27, 18
スライド概要
Introduction to Pedestal (Clojure server-side library) and its "interceptors".
「楽しく楽にcoolにsmartに」を理想とするprogrammer/philosopher/liberalist/realist。 好きな言語はClojure, Haskell, Python, English, français, русский。 読書、プログラミング、語学、法学、数学が大好き! イルカと海も大好き🐬
Interceptors Into the Core of Pedestal
lagénorhynque カマイルカ /laʒenɔʁɛk ̃ / (defprofile lagénorhynque :name "Kent OHASHI" :languages [Clojure Common-Lisp Scheme Haskell English français] :interests [programming language-learning law mathematics] :contributing [github.com/japan-clojurians/clojure-site-ja])
1. Introduction to Pedestal 2. Ring "middleware" 3. Pedestal "interceptor"
Introduction to Pedestal
What is Pedestal? Pedestal is a set of libraries written in Clojure that aims to bring both the language and its principles (Simplicity, Power and Focus) to server-side development. cf. pedestal-app: client-side (no longer developed)
Create a Pedestal project $ lein new pedestal-service hello-pedestal Generating a pedestal-service application called hello-pedestal. $ cd hello-pedestal/ cf. lagenorhynque/hello-pedestal
$ tree . . ├── Capstanfile ├── Dockerfile ├── README.md ├── config │ └── logback.xml ├── project.clj ├── src │ └── hello_pedestal │ ├── server.clj │ └── service.clj └── test └── hello_pedestal └── service_test.clj
Run the server
$ lein run
INFO org.eclipse.jetty.util.log - Logging initialized @16865ms
to org.eclipse.jetty.util.log.Slf4jLog
Creating your server...
INFO org.eclipse.jetty.server.Server - jetty-9.4.10.v20180503;
built: 2018-05-03T15:56:21.710Z; git: daa59876e6f384329b122929e7
0a80934569428c; jvm 10.0.2+13
INFO o.e.j.server.handler.ContextHandler - Started o.e.j.s.Ser
vletContextHandler@16768389{/,null,AVAILABLE}
INFO o.e.jetty.server.AbstractConnector - Started ServerConnec
tor@22531d51{HTTP/1.1,[http/1.1, h2c]}{localhost:8080}
INFO org.eclipse.jetty.server.Server - Started @17463ms
GET http://localhost:8080
$ curl -i "http://localhost:8080"
HTTP/1.1 200 OK
Date: Tue, 25 Sep 2018 08:41:25 GMT
Strict-Transport-Security: max-age=31536000; includeSubdomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Content-Security-Policy: object-src 'none'; script-src 'unsafe-i
nline' 'unsafe-eval' 'strict-dynamic' https: http:;
Content-Type: text/html;charset=utf-8
Transfer-Encoding: chunked
Hello World!
homepage handler (defn home-page [request] (println request) (ring-resp/response "Hello World!")) src/hello_pedestal/service.clj#L13-L16
GET http://localhost:8080/about
$ curl -i "http://localhost:8080/about"
HTTP/1.1 200 OK
Date: Tue, 25 Sep 2018 08:41:43 GMT
Strict-Transport-Security: max-age=31536000; includeSubdomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Content-Security-Policy: object-src 'none'; script-src 'unsafe-i
nline' 'unsafe-eval' 'strict-dynamic' https: http:;
Content-Type: text/html;charset=utf-8
Transfer-Encoding: chunked
Clojure 1.9.0 - served from /about
aboutpage handler (defn about-page [request] (ring-resp/response (format "Clojure %s - served from %s" (clojure-version) (route/url-for ::about-page)))) src/hello_pedestal/service.clj#L7-L11
routes
;; Defines "/" and "/about" routes with their associated :get ha
;; ndlers.
;; The interceptors defined after the verb map (e.g., {:get home
;; -page}
;; apply to / and its children (/about).
(def common-interceptors [(body-params/body-params)
http/html-body])
;; Tabular routes
(def routes #{["/" :get (conj common-interceptors
`home-page)]
["/about" :get (conj common-interceptors
`about-page)]})
src/hello_pedestal/service.clj#L18-L25
Ring "middleware"
request (map)
{:server-port 8080
:server-name "localhost"
:remote-addr "127.0.0.1"
:uri "/hello"
:query-string "name=World"
:scheme :http
:request-method :get
:headers {"Accept" "application/json"}
:body nil}
spec
(s/def ::request
(s/keys :opt-un [::server-port ::server-name ::remote-addr
::uri ::query-string ::scheme
::request-method ::headers ::body
,,,]))
response (map)
{:status 200
:headers {"Content-Type" "application/json"}
:body {:greeting "Hello, World!"}}
spec
(s/def ::response
(s/keys :opt-un [::status ::headers ::body]))
handler (function)
(defn hello [request]
(let [name (get-in request [:params :name])]
{:status 200
:headers {"Content-Type" "application/json"}
:body {:greeting (str "Hello, " name "!")}}))
spec
(s/def ::handler
(s/fspec :args (s/cat :request ::request)
:ret ::response))
cf. asynchronous handler
middleware (defn wrap-keyword-params* [handler] (fn [request] (handler (update request :params #(->> % (map (fn [[k v]] [(keyword k) v])) (into {})))))) cf. ring.middleware.keyword-params
spec (s/def ::middleware (s/fspec :args (s/cat :handler ::handler) :ret ::handler)) template (defn some-middleware [handler] (fn [request] (let [response (handler (f request))] (g response)))) cf. asynchronous middleware
Ring middleware
Pedestal "interceptors"
context (map)
{:request {:protocol "HTTP/1.1", :async-supported? true,
:remote-addr "127.0.0.1", ,,,},
:response nil,
:io.pedestal.interceptor.chain/terminators (#object[io.pedestal
.http.impl.servlet_interceptor$terminator_inject$fn__15706 0x257
66941 "io.pedestal.http.impl.servlet_interceptor$terminator_inje
ct$fn__15706@25766941"]),
:io.pedestal.interceptor.chain/queue #object[clojure.lang.Persi
stentQueue 0x37f89872 "clojure.lang.PersistentQueue@72c17787"],
,,,}
spec
(s/def ::context
(s/keys :opt-un [::request ::response]
:opt [:io.pedestal.interceptor.chain/queue
:io.pedestal.interceptor.chain/terminators
,,,]))
interceptor (def keyword-params* {:name ::keyword-params* :enter (fn [context] (update-in context [:request :params] #(->> % (map (fn [[k v]] [(keyword k) v])) (into {}))))}) cf. io.pedestal.http.params
spec (s/def ::enter (s/fspec :args (s/cat :context ::context) :ret ::context)) (s/def ::leave (s/fspec :args (s/cat :context ::context) :ret ::context)) (s/def ::interceptor (s/keys :opt-un [::name ::enter ::leave ::error])) template (def some-interceptor {:name ::some-interceptor :enter (fn [context] (f context)) :leave (fn [context] (g context))}) cf. function returning a core.async channel
Pedestal interceptors
e.g. lagenorhynque/hello-pedestal (def common-interceptors [(body-params/body-params) http/html-body (def routes #{["/" :get (conj common-interceptors `home-page)] ["/about" :get (conj common-interceptors `about-pag src/hello_pedestal/service.clj
e.g. lagenorhynque/js-frameworks (chat-server) (defmethod ig/init-key ::routes [_ {:keys [db redis]}] (let [common-interceptors [(body-params/body-params) http/json-body (interceptor/store-session redis) interceptor/authenticate interceptor/attach-tx-data (interceptor/validate validation-sch (interceptor/attach-database db)] auth-interceptors [(body-params/body-params) http/json-body (interceptor/store-session redis) interceptor/attach-tx-data (interceptor/validate validation-schem (interceptor/attach-database db)]]
#(route/expand-routes
#{["/api/authentication" :get
(conj common-interceptors
`authentication/fetch-user)]
["/api/authentication" :post
(conj auth-interceptors
`authentication/login)]
["/api/authentication" :delete
(conj auth-interceptors
`authentication/logout)]
["/api/channels" :get (conj common-interceptors
`channels/list-channels)]
["/api/channels" :post (conj common-interceptors
`channels/create-channel)]
,,,})))
src/clj/chat_server/routes.clj
Further Reading Pedestal Pedestal GitHub Microservices with Clojure example code lagenorhynque/hello-pedestal lagenorhynque/js-frameworks (chat-server)
interceptors Interceptors Why interceptors? - quanttype middleware Concepts (Middleware) · ring-clojure/ring Wiki ring/SPEC · ring-clojure/ring Asynchronous Ring - Boolean Knot
other libraries reitit cf. sieppari Reitit, Data-Driven Routing with Clojure(Script) - Metosin Welcome Reitit 0.2.0! - Metosin re-frame