Routing Requests with Ring
Routing Requests with Ring
Ring is the de facto standard HTTP library for Clojure. Because of this there are many tools built on top of it so we will rarely be using it directly. However it is important to learn how it works so we can make better applications.
Ring applications consist of four components:
- the request - an HTTP request transformed to Ring request by the adapter sent to middleware
- the middleware - modifies the Ring request as necessary, until it reaches the handler
- the handler - generates a Ring reponse from the request and sends it to middleware
- the response - an HTTP response generated from the response map by the adapter
Here is a picture of this process and components to better understand:
(taken from https://practical.li/blog/posts/webapp-routes-with-json/)
Creating a Web Server
Lets create a ring demo app and add Ring as a dependancy:
1lein new ring-demo
1; ring-demo/project.clj
2(defproject ring-demo "0.1.0-SNAPSHOT"
3 :description "FIXME: write description"
4 :url "http://example.com/FIXME"
5 :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
6 :url "https://www.eclipse.org/legal/epl-2.0/"}
7 :dependencies [[org.clojure/clojure "1.11.1"]
8 [ring "1.9.6"]]
9 :repl-options {:init-ns ring-demo.core}
10 :main ring-demo.core)
Handling requests
To handle incoming requests we will need a handler function that takes a request and return a response map. We can create one like this:
1; ring-demo/src/ring-demo/core.clj
2(defn handler
3 [request-map]
4 {:status 200
5 :header {"Content-Type" "text/html"}
6 :body "<html><body>Hello World</body></html>"
7 })
We then need to start our web server with the handler. For this we can use Ring’s jetty adapter.
1; ring-demo/src/ring-demo/core.clj
2(ns ring-demo.core
3 (:require [ring.adapter.jetty :as jetty]))
4
5(defn handler
6 [request-map]
7 {:status 200
8 :header {"Content-Type" "text/html"}
9 :body "<html><body>Hello World</body></html>"
10 })
11
12(defn -main
13 []
14 (jetty/run-jetty
15 handler
16 {:port 3000
17 :join? false})) ; runs in another thread so REPL doesn't block
To run our code we can use lein run or just execute -main in the REPL. We can now see
our Hello World string when we go to localhost:3000.
Because sending HTML with response code 200 is a common task, Ring provides ring.util.response/response
function to shorten the syntax. We can use it as:
1; ring-demo/src/ring-demo/core.clj
2(ns ring-demo.core
3 (:require [ring.adapter.jetty :as jetty]
4 [ring.util.response :as response]
5 ))
6
7(defn handler
8 [request-map]
9 (response/response
10 "<html><body>Hello World</body></html>"))
Middleware
Middleware is used to wrap the handlers in functions that can modify both the response and/or the request. These functions take a handler and return a new handler with some added behaviour. Here is an example:
1; ring-demo/src/ring-demo/core.clj
2(defn wrap-nocache
3 [handler]
4 (fn [request]
5 (-> request
6 handler
7 (assoc-in [:headers "Cache-Control"] "no-cache"))))
To add this middlware we just change our main function:
1; ring-demo/src/ring-demo/core.clj
2(defn -main
3 []
4 (jetty/run-jetty
5 (-> handler
6 wrap-nocache)
7 {:port 3000
8 :join? false})) ; runs in another thread so REPL doesn't block
We can see our header is in place
1curl -I localhost:3000
2 HTTP/1.1 200 OK
3 ...
4 Cache-Control: no-cache