Date post: | 26-May-2015 |
Category: |
Technology |
Upload: | norman-richards |
View: | 1,285 times |
Download: | 3 times |
HTTP → {} → fn → {} → HTTP
Request Map Response Map
Handler FN
(def server (ring.adapter.jetty/run-jetty #’handler {:port 8080 :join? false})) !(.stop server)
ring-jetty-adaptor
ring handler
ring.adaptor.jetty/proxy-handler
(defn- proxy-handler "Returns an Jetty Handler implementation for the given Ring handler." [handler] (proxy [AbstractHandler] [] (handle [_ ^Request base-request request response] (let [request-map (servlet/build-request-map request) response-map (handler request-map)] (when response-map (servlet/update-servlet-response response response-map) (.setHandled base-request true))))))
ring.util.servlet/build-request-map
(defn build-request-map "Create the request map from the HttpServletRequest object." [^HttpServletRequest request] {:server-port (.getServerPort request) :server-name (.getServerName request) :remote-addr (.getRemoteAddr request) :uri (.getRequestURI request) :query-string (.getQueryString request) :scheme (keyword (.getScheme request)) :request-method (keyword (.toLowerCase (.getMethod request))) :headers (get-headers request) :content-type (.getContentType request) :content-length (get-content-length request) :character-encoding (.getCharacterEncoding request) :ssl-client-cert (get-client-cert request) :body (.getInputStream request)})
not functional :(
(defn update-servlet-response "Update the HttpServletResponse using a response map." [^HttpServletResponse response, {:keys [status headers body]}] (when-not response (throw (Exception. "Null response given."))) (when status (set-status response status)) (doto response (set-headers headers) (set-body body)))
ring.util.servlet/update-servlet-response
Ring Handlers
Handler: (RequestMap → ResponseMap)
(defn handler-nil [req] {:body nil})
response body
ring.util.servlet/set-body
(defn- set-body "Update a HttpServletResponse body with a String, ISeq, File or InputStream." [^HttpServletResponse response, body] (cond (string? body) (with-open [writer (.getWriter response)] (.print writer body)) (seq? body) ;; … (instance? File body) ;; … (nil? body) nil :else (throw (Exception. ^String (format "Unrecognized body: %s" body)))))
(defn handler-string [req] {:body "hello world”})
(defn handler-file [req] {:body (clojure.java.io/file "info.txt")})
(defn handler-status [req] {:status 402 :headers {"Location" "bitcoin:1G9TyAaKrfJn7q4Vrr15DscLXFSRPxBFaH?amount=.001"}})
Handlers can return status code and headers
ring.util.response/*(defn response "Returns a skeletal Ring response with the given body, status of 200, and no headers." [body] {:status 200 :headers {} :body body}) !(defn not-found "Returns a 404 'not found' response." [body] {:status 404 :headers {} :body body}) !(defn redirect "Returns a Ring response for an HTTP 302 redirect." [url] {:status 302 :headers {"Location" url} :body ""})
A few response helpers
(defn handler [req] (response/response "Hello, world!”)) !(defn handler [req] (response/redirect "http://lmgtfy.com/?q=http+redirect")) !(defn handler [req] (response/resource-response "hello.txt"))
(defn handler [req] (-> (response/response "") (response/status 302) (response/header "Location" "http://www.google.com")))
Building up a response
Wrapping requests (middleware)
Middleware: (Handler → Handler)
(defn handler-reload1 [req] (response/response (reload-me/some-work)))
A function we’d like to be reloaded if it changes
(defn handler-reload2 [req] (require 'ringtest.reload-me :reload) (handler-reload1 req))
The original handler is wrapped
(defn wrap-reload [other-handler] (fn [req] (require 'ringtest.reload-me :reload) (other-handler req))) (def handler-reload3 (wrap-reload #'handler-reload1))
Abstracting the reloading
(defn wrap-reload "Reload namespaces of modified files before the request is passed to the supplied handler. ! Takes the following options: :dirs - A list of directories that contain the source files. Defaults to [\"src\"]." [handler & [options]] (let [source-dirs (:dirs options ["src"]) modified-namespaces (ns-tracker source-dirs)] (fn [request] (doseq [ns-sym (modified-namespaces)] (require ns-sym :reload)) (handler request))))
ring.middleware.reload/wrap-reload
Smarter reloading surrounding the wrapped handler
(defn- add-middleware [handler options] (-> handler (add-auto-refresh options) (add-auto-reload options) (add-stacktraces options)))
ring.server.standalone/add-middleware
This is what “lein ring server” does
Middleware stacks
(middleware1 (middleware2 (middleware3 handler)))
(defn ring-stack [handler] (-> handler (wrap-reload) (wrap-stacktrace))) (defonce server-atom (atom nil)) (defn start [handler] (swap! server-atom (fn [server] (when server (.stop server)) (jetty/run-jetty handler {:port 8080 :join? false})))) (defn stop [] (swap! server-atom (fn [server] (when server (.stop server)) nil)))
Our custom “ring” middleware stack
And some custom sever code
• wrap-content-type • wrap-cookies • wrap-file-info • wrap-flash • wrap-head • wrap-keyword-params • wrap-multi-part-params • wrap-nested-params • wrap-not-modified • wrap-params • wrap-resource • wrap-session
ring.middleware.*
Lot’s of middleware to choose from
compojure.handler/api
(defn api "Create a handler suitable for a web API. This adds the following middleware to your routes: - wrap-params - wrap-nested-params - wrap-keyword-params" [routes] (-> routes wrap-keyword-params wrap-nested-params wrap-params))
An existing minimal stack for APIs
compojure.handler/site
(defn site "Create a handler suitable for a standard website. This adds the following middleware to your routes: - wrap-session - wrap-flash - wrap-cookies - wrap-multipart-params - wrap-params - wrap-nested-params - wrap-keyword-params ! A map of options may also be provided. These keys are provided: :session - a map of session middleware options :multipart - a map of multipart-params middleware options" [routes & [opts]] (-> (api routes) (with-opts wrap-multipart-params (:multipart opts)) (wrap-flash) (with-opts wrap-session (:session opts))))
Extends the API stack
(def handler (-> #'app compojure.handler/site ring-stack))
Use it, or make your own
(defn app-handler [app-routes & {:keys [session-options store multipart middleware access-rules formats]}] (letfn [(wrap-middleware-format [handler] (if formats (wrap-restful-format handler :formats formats) handler))] (-> (apply routes app-routes) (wrap-middleware middleware) (wrap-request-map) (api) (wrap-base-url) (wrap-middleware-format) (with-opts wrap-multipart-params multipart) (wrap-access-rules access-rules) (wrap-noir-validation) (wrap-noir-cookies) (wrap-noir-flash) (wrap-noir-session (update-in session-options [:store] #(or % (memory-store mem)))))))
noir.util.middleware/app-handler
Lot’s of customization
A hook to extend the noir stack
Routing
Route: (RequestMap → (Option ResponseMap))
(defn home [] (response/response "Home Page")) (defn foo [] (response/response "Foo Page")) (defn foo-n [n] (response/response (str "This is Foo#" n))) (defn app1 [req] (condp re-matches (:uri req) #"/" (home) #"/foo" (foo) #"/foo/(.*)" :>> #(foo-n (second %)) (response/not-found "Wat")))
Not ring handlers because they don’t
take a request.
Select the page to show based on URL
(defn route-to [handler] (fn [match] (if (string? match) (handler) (apply handler (rest match))))) (defn app2 [req] (condp re-matches (:uri req) #"/" :>> (route-to home) #"/foo" :>> (route-to foo) #"/foo/(.*)" :>> (route-to foo-n) (response/not-found "Wat")))
Abstract the route dispatch
Cleaner, but still awkward
(defn my-route [pattern page-fn] (fn [req] (if-let [match (re-matches pattern (:uri req))] ((route-to handler) page-fn)))) (defn app3 [req] (let [my-routes [(my-route #"/" home) (my-route #"/foo" foo) (my-route #"/foo/(.*)" foo-n) (my-route #".*" #(response/not-found "Wat"))]] (some #(% req) my-routes)))
Include the pattern
Much cleaner
The first route that responds wins
(defn app4 [req] (let [my-routes [(GET "/" [] (home)) (GET "/foo" [] (foo)) (GET "/foo/:id" [id] (foo-n id)) (route/not-found "Wat")]] (some #(% req) my-routes)))
Routing fn includes method and path
Some things never change
Some extra macro magic
(defn make-route "Returns a function that will only call the handler if the method and Clout route match the request." [method route handler] (if-method method (if-route route (fn [request] (render (handler request) request))))) !(defn- compile-route "Compile a route in the form (method path & body) into a function." [method route bindings body] `(make-route ~method ~(prepare-route route) (fn [request#] (let-request [~bindings request#] ~@body)))) !(defmacro GET "Generate a GET route." [path args & body] (compile-route :get path args body))
compojure.core/*
(def app5 (routes (GET "/" [] (home)) (GET "/foo" [] (foo)) (GET "/foo/:id" [id] (foo-n id)) (route/not-found "Wat")))
a collection of routes
(defn routing "Apply a list of routes to a Ring request map." [request & handlers] (some #(% request) handlers)) !(defn routes "Create a Ring handler by combining several handlers into one." [& handlers] #(apply routing % handlers))
(def foo-routes (routes (GET "/foo" [] (foo)) (GET "/foo/:id" [id] (foo-n id)))) (def app6 (routes (GET "/" [] (home)) foo-routes (route/not-found "Wat")))
Routing functions nest easily
(defn foobar-routes [foobar-type] (routes (GET "/" [] (str foobar-type " Page")) (GET "/:id" [id] (str foobar-type "#" id)))) (def app7 (routes (GET "/" [] (home)) (context "/foo" [] (foobar-routes "Foo")) (context "/bar" [] (foobar-routes "Bar")) (route/not-found "Wat")))
Not ideal - generates the route fn each call
(defmacro context "Give all routes in the form a common path prefix and set of bindings. ! The following example demonstrates defining two routes with a common path prefix ('/user/:id') and a common binding ('id'): ! (context \"/user/:id\" [id] (GET \"/profile\" [] ...) (GET \"/settings\" [] ...))" [path args & routes] `(#'if-route ~(context-route path) (#'wrap-context (fn [request#] (let-request [~args request#] (routing request# ~@routes))))))
(defprotocol Renderable (render [this request] "Render the object into a form suitable for the given request map.")) !(extend-protocol Renderable nil … String … APersistentMap … IFn … IDeref … File … ISeq … InputStream … URL … )
compojure.response/Renderable
Generate response maps based on types