Share
Embed
Sane, smart, fast, Clojure web services -- Rook
As presented to Clojerks (PDX Clojure User's Group).
Howard M. Lewis Ship
April 02, 2015
Transcript
33 slides
-
Liberator (defresource entry-resource [id] :allowed-methods [:get :put :delete] :known-content-type? #(check-content-type
% ["application/json"]) :exists? (fn [_] (let [e (get @entries id)] (if-not (nil? e) {::entry e}))) :existed? (fn [_] (nil? (get @entries id ::sentinel))) :available-media-types ["application/json"] :handle-ok ::entry :delete! (fn [_] (dosync (alter entries assoc id nil))) :malformed? #(parse-json % ::data) :can-put-to-missing? false :put! #(dosync (alter entries assoc id (::data %))) :new? (fn [_] (nil? (get @entries id ::sentinel)))) -
(ns org.example.resources.health-check) (defn ping {:route [:get []]} [] {:status
200 :headers {} :body "Health check A-OK!"}) -
(ns org.example.server (:require [ring.adapter.jetty :as jetty] [io.aviso.rook :as rook])) (defn
start-server [port] (let [handler (-> (rook/namespace-handler ['org.example.resources.health-check]) rook/wrap-with-standard-middleware)] (jetty/run-jetty handler {:port port :join? false}))) -
$ http localhost:8080 HTTP/1.1 200 OK Content-Length: 18 Date: Thu,
26 Mar 2015 23:49:54 GMT Server: Jetty(7.6.13.v20130916) Health check A-OK! -
(ns org.example.resources.health-check) (defn ping {:route [:get [:id]]} [request id]
{:status 200 :headers {} :body {:uri (:uri request) :message (format "Health check %s A-OK!" id)}}) -
$ http localhost:8080 HTTP/1.1 404 Not Found Cache-Control: must-revalidate,no-cache,no-store Content-Length:
1267 Content-Type: text/html;charset=ISO-8859-1 Date: Thu, 26 Mar 2015 23:59:19 GMT Server: Jetty(7.6.13.v20130916) <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/> <title>Error 404 Not Found</title> </head> <body> <h2>HTTP ERROR: 404</h2> <p>Problem accessing /. Reason: <pre> Not Found</pre></p> <hr /><i><small>Powered by Jetty://</small></i> </body> </html> -
$ http localhost:8080/gnip HTTP/1.1 200 OK Content-Length: 51 Content-Type: application/json;
charset=utf-8 Date: Fri, 27 Mar 2015 00:00:36 GMT Server: Jetty(7.6.13.v20130916) { "message": "Health check gnip A-OK!", "uri": "/gnip" } -
(ns org.example.resources.health-check) (defn ping {:route [:get [:id]]} [request id]
{:status 200 :headers {} :body {:uri (:uri request) :message (format "Health check %s A-OK!" id)}}) -
(ns org.example.resources.offers (:require [io.aviso.rook.utils :as utils])) (defn show [db-conn
id] (utils/response {:db-conn db-conn :id id})) -
(ns org.example.complex-server (:require [ring.adapter.jetty :as jetty] [io.aviso.rook :as rook] [io.aviso.rook.server
:as server])) (defn create-handler [database-connection] (-> (rook/namespace-handler {:arg-resolvers {'db-conn (fn [request] database-connection)}} ["offers" 'org.example.resources.offers] ["users" 'org.example.resources.users]) rook/wrap-with-standard-middleware)) (defn start-server [database-connection port] (jetty/run-jetty (server/construct-handler {:log true :reload true} #'create-handler database-connection) {:port port :join? false})) -
$ http localhost:8080/offers/1234 HTTP/1.1 200 OK Content-Length: 44 Content-Type: application/json;
charset=utf-8 Date: Fri, 27 Mar 2015 00:21:30 GMT Server: Jetty(7.6.13.v20130916) { "db-conn": "mock-db-connection", "id": "1234" } -
(ns org.example.complex-server (:require [ring.adapter.jetty :as jetty] [io.aviso.rook :as rook] [io.aviso.rook.server
:as server])) (defn create-handler [database-connection] (-> (rook/namespace-handler {:arg-resolvers {'db-conn (fn [request] database-connection)}} ["offers" 'org.example.resources.offers] ["users" 'org.example.resources.users]) rook/wrap-with-standard-middleware)) (defn start-server [database-connection port] (jetty/run-jetty (server/construct-handler {:log true :reload true} #'create-handler database-connection) {:port port :join? false})) -
{:arg-resolvers {'db-conn (fn [request] database-connection)} Symbol to match Resolves the
argument value from the request -
(ns org.example.complex-server (:require [ring.adapter.jetty :as jetty] [io.aviso.rook :as rook] [io.aviso.rook.server
:as server])) (defn create-handler [database-connection] (-> (rook/namespace-handler {:arg-resolvers {'db-conn (fn [request] database-connection)}} ["offers" 'org.example.resources.offers] ["users" 'org.example.resources.users]) rook/wrap-with-standard-middleware)) (defn start-server [database-connection port] (jetty/run-jetty (server/construct-handler {:log true :reload true} #'create-handler database-connection) {:port port :join? false})) Development mode: the handler is recreated on each request -
(defn ping {:route [:get [:id]]} [request id] {:status 200 :headers
{} :body (format "Health check %s A-OK!" id)}) -
Endpoint Middleware (fn [handler endpoint-metadata]) ➠ handler Meta-data of function
merged w/ namespace metadata (to provide defaults) -
(defn wrap-with-operation-tracking "Checks for the :operation-trace metadata …" [handler metadata]
(when-let [label (:operation-trace metadata)] (let [logger (-> metadata :ns t/get-logger)] (fn [request] (t/track* logger label (handler request)))))) -
Middleware Shootout Ring Rook No Metadata Available Has full metadata
of endpoint function Must return a handler Can return handler, or return nil Interceptors execute during request dispatch Interceptors execute after dispatch (once endpoint known) Tree of contexts & interceptors Stack of interceptors unique for each endpoint -
Checklist • Sane? — Yes, natural mapping from URIs to
simple functions • Smart? — Yes, metadata on functions w/ argument resolvers and middleware keeps code tight & free of globals • Fast? — Yes, efficient dispatch & interceptors