-
Notifications
You must be signed in to change notification settings - Fork 520
Concepts
A web application developed for Ring consists of four components:
- Handler
- Request
- Response
- Middleware
Handlers are functions that define your web application. Synchronous handlers take one argument, a map representing a HTTP request, and return a map representing the HTTP response.
For example:
(defn what-is-my-ip [request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body (:remote-addr request)})
This function returns a map that Ring can translate into a HTTP response. The response returns a plain text file that contains the IP address that was used to access the web application.
Handlers may also be asynchronous. Handlers of this type take three arguments: the request map, a response callback and an exception callback.
For example:
(defn what-is-my-ip [request respond raise]
(respond {:status 200
:headers {"Content-Type" "text/plain"}
:body (:remote-addr request)}))
Because these two types of handler have different arities, we can combine them into a handler that can be used synchronously or asynchronously:
(defn what-is-my-ip
([request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body (:remote-addr request)})
([request respond raise]
(respond (what-is-my-ip request))))
All official Ring middleware supports both types of handler, but for most purposes synchronous handlers are sufficient.
The handler function can be converted into a web application through a variety of different methods which will be covered in the next section.
As previously mentioned, HTTP requests are represented by Clojure maps. There are a number of standard keys that will always exist, but requests can (and often do) contain custom keys added by middleware.
The standard keys are:
-
:server-port
The port on which the request is being handled. -
:server-name
The resolved server name, or the server IP address. -
:remote-addr
The IP address of the client or the last proxy that sent the request. -
:uri
The request URI (the full path after the domain name). -
:query-string
The query string, if present. -
:scheme
The transport protocol, either:http
or:https
. -
:request-method
The HTTP request method, which is one of:get
,:head
,:options
,:put
,:post
, or:delete
. -
:headers
A Clojure map of lowercase header name strings to corresponding header value strings. -
:body
An InputStream for the request body, if present.
Previous versions of Ring also had the following keys. These are now DEPRECATED.
-
:content-type
The MIME type of the request body, if known. -
:content-length
The number of bytes in the request body, if known. -
:character-encoding
The name of the character encoding used in the request body, if known.
The response map is created by the handler, and contains three keys:
-
:status
The HTTP status code, such as 200, 302, 404 etc. -
:headers
A Clojure map of HTTP header names to header values. These values may either be strings, in which case one name/value header will be sent in the HTTP response, or a collection of strings, in which case a name/value header will be sent for each value. -
:body
A representation of the response body, if a response body is appropriate for the response's status code. The body can be one of four types:-
String
The body is sent directly to the client. -
ISeq
Each element of the seq is sent to the client as a string. -
File
The contents of the referenced file is sent to the client. -
InputStream
The contents of the stream is sent to the client. When the stream is exhausted, the stream is closed.
-
Middleware are higher-level functions that add additional functionality to handlers. The first argument of a middleware function should be a handler, and its return value should be a new handler function that will call the original handler.
Here is a simple example:
(defn wrap-content-type [handler content-type]
(fn [request]
(let [response (handler request)]
(assoc-in response [:headers "Content-Type"] content-type))))
This middleware function adds a "Content-Type" header to every response generated by the handler. It will only work with synchronous handlers, but we can extend it to support both synchronous and asynchronous handlers:
(defn content-type-response [response content-type]
(assoc-in response [:headers "Content-Type"] content-type))
(defn wrap-content-type [handler content-type]
(fn
([request]
(-> (handler request) (content-type-response content-type)))
([request respond raise]
(handler request #(respond (content-type-response % content-type)) raise))))
Notice that we factored out the common code that changes the response into its own function. By convention, if wrap-foo
is our middleware function, then foo-request
and foo-response
are helper functions that operate on the request and response.
Once this middleware is written, it can be applied to a handler:
(def app
(wrap-content-type handler "text/html"))
This defines a new handler, app
that consists of the handler handler
with the wrap-content-type
middleware applied.
The threading macro (->
) can be used to chain middleware together:
(def app
(-> handler
(wrap-content-type "text/html")
(wrap-keyword-params)
(wrap-params)))
Middleware is used often in Ring, and is used to provide much of its functionality beyond handling raw HTTP requests. Parameters, sessions, and file uploading are all handled by middleware in the Ring standard library.
Note: middleware runs from bottom to top, and if any middleware creates a response, that will short-circuit and the middleware above will not be called.