We created Pedestal as a web framework. At the base, every web framework must solve certain common problems:
Interpret incoming requests
Dispatch to application code
Produce a well-formed response
There is an unstated requirement that these all work in a variety of deployment environments:
And with a variety of network connectors:
One more dimension of complexity arises from the relevant styles of application we want to support:
API + SPA
Solving these problems led us to an architecture that is based on interceptors, the context map, and an adaptor to the HTTP connector.
In their original form, interceptors were a way to reify the callstack of request processing. That is to say, what would normally be a layered jawbreaker of function closures became a sequence of data structures. This model has allowed us to extract an increasing amount of logic from the core framework into interceptors. For example, routing is usually implemented as a core function of a web framework. Because an interceptor can enqueue more interceptors to execute, Pedestal implements routing as an ordinary interceptor.
Over time, we have moved most of the functionality of a typical web framework into interceptors:
Request body parsing
Parsing query parameters
Assigning content type to the response
Application logic is also implemented in interceptors.
Interceptors are called in core.async go blocks, so one interceptor may be called on a different thread than the next. All bindings will be conveyed to each interceptor.
An interceptor may return a channel instead of the context map. In that case, the channel is treated like a promise to deliver the context map in the future. Once the channel delivers the context map, the chain executor closes the channel.
The key protocol for interceptors is:
pedestal-service library includes a large set of interceptors
that are specialized for HTTP request handling.
See the following namespaces for stock interceptors:
See the following namespaces for routing interceptors:
Interceptors expect certain keys to be present in the context map. These context bindings are part of the contract between provider and interceptors.
Along with moving core logic into interceptors, we moved the HTTP connection handling out of interceptor processing to create an interface for the chain provider.
The chain provider sets up the initial context and queue of interceptors. It starts execution.
Pedestal includes a servlet chain provider out of the box. It connects
any servlet container to an interceptor chain. The
function orchestrates this work. This is strictly a convenience
function that takes a service map of everything
needed to run an HTTP service.
It is possible to create other chain providers. The fast-pedestal sample shows how to do this with Jetty.
See the following namespaces for the HTTP chain provider:
The servlet chain provider immediately works with every HTTP server that works with servlets. This allows many deployment scenarios.
Sometimes it is advantageous to work directly with a server by implementing a custom chain provider.
The servlet chain provider (and main interface to network connectors) is in: