Skip to content

Instantly share code, notes, and snippets.

@lagenorhynque
Last active November 11, 2022 07:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lagenorhynque/32274d72a706c345c1765c8dffb3774c to your computer and use it in GitHub Desktop.
Save lagenorhynque/32274d72a706c345c1765c8dffb3774c to your computer and use it in GitHub Desktop.
Interceptors: Into the Core of Pedestal

Interceptors

Into the Core of Pedestal


カマイルカ🐬/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])

twitter icon


  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!

home-page 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

about-page 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


(def common-interceptors [(body-params/body-params) http/html-body])

(def routes #{["/" :get (conj common-interceptors `home-page)]
              ["/about" :get (conj common-interceptors `about-page)]})

src/hello_pedestal/service.clj


(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-schemas)
                             (interceptor/attach-database db)]
        auth-interceptors [(body-params/body-params)
                           http/json-body
                           (interceptor/store-session redis)
                           interceptor/attach-tx-data
                           (interceptor/validate validation-schemas)
                           (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

example code


interceptors

middleware


other libraries

#!/usr/bin/env bash
# npm install -g reveal-md
reveal-md interceptors-into-the-core-of-pedestal.md --theme night --highlight-theme monokai-sublime $@
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment