Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Interceptors: Into the Core of Pedestal

Kent OHASHI
September 27, 2018

Interceptors: Into the Core of Pedestal

Introduction to Pedestal (Clojure server-side library) and its "interceptors".

Kent OHASHI

September 27, 2018
Tweet

More Decks by Kent OHASHI

Other Decks in Programming

Transcript

  1. カマイルカ /laʒenɔʁɛ̃k/ カマイルカ /laʒenɔʁɛ̃k/ lagénorhynque lagénorhynque (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])
  2. What is What is ? ? cf. : client-side (no

    longer developed) Pedestal 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. pedestal-app
  3. Create a Pedestal project Create a Pedestal project cf. $

    lein new pedestal-service hello-pedestal Generating a pedestal-service application called hello-pedestal. $ cd hello-pedestal/ lagenorhynque/hello-pedestal
  4. $ tree . . ├── Capstanfile ├── Dockerfile ├── README.md

    ├── config │ └── logback.xml ├── project.clj ├── src │ └── hello_pedestal │ ├── server.clj │ └── service.clj └── test └── hello_pedestal └── service_test.clj
  5. Run the server 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
  6. GET http://localhost:8080 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!
  7. GET http://localhost:8080/about 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
  8. routes routes src/hello_pedestal/service.clj#L18-L25 ;; 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)]})
  9. request (map) request (map) spec {: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} (s/def ::request (s/keys :opt-un [::server-port ::server-name ::remote-addr ::uri ::query-string ::scheme ::request-method ::headers ::body ,,,]))
  10. response (map) response (map) spec {:status 200 :headers {"Content-Type" "application/json"}

    :body {:greeting "Hello, World!"}} (s/def ::response (s/keys :opt-un [::status ::headers ::body]))
  11. handler (function) handler (function) spec cf. asynchronous handler (defn hello

    [request] (let [name (get-in request [:params :name])] {:status 200 :headers {"Content-Type" "application/json"} :body {:greeting (str "Hello, " name "!")}})) (s/def ::handler (s/fspec :args (s/cat :request ::request) :ret ::response))
  12. middleware middleware cf. (defn wrap-keyword-params* [handler] (fn [request] (handler (update

    request :params #(->> % (map (fn [[k v]] [(keyword k) v])) (into {})))))) ring.middleware.keyword-params
  13. spec template cf. asynchronous middleware (s/def ::middleware (s/fspec :args (s/cat

    :handler ::handler) :ret ::handler)) (defn some-middleware [handler] (fn [request] (let [response (handler (f request))] (g response))))
  14. context (map) context (map) spec {: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"], ,,,} (s/def ::context (s/keys :opt-un [::request ::response] :opt [:io.pedestal.interceptor.chain/queue :io.pedestal.interceptor.chain/terminators ,,,]))
  15. interceptor interceptor cf. (def keyword-params* {:name ::keyword-params* :enter (fn [context]

    (update-in context [:request :params] #(->> % (map (fn [[k v]] [(keyword k) v])) (into {}))))}) io.pedestal.http.params
  16. spec template cf. function returning a core.async channel (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])) (def some-interceptor {:name ::some-interceptor :enter (fn [context] (f context)) :leave (fn [context] (g context))})
  17. e.g. e.g. lagenorhynque/js-frameworks (chat-server) 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)]]
  18. src/clj/chat_server/routes.clj #(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)] ,,,})))
  19. Further Reading Further Reading Pedestal Pedestal example code example code

    Pedestal GitHub Microservices with Clojure lagenorhynque/hello-pedestal lagenorhynque/js-frameworks (chat-server)
  20. interceptors interceptors middleware middleware Interceptors Why interceptors? - quanttype Concepts

    (Middleware) · ring-clojure/ring Wiki ring/SPEC · ring-clojure/ring Asynchronous Ring - Boolean Knot
  21. other libraries other libraries cf. reitit sieppari Reitit, Data-Driven Routing

    with Clojure(Script) - Metosin Welcome Reitit 0.2.0! - Metosin re-frame