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

Transcript

33 slides
  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

More Decks by Howard M. Lewis Ship

Other Decks in Programming