|
| 1 | +# Ring Spec (1.5-DRAFT) |
| 2 | + |
| 3 | +Ring is an abstraction layer for building HTTP server applications in |
| 4 | +Clojure. |
| 5 | + |
| 6 | +The specification is divided into two parts; a synchronous API, and an |
| 7 | +asynchronous API. The synchronous API is simpler, but the asynchronous |
| 8 | +API can be more performant. |
| 9 | + |
| 10 | + |
| 11 | +## 1. Synchronous API |
| 12 | + |
| 13 | +Ring is defined in terms of handlers, middleware, adapters, request |
| 14 | +maps and response maps, each of which are described below. |
| 15 | + |
| 16 | +## 1.1. Handlers |
| 17 | + |
| 18 | +Ring handlers constitute the core logic of the web application. |
| 19 | +Handlers are implemented as Clojure functions. |
| 20 | + |
| 21 | +A synchronous handler takes 1 argument, a request map, and returns a |
| 22 | +response map. |
| 23 | + |
| 24 | +```clojure |
| 25 | +(fn [request] response) |
| 26 | +``` |
| 27 | + |
| 28 | +### 1.2. Middleware |
| 29 | + |
| 30 | +Ring middlware augment the functionality of handlers. Middleware is |
| 31 | +implemented as higher-order functions that take one or more handlers |
| 32 | +and configuration options as arguments and return a new handler with |
| 33 | +the desired additional behavior. |
| 34 | + |
| 35 | +### 1.3. Adapters |
| 36 | + |
| 37 | +Ring adapters are side-effectful functions that take a handler and a |
| 38 | +map of options as arguments, and when invoked start a HTTP server. |
| 39 | + |
| 40 | +```clojure |
| 41 | +(run-adapter handler options) |
| 42 | +``` |
| 43 | + |
| 44 | +Once invoked, adapters will receive HTTP requests, parse them to |
| 45 | +construct a request map, and then invoke their handler with this |
| 46 | +request map as an argument. Once the handler response with a response |
| 47 | +map, the adapter will use it to construct and send an HTTP response to |
| 48 | +the client. |
| 49 | + |
| 50 | +### 1.4. Request Maps |
| 51 | + |
| 52 | +A Ring request map represents a HTTP request, and contains the |
| 53 | +following keys. Any key not marked as **required** may be omitted. Keys |
| 54 | +marked as **deprecated** are there only for backward compatibility with |
| 55 | +earlier versions of the specification. |
| 56 | + |
| 57 | +| Key | Type | Required | Deprecated | |
| 58 | +| ------------------- | ---------------------------------- | -------- | ---------- | |
| 59 | +|`:body` |`java.io.InputStream` | | | |
| 60 | +|`:character-encoding`|`String` | | Yes | |
| 61 | +|`:content-length` |`String` | | Yes | |
| 62 | +|`:content-type` |`String` | | Yes | |
| 63 | +|`:headers` |`{String String}` | Yes | | |
| 64 | +|`:protocol` |`String` | Yes | | |
| 65 | +|`:query-string` |`String` | | | |
| 66 | +|`:remote-addr` |`String` | Yes | | |
| 67 | +|`:request-method` |`Keyword` | Yes | | |
| 68 | +|`:scheme` |`Keyword` | Yes | | |
| 69 | +|`:server-name` |`String` | Yes | | |
| 70 | +|`:server-port` |`Integer` | Yes | | |
| 71 | +|`:ssl-client-cert` |`java.security.cert.X509Certificate`| | | |
| 72 | +|`:uri` |`String` | Yes | | |
| 73 | + |
| 74 | +#### :body |
| 75 | + |
| 76 | +An `InputStream` for the request body, if one is present. |
| 77 | + |
| 78 | +#### :character-encoding [DEPRECATED] |
| 79 | + |
| 80 | +Equivalent to the character encoding specified in the `Content-Type` |
| 81 | +header. Deprecated key. |
| 82 | + |
| 83 | +#### :content-length [DEPRECATED] |
| 84 | + |
| 85 | +Equivalent to the `Content-Length` header, converted to an integer. |
| 86 | +Deprecated key. |
| 87 | + |
| 88 | +#### :content-type [DEPRECATED] |
| 89 | + |
| 90 | +Equivalent to the media type in the `Content-Type` header. Deprecated |
| 91 | +key. |
| 92 | + |
| 93 | +#### :headers |
| 94 | + |
| 95 | +A Clojure map of lowercased header name strings to corresponding |
| 96 | +header value strings. |
| 97 | + |
| 98 | +Where there are multiple headers with the same name, the adapter must |
| 99 | +concatenate the values into a single string, using the ASCII `,` |
| 100 | +character as a delimiter. |
| 101 | + |
| 102 | +The exception to this is the `cookie` header, which should instead use |
| 103 | +the ASCII `;` character as a delimiter. |
| 104 | + |
| 105 | +#### :protocol |
| 106 | + |
| 107 | +The protocol the request was made with, e.g. "HTTP/1.1". |
| 108 | + |
| 109 | +#### :query-string |
| 110 | + |
| 111 | +The query segment of the URI in the HTTP request. This includes |
| 112 | +everything after the `?` character, but excludes the `?` itself. |
| 113 | + |
| 114 | +#### :remote-addr |
| 115 | + |
| 116 | +The IP address of the client or the last proxy that sent the request. |
| 117 | + |
| 118 | +#### :request-method |
| 119 | + |
| 120 | +The HTTP request method. Must be a lowercase keyword corresponding to |
| 121 | +a HTTP request method, such as `:get` or `:post`. |
| 122 | + |
| 123 | +#### :scheme |
| 124 | + |
| 125 | +The transport protocol denoted in the scheme of the request URL. Must be |
| 126 | +either: `:http`, `:https`, `:ws` or `:wss`. |
| 127 | + |
| 128 | +#### :server-name |
| 129 | + |
| 130 | +The resolved server name, or the server IP address, as a string. |
| 131 | + |
| 132 | +#### :server-port |
| 133 | + |
| 134 | +The port on which the request is being handled. |
| 135 | + |
| 136 | +#### :ssl-client-cert |
| 137 | + |
| 138 | +The SSL client certificate, if supplied. |
| 139 | + |
| 140 | +#### :uri |
| 141 | + |
| 142 | +The absolute path of the URI in the HTTP request. Must start with a `/`. |
| 143 | + |
| 144 | +### 1.5. Response Maps |
| 145 | + |
| 146 | +A Ring response map represents a HTTP response, and contains the |
| 147 | +following keys. Any key not marked as **required** may be omitted. |
| 148 | + |
| 149 | +| Key | Type | Required | |
| 150 | +| -------- | ------------------------------------------ | -------- | |
| 151 | +|`:body` |`ring.core.protocols/StreamableResponseBody`| | |
| 152 | +|`:headers`|`{String String}` or `{String [String]}` | Yes | |
| 153 | +|`:status` |`Integer` | Yes | |
| 154 | + |
| 155 | +#### :body |
| 156 | + |
| 157 | +A representation of the request body that must satisfy the |
| 158 | +`ring.core.protocols/StreamableResponseBody` protocol. |
| 159 | + |
| 160 | +```clojure |
| 161 | +(defprotocol StreamableResponseBody |
| 162 | + (write-body-to-stream [body response output-stream])) |
| 163 | +``` |
| 164 | + |
| 165 | +#### :headers |
| 166 | + |
| 167 | +A Clojure map of lowercased header name strings to either a string or |
| 168 | +a vector of strings that correspond to the header value or values. |
| 169 | + |
| 170 | +#### :status |
| 171 | + |
| 172 | +The HTTP status code. Must be greater than or equal to 100, and less |
| 173 | +than or equal to 599. |
| 174 | + |
| 175 | + |
| 176 | +## 2. Asynchronous API |
| 177 | + |
| 178 | +The asynchronous API builds upon the synchronous API. The differences |
| 179 | +between the two APIs are described below. |
| 180 | + |
| 181 | +### 2.1. Handlers |
| 182 | + |
| 183 | +An asynchronous handler takes 3 arguments: a request map, a callback |
| 184 | +function for sending a response and a callback function for raising an |
| 185 | +exception. The response callback takes a response map as its |
| 186 | +argument. The exception callback takes an exception as its |
| 187 | +argument. The return value of the function is ignored. |
| 188 | + |
| 189 | +```clojure |
| 190 | +(fn [request respond raise] |
| 191 | + (respond response)) |
| 192 | +``` |
| 193 | + |
| 194 | +```clojure |
| 195 | +(fn [request respond raise] |
| 196 | + (raise exception)) |
| 197 | +``` |
| 198 | + |
| 199 | +A handler function may simultaneously support synchronous and |
| 200 | +asynchronous behavior by accepting both arities. |
| 201 | + |
| 202 | +```clojure |
| 203 | +(fn |
| 204 | + ([request] |
| 205 | + response) |
| 206 | + ([request respond raise] |
| 207 | + (respond response))) |
| 208 | +``` |
| 209 | + |
| 210 | +### 2.2. Adapters |
| 211 | + |
| 212 | +An adapter may support synchronous handlers, or asynchronous handlers, |
| 213 | +or both. If it supports both, it should have an option to specify |
| 214 | +which one to use at the time it is invoked. |
| 215 | + |
| 216 | +For example: |
| 217 | + |
| 218 | +```clojure |
| 219 | +(run-adapter handler {:async? true}) |
| 220 | +``` |
| 221 | + |
| 222 | +## 3. Websockets |
| 223 | + |
| 224 | +A HTTP request can be promoted into a websocket by means of an |
| 225 | +"upgrade" header. |
| 226 | + |
| 227 | +In this situation, a Ring handler may choose to respond with a |
| 228 | +websocket response instead of a HTTP response. |
| 229 | + |
| 230 | +### 3.1. Websocket Responses |
| 231 | + |
| 232 | +A websocket response is a map that has the `:ring.websocket/listener` |
| 233 | +key, which maps to a websocket listener, described in section 3.2. |
| 234 | + |
| 235 | +```clojure |
| 236 | +(fn [request] |
| 237 | + #:ring.websocket{:listener websocket-listener}) |
| 238 | +``` |
| 239 | + |
| 240 | +A websocket response may be returned from a synchronous listener, or |
| 241 | +via the response callback of an asynchronous listener. |
| 242 | + |
| 243 | +```clojure |
| 244 | +(fn [request respond raise] |
| 245 | + (respond #:ring.websocket{:listener websocket-listener})) |
| 246 | +``` |
| 247 | + |
| 248 | +### 3.2. Websocket Listeners |
| 249 | + |
| 250 | +A websocket listener must satisfy the `ring.websocket/Listener` |
| 251 | +protocol: |
| 252 | + |
| 253 | +```clojure |
| 254 | +(defprotocol Listener |
| 255 | + (on-open [listener socket]) |
| 256 | + (on-message [listener socket message]) |
| 257 | + (on-pong [listener socket data]) |
| 258 | + (on-error [listener socket throwable]) |
| 259 | + (on-close [listener socket code reason])) |
| 260 | +``` |
| 261 | + |
| 262 | +The arguments are described as follows: |
| 263 | + |
| 264 | +* `socket` - described in section 3.3. |
| 265 | +* `message` - a `String` or `java.nio.ByteBuffer` containing a message |
| 266 | +* `data` - a `java.nio.ByteBuffer` containing pong data |
| 267 | +* `throwable` - an error inheriting from `java.lang.Throwable` |
| 268 | +* `code` - an integer from 1000 to 4999 |
| 269 | +* `reason` - a string describing the reason for closing the socket |
| 270 | + |
| 271 | +### 3.3. Websocket Sockets |
| 272 | + |
| 273 | +A socket must satisfy the `ring.websocket/Socket` protocol: |
| 274 | + |
| 275 | +```clojure |
| 276 | +(defprotocol Socket |
| 277 | + (-send [socket message]) |
| 278 | + (-ping [socket data]) |
| 279 | + (-pong [socket data]) |
| 280 | + (-close [socket status reason])) |
| 281 | +``` |
| 282 | + |
| 283 | +The types of the arguments are the same as those described for the |
| 284 | +`Listener` protocol. |
| 285 | + |
| 286 | +It *may* optionally satisfy the `ring.websocket/AsyncSocket` protocol: |
| 287 | + |
| 288 | +```clojure |
| 289 | +(defprotocol AsyncSocket |
| 290 | + (-send-async [socket message callback])) |
| 291 | +``` |
0 commit comments