Liking cljdoc? Tell your friends :D

Ring Router

Ring is a Clojure web applications library inspired by Python's WSGI and Ruby's Rack. By abstracting the details of HTTP into a simple, unified API, Ring allows web applications to be constructed of modular components that can be shared among a variety of applications, web servers, and web frameworks.

[metosin/reitit-ring "0.1.0-SNAPSHOT"]

Ring-router adds support for handlers, middleware and routing based on :request-method. Ring-router is created with reitit.ring/router function. It uses a custom route compiler, creating a optimized data structure for handling route matches, with compiled middleware chain & handlers for all request methods. It also ensures that all routes have a :handler defined. reitit.ring/ring-handler is used to create a Ring handler out of ring-router.

Example

Simple Ring app:

(require '[reitit.ring :as ring])

(defn handler [_]
  {:status 200, :body "ok"})

(def app
  (ring/ring-handler
    (ring/router
      ["/ping" handler])))

Applying the handler:

(app {:request-method :get, :uri "/favicon.ico"})
; nil
(app {:request-method :get, :uri "/ping"})
; {:status 200, :body "ok"}

The expanded routes shows the compilation results:

(-> app (ring/get-router) (reitit/routes))
; [["/ping"
;   {:handler #object[...]}
;   #Methods{:any #Endpoint{:data {:handler #object[...]},
;                           :handler #object[...],
;                           :middleware []}}]]

Note the compiled resuts as third element in the route vector.

Request-method based routing

Handler are also looked under request-method keys: :get, :head, :patch, :delete, :options, :post or :put. Top-level handler is used if request-method based handler is not found.

(def app
  (ring/ring-handler
    (ring/router
      ["/ping" {:name ::ping
                :get handler
                :post handler}])))

(app {:request-method :get, :uri "/ping"})
; {:status 200, :body "ok"}

(app {:request-method :put, :uri "/ping"})
; nil

Name-based reverse routing:

(-> app
    (ring/get-router)
    (reitit/match-by-name ::ping)
    :path)
; "/ping"

Middleware

Middleware can be added with a :middleware key, either to top-level or under :request-method submap. It's value should be a vector value of the following:

  1. normal ring middleware function handler -> request -> response
  2. vector of middleware function handler ?args -> request -> response and optinally it's args.

A middleware and a handler:

(defn wrap [handler id]
  (fn [request]
    (handler (update request ::acc (fnil conj []) id))))

(defn handler [{:keys [::acc]}]
  {:status 200, :body (conj acc :handler)})

App with nested middleware:

(def app
  (ring/ring-handler
    (ring/router
      ;; a middleware function
      ["/api" {:middleware [#(wrap % :api)]}
       ["/ping" handler]
       ;; a middleware vector at top level
       ["/admin" {:middleware [[wrap :admin]]}
        ["/db" {:middleware [[wrap :db]]
                ;; a middleware vector at under a method
                :delete {:middleware [[wrap :delete]]
                         :handler handler}}]]])))

Middleware is applied correctly:

(app {:request-method :delete, :uri "/api/ping"})
; {:status 200, :body [:api :handler]}
(app {:request-method :delete, :uri "/api/admin/db"})
; {:status 200, :body [:api :admin :db :delete :handler]}

Default handler

By default, if no routes match, nil is returned, which is not valid response in Ring:

(defn handler [_]
  {:status 200, :body ""})

(def app
  (ring/ring-handler
    (ring/router
      ["/ping" handler])))

(app {:uri "/invalid"})
; nil

Setting the default-handler as a second argument to ring-handler:

(def app
  (ring/ring-handler
    (ring/router
      ["/ping" handler])
    (constantly {:status 404, :body ""})))

(app {:uri "/invalid"})
; {:status 404, :body ""}

To get more correct http error responses, ring/create-default-handler can be used. It differentiates :not-found (no route matched), :method-not-accepted (no method matched) and :not-acceptable (handler returned nil).

With defaults:

(def app
  (ring/ring-handler
    (ring/router
      [["/ping" {:get handler}]
       ["/pong" (constantly nil)]])
    (ring/create-default-handler)))

(app {:request-method :get, :uri "/ping"})
; {:status 200, :body ""}

(app {:request-method :get, :uri "/"})
; {:status 404, :body ""}

(app {:request-method :post, :uri "/ping"})
; {:status 405, :body ""}

(app {:request-method :get, :uri "/pong"})
; {:status 406, :body ""}

With custom responses:

(def app
  (ring/ring-handler
    (ring/router
      [["/ping" {:get handler}]
       ["/pong" (constantly nil)]])
    (ring/create-default-handler
      {:not-found (constantly {:status 404, :body "kosh"})
       :method-not-allowed (constantly {:status 405, :body "kosh"})
       :not-acceptable (constantly {:status 406, :body "kosh"})})))

(app {:request-method :get, :uri "/ping"})
; {:status 200, :body ""}

(app {:request-method :get, :uri "/"})
; {:status 404, :body "kosh"}

(app {:request-method :post, :uri "/ping"})
; {:status 405, :body "kosh"}

(app {:request-method :get, :uri "/pong"})
; {:status 406, :body "kosh"}

Async Ring

All built-in middleware provide both 2 and 3-arity and are compiled for both Clojure & ClojureScript, so they work with Async Ring and Node.js too.

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close