Pedestal is data driven, and the routing information starts in one form (as data) and is then
transformed in a couple of stages to reach a final result that can be used to route incoming requests.
The start is the route specification.
Pedestal has three built-in formats for route specification:
table, terse, and verbose. Table is the newest format
and is the preferred format; the others are maintained for compatibility.
For each of the built-in formats, there is a function that can convert that specific format
into a routing fragment. One or more fragments are then combined to form the routing table.
Pedestal will determine which function to invoke based on the kind of data: set, vector, or map:
flowchart TD
Tab["#{} set"] --> tabr
tabr([table/table-routes]) --> frag
Terse["[] vector"] --> terser
terser([terse/terse-routes]) --> frag
Verb["{}] map"] --> verbr
verbr([terse/map-routes->vec-routes]) --> frag
frag[Routing Fragment] --> expand
expand([expand-routes]) --> table
cons[Router Constructor Fn] --> make-fn
table[Routing Table] --> make-fn
make-fn([router]) --> intc
intc[Routing Interceptor]
Route specifications are the data values that spell out the possible routes.
These are normalized and expanded and combined into a routing table. In effect, a routing specification
is a way to avoid calling any of the specific functions, such as
api:table-routes[ns=io.pedestal.http.route.table]. The end result is equivalent.
The functions convert their inputs into routing fragments which are then combined into a routing table by api:expand-routes[].
A Router Constructor function is passed the routing table, and applies a specific strategy to match incoming requests
to the provided routes; there are a few different constructor functions, each
with different limitations and trade-offs. The default is Sawtooth (api:router[ns=io.pedestal.http.route.sawtooth]).
The router constructor returns a Routing function encapsulating the routing table and the strategy.
The routing function is passed the request and returns a vector of two values on success: the matching route, then the map
of path parameters. On failure, the routing function returns nil.
The routing interceptor is built around the routing function; its job
it to combine the routing function and the current request to identify the route, if any,
that matches the request. When routing is successful, the interceptor updates the
request-map.adoc, and queues up route-specific interceptors to handle the request.
|
There are actually two different implementations of the interceptor; the production one is used
in the normal case, where the routing table is an actual value. During development,
the routing table may be a function that returns the routing table; in which case, the routing
function is rebuilt on every request.
|
Generally, all of this is automatic; an application provides routes to
the api:with-routes[ns=io.pedestal.connector] function, or provides a route specification in the :io.pedestal.http/route key
of the service-map.adoc (and perhaps a value for :io.pedestal.http/router) and a routing
interceptor is automatically created.