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

Sane, smart, fast, Clojure web services -- Rook

Sane, smart, fast, Clojure web services -- Rook

As presented to Clojerks (PDX Clojure User's Group).

Howard M. Lewis Ship

April 02, 2015
Tweet

More Decks by Howard M. Lewis Ship

Other Decks in Programming

Transcript

  1. 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))))
  2. (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})))
  3. $ 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!
  4. (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)}}) 

  5. $ 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>
  6. $ 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" }
  7. (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)}}) 

  8. (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}))
  9. $ 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" }
  10. (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}))
  11. (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
  12. (defn ping
 {:route [:get [:id]]}
 [request id]
 {:status 200
 :headers

    {}
 :body (format "Health check %s A-OK!" id)})

  13. (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))))))
  14. 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
  15. 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