The interceptor infrastructure supports asynchronous processing via core.async channels. Any interceptor that returns a channel will initiate asynchronous behavior. When a new context is later written to the channel, the interceptor path will continue processing with this new context in place of the original context.
This allows long-running work to occur without blocking a Web server thread.
Here is a synchronous handler that needs to wait for something to happen:
(interceptors/defhandler takes-time [request]
(ring-resp/response (lengthy-computation request)))
(defroutes routes
[[["/takes-time" {:get takes-time}]]])
Because this handler is synchronous, requests for /takes-time
will
block until the call to lengthy-computation
completes, stopping the
Web server thread from handling other work.
This code can be rewritten so that it releases the Web server thread, as shown below:
(interceptors/defbefore takes-time
[context]
(let [channel (chan)]
(go
(let [result (lengthy-computation (:request context))
new-context (assoc context :response (ring-resp/response result))]
(>! channel new-context)))
channel))
(defroutes routes
[[["/takes-time" {:get takes-time}]]])
The go
block allows work to take place asynchronously while the
thread is released. When that work is complete, the request will
complete, and a response will be delivered to the client.
This facility allows a single value to be placed on a channel per interceptor; for more extensive use of channels for SSE, see Server-Sent Events.
For more about streaming, see Streaming Responses.