Interceptors
See the guide
This is the detailed interceptor reference; if you are new to Pedestal you may want to start with the What Is An Interceptor Guide. |
Interceptors are the basic unit of work in Pedestal. The core library provides interceptors that are generally useful for creating HTTP web services. Applications augment those with their own logic to handle.
An interceptor is a pair of unary functions [1] An interceptor must provide at least one of :enter, :leave and :error. Each function is called with a context map and must return either a context map or a core.async channel that will deliver a context map; the latter case triggers asynchronous request processing.
Pedestal calls the :enter function on the way "in" to handling a request. It calls the :leave function on the way back "out". This is shown here for a single interceptor:
Either the :enter or :leave function may be omitted without harm.
Logically speaking, interceptors form a queue. During the :enter phase, the next interceptor is popped off the queue, pushed onto the leave stack, and its :enter function, if any, is executed.
Once the handler (or other interceptor) adds a :response to the context, the chain logic switches to :leave mode: it pops interceptors off the leave stack and invokes the :leave function, if any.
Because it’s the leave stack the :leave functions are invoked in the opposite order from the :enter functions.
Both the queue and the stack reside in the context map. Since interceptors can modify the context map, that means they can change the plan of execution for the rest of the request! Interceptors are allowed to enqueue more interceptors to be called, or they can terminate the request.
This process, of running all the interceptor :enter functions, then running the interceptor :leave functions, is called executing the interceptor chain.
Context Bindings
Interceptors expect certain keys to be present in the context map. These context bindings are part of the contract between Pedestal connector and interceptors.
The most important keys are :request, which holds the request map, and :response, which (once added to the context) holds the response map.
Technically, the interceptor pipeline doesn’t know about :request and :response, because the interceptor pipeline is not specically purposed with HTTP processing; the Servlet Interceptor adds these details. |
Further keys are described in the context map reference.
Interceptor Return Values
Interceptor functions must return the context passed to it, or a modification of that context.
Returning nil
will cause an internal server error.
An :enter or :leave function may return a context map directly. In this case, processing continues with the next interceptor.
If the interceptor is expected to take a long time to return a result, it may instead return a core.async channel. Pedestal will yield the request processing thread back to the network adapter (so that it can process other incoming requests) and wait for a value to be produced.
Only one value will be consumed from the returned channel, and the value must be a context map.
Request processing continues once the channel delivers the context map.
Chaining With Async Interceptors
Any interceptor downstream of an asynchronous interceptor will be executed in the core.async dispatch thread pool. This can be problematic if any later interceptor or handler performs any blocking I/O, as the thread pool is a fixed size. Generally speaking, if any interceptor is asynchronous, all following non-trivial interceptors should also be asynchronous. Trivial interceptors do short computations or make changes to the context map; they do not perform any I/O or other operations that could block the thread they execute on, such as any file or socket I/O. When an interceptor returns a channel, the request processing thread can be returned to the servlet container. This may allow another pending request to be processed while the initial request is parked, waiting for the channel returned by an interceptor to convey the new context map. |
IntoInterceptor
The protocol
IntoInterceptor
represents anything that can be used as an interceptor.
Pedestal extends that protocol to the following types:
Type | Interpretation |
---|---|
Map |
The :enter, :leave, :error, and :name keys are used directly. |
Function |
The function is interpreted as a handler. |
List |
The list is evaluated and its result is used as an interceptor. [2] Support for List is deprecated. |
Cons |
Same as List (and also deprecated) |
Symbol |
The symbol is resolved and its target is converted to an interceptor. |
Var |
The var is de-referenced and its value is converted to an interceptor. |
Most of these cases are provided to make routing syntax easier, or reflect earlier attempts to improve live reloading at the REPL.
Applications should mainly use the map form as shown in the earlier examples when defining interceptors for routing purposes.
Manipulating the interceptor queue
The queue of interceptors remaining to execute is held in the context map. This means that an interceptor can enqueue other interceptors to be executed. In fact, this is exactly how routing works, the router is an interceptor that matches information from incoming requests to specific routes, then enqueues interceptors for that specific route.
Use
enqueue
to push more interceptors onto the queue.
Use
terminate
if processing should not continue - though normally, this is accomplished
by attaching a :response map (the response map) to the context map.
Interceptor Records
Interceptors that are explicitly enqueued by the application must
be defined using the
This is not necessary when constructing interceptors used in routing because interceptor representations are transformed to Interceptor records during route expansion. |
It’s worth noting that when an interceptor queues additional interceptors for execution, they execute after all interceptors already in the queue (not immediately after the interceptor that modified the queue). This means you could, for example, put a routing interceptor first in the queue, then a few interceptors that provide behavior common to all routes, and those common interceptors will run before any route-specific interceptors.
Handlers
A handler function is a special case of an interceptor. Pedestal treats the handler as a function that accepts a request map parameter, and returns a response map result.
A handler does not have access to the full execution context, therefore, it cannot manipulate the interceptor queue.
Because a handler takes one kind of thing (request) and returns a different kind of thing (response), it can only be used in the last position of an interceptor stack.
Handlers return a response map; alternately, an asynchronous handler should return a channel that conveys the response map.
Interceptors should always have names [3]. When a handler function is converted to an Interceptor, Pedestal will check to see if the function has :name metadata and use that as the Interceptor’s name.
Failing that, Pedestal will generate an interceptor name from the function’s class name; this does not always provide ideal results.
When using Terse Syntax or Verbose Syntax to define routes,
the route name is used as a default on the handler interceptor, if it does not have its
own name. This can cause interceptors to have different names after upgrading to 0.8.0.
If this is a problem, the default name behavior can be disabled;
see |
Provide a keyword :name metadata on your function. |
Example:
(def version-handler
^{:name ::version} (1)
(fn [_request] {:status 200 :body "0.3.7"}))
; Later, in routes
["/api/version" :get version-handler] (2)
1 | This is metadata applied to the function itself. If using defn , the metadata would be applied
to the Var. |
2 | Prior to release 0.8.0, this would be an error because there would be no route name; now the route name will be the same as the interceptor’s name. |
Error Handling
Pedestal supports defining interceptor-specific error handlers via the :error key. Refer to the Error Handling reference for more details.
Pedestal Interceptors
The io.pedestal/pedestal.service library includes a large set of interceptors
that are specialized for HTTP request handling.
Many of these interceptors are automatically added to the
interceptor queue by the default-interceptors
function,
using information from the service map.
Routing-related interceptors are provided by the io.pedestal/pedestal.route library: