(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])
-
Introduction to Pedestal
-
Ring "middleware"
-
Pedestal "interceptor"
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)
$ 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
$ 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
$ 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!
(defn home-page
[request]
(println request)
(ring-resp/response "Hello World!"))
src/hello_pedestal/service.clj#L13-L16
$ 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
(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
;; 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
{: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
,,,]))
{:status 200
:headers {"Content-Type" "application/json"}
:body {:greeting "Hello, World!"}}
spec
(s/def ::response
(s/keys :opt-un [::status ::headers ::body]))
(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
(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
{: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
,,,]))
(def keyword-params*
{:name ::keyword-params*
:enter (fn [context]
(update-in context [:request :params]
#(->> %
(map (fn [[k v]] [(keyword k) v]))
(into {}))))})
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
(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