Ring handler a function which accepts a request (map) and returns a response (map).

Ring middleware

function (handler, &args -> function(request -> response) because it is a closure handler and &args are in scope within the returned handler function.

a function which accepts a “next handler” (the next handler in the chain) + any extra arguments and returns a handler (function), which will accept a request and return a response.

the returned function can fetch the response by calling the given handler with the given request.

  • it could return a handler function which changes the request and then calls the given “next handler” with changed request.
  • it could return a handler function which calls the given “next handler” and changes the given response before returning it.
  • the handler returned by the middleware does not have to call the given “next handler”, it can return a response, i.e. terminate the middleware chain.

so the returned handler function has access to the “next handler” which is only actually used to get the response, via (handler request)..

;; middleware which does nothing
(defn wrap-nothing [handler]
  (fn [request]
    (handler request)))
;; middleware which terminates the chain
(defn wrap-nothing [handler]
  (fn [request]
    (response "Terminated"))
;; middleware which prints the request
(defn wrap-print-request [handler]
  (fn [request]
    (println request)
    (handler request))
;; adds a uuid to the response
(defn wrap-uuid [handler]
  (fn [request]
    (let [response (handler request)
          uuid (..)]
      (assoc response :uuid uuid))))
;; adds a uuid to the request
(defn wrap-uuid [handler]
  (fn [request]
    (handler (assoc request :uuid uuid))))
(defn wrap-joke [handler joke]
  (fn [request]
    (let [response (handler request)]
      (assoc response :joke joke))))

Chaing the middlewares

(def app
  (-> handler
      (wrap-uuid)
      (wrap-joke "How did the chicken cross the road?")))

same as (wrap-joke (wrap-uuid handler) "How did the chicken cross the road?"), so the first form is evaludated first and is wrapped in the following forms, the last being the outermost form which wraps everything.

Typically the first handler passed to the threading macro will be a handler which does routing.

Note that app will return a handler, so:

(app request) ;; => response

https://github.com/ring-clojure/ring/wiki/Middleware-Patterns

(defroutes (GET “/” [] home-page-handler)

defroutes returns a handler (function)

Putting it all together

;; a ring application is a function which accepts a request (map) and returns a response (map), these functions are called handlers.

(def request-homepage (mock/request :get "/"))

(defn app [request] { :body "HELLO" })

(app request-homepage) ;; {:body "HELLO"}
;; note def, not defn
(def app
  (fn [request] { :body "HELLO"}))
  
(app request-homepage) ;; => {:body "HELLO" }
(defn homepage-handler [request]
  { :body "HELLO"})

(def app
  (fn [request] (homepage-handler request)))
  
;; or

(def app homepage-handler)
;; adding middleware

;; middleware input is a handler (+ extra args) and output is a handler.
;; a handler being a function which accepts a request and returns a response

(defn middleware [handler]
  (fn [request]
    ;; ...
    ;; Do something to the request before sending it down the chain.
    ;; ...
    (let [response (handler request)]
      ;; ...
      ;; Do something to the response that's coming back up the chain.
      ;; ...
      response)))

(handler3 (handler2 (handler1 request)))  ;=> response
((middleware1 (middleware2 (middleware3 handler1))) request)  ;=> response

;; https://stackoverflow.com/questions/19455801/why-does-the-order-of-ring-middleware-need-to-be-reversed

clj (defn wrap-joke handler joke) ;; return changed response

(wrap-joke homepage-handler “Why did the…?”) ;; => function, aka handler

;; call the function, passing a request ((wrap-joke homepage-handler “Why did the…?”) request-homepage) ;; => {:body “HELLO”, :joke “Why did the…?”}

;; chainging middleware

(def app (-> homepage-handler (wrap-joke “Why did the…?”)))

;; routing

(defroutes public-routes (GET “/” [] homepage-handler))

(public-routes request-homepage) ;; => {:status 200, :headers {}, :body “HELLO”}

;; defroutes creates a handler which will match the URL to other handlers.

(public-routes (mock/request :get “/asdasdasd”)) ;; => nil

(def app (-> public-routes (wrap-joke “Why did the chicken?”)))

(app request-homepage) ;; {:status 200, :headers {}, :body “HELLO”, :joke “Why did the chicken?”}

(app (mock/request :get “/asdasdasd”)) ;; => {:joke “Why did the chicken?”}

;; when the route is not found nothing is added to the response by the public-routes handler.

;; note that since there is currently no status the web server will default to 200, so no 404 response is given for unfound routes.

;; to return a 404 when a route is not found:

(defroutes public-routes (GET “/” [] homepage-handler) (route/not-found “Page not found”))

(app (mock/request :get “/asdasdasd”)) ;; => {:status 404, :headers {“Content-Type” “text/html; charset=utf-8”}, :body “Page not found”, :joke “Why did the chicken?”} ```