Liking cljdoc? Tell your friends :D

Routing Quick Reference

Routing is the process by which an incoming request is analyzed to determine how it will be processed by Pedestal.

Routing is based on comparing any of a number of factors from the request, including:

  • The request path (including matching wild cards in the path)

  • The HTTP method used (GET, POST, etc.)

  • The HTTP scheme used in the request

  • The name of host in the request

  • The port number

It’s natural to understand why path and method are involved in routing; less obvious for the other factors. Essentially, a single Pedestal application may host two or more concurrent applications inside the same server - incoming requests are differentiated most often by port, but can also be differentiated by the host. A somewhat common example is an outward-facing consumer application exposed outside a company’s firewall, and an inward-facing admin application that is only available inside the firewall.

Library

The library api:*[ns=io.pedestal.http.route] namespace provides the core components to express routes and construct routers. It can be used independently of the pedestal-service library.

Route Specifications vs. Routers vs. Routing Interceptor

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 (but still old - it dates back to 2016) 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 table (also known as expanded routes). Pedestal can often figure out which function to invoke based on the kind of data: set, vector, or map:

flowchart
  subgraph ExpandableRoutes/-expand-routes
  table("#{} table")-->|table/table-routes|expanded
  terse("[] terse")-->|terse/terse-routes|expanded
  map("{} verbose")-->|terse/map-routes->vec-routes|expanded
  end
  expanded(expanded routes)-->|RouterSpecification/router-spec|inter
  cons(Router Constructor)-->inter
  inter(Routing Interceptor)

Route specifications are the data values that spell out the possible routes. These are normalized and expanded into a routing table.

A Router Constructor function is provided with 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 constructor returns a api:Router[ns=io.pedestal.http.route.router] instance encapsulating the routing table and the strategy.

The routing interceptor builds on a Router to dispatch incoming requests to routes defined by the route specification. It identifies the route matching the incoming request and queues up route-specific interceptors to handle the request.

Generally, all of this is automatic; an application 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.

Route Specifications

Route specifications go through a series of transformations that results in a routing table; a routing table is a sequence of verbose routing maps.

The api:expand-routes[ns=io.pedestal.http.route] function converts any kind of specification into a routing table:

Argument to expand-routesSyntax used

Set

table-syntax.adoc - Most recent (2016), easier than terse format, recommended for new projects.

Vector

terse-syntax.adoc - improvement on the verbose syntax, with an emphasis on avoiding redundancy, but can be hard to read and write, since its deeply nested maps.

Map

verbose-syntax.adoc - oldest format.

This mapping from value type to specification type is the responsibility of the api:ExpandableRoutes[ns=io.pedestal.http.route] protocol, which is extended on Map, Set, and Vector.

ExpandableRoutes is only needed when Pedestal is provided with routes but doesn’t know what format they are in. It is also perfectly valid to build the routes explicitly, by directly calling api:table-routes[ns=io.pedestal.http.route.definition.table], and then building your own routing interceptor.

It’s not impossible that your application has specific needs that can’t be met by any of the built-in formats and, as is often the case in Pedestal. To support a new syntax, come up with a syntax that resolves to new record type that implements the ExpandableRoutes protocol.

Routing Table

The expanded routing table is a list of maps, each with the following specific structure:

(table-routes #{{:app-name :example-app
                 :scheme   :https
                 :host     "example.com"}
                ["/department/:id/employees" :get [...]
                 :route-name :org.example.app/employee-search
                 :constraints {:name  #".+"
                               :order #"(asc|desc)"}]})
=>
({:route-name :org.example.app/employee-search (1)
   :app-name   :example-app  (2)
   :path       "/department/:id/employees" (3)
   :method     :get (4)
   :scheme     :https (5)
   :host       "example.com" (6)
   :port       8080 (7)
   :interceptors [...] (8)

   :path-re #"/\Qdepartment\E/([^/]+)/\Qemployees\E" (9)
   :path-parts        ["department" :id "employees"] (10)
   :path-params       [:id] (11)
   :path-constraints  {:id "([^/]+)"} (12)
   :query-constraints {:name #".+" (13)
                       :order #"(asc|desc)"}
   })
1:route-name is required and must be a keyword; often a qualified keyword. The route name must be unique within the table.
2Optional, used for documentation only.
3Must start with a leading slash; terms with a leading : identify path parameters, or a leading * identifies a wildcard.
4HTTP method to match against, or :any to match any HTTP method.
5Optional for matching, must be :http or :https.
6Optional for matching.
7Optional for matching.
8A vector of interceptors (converted via api:IntoInterceptor[ns=io.pedestal.interceptor])
9A regular expression, generated from the path, that can match an incoming path and provide capture groups for path parameters.
10The parts of the path, as strings or keywords.
11The path parameters, in the order they appear in the path.
12Path constraints[1]
13Query constraints, which maps keywords corresponding to query parameters to regular expressions used to match the parameters.

This ficticious example defines a URI that includes an id in the request path, but has also defined query parameters constraints.

The api:routing-table[] specification exhaustively defines what is allowed in a routing table.

A path parameter will normally match a single name with in the path, delimited by / characters; an alternate form is the wildcard,, which starts with * and must come at the end of the path: ["/accounts/*ids :get …​] would provide a path parameter named :ids, and will contain anything on the URL after /accounts/, including any slashes.

Builtin Routers

Pedestal includes several Routers; this reflects not only the evolution of the Pedestal library, but also allows for different trade-offs in the algorithm used by each Router. In rare cases, an application can provide its own Router rather than use one of Pedestal’s.

When your application starts a Pedestal service with api:create-servlet[ns=io.pedestal.http] or api:create-server[ns=io.pedestal.http], Pedestal creates a router, using the following keys from the service map:

KeyMeaning

:io.pedestal.http/routes

A route specification

:io.pedestal.http/router

Key to select a router, or a function that constructs a router from a routing table

When the value of :io.pedestal.http/router is a keyword, it selects one of the built-in algorithms:

KeywordRouterPerformance

:map-tree

Map Tree (default)

Very fast

:prefix-tree

Prefix Tree

High performance, space efficient

:linear-search

Linear Search

Lowest performance

Custom Router

When the value of :io.pedestal.http/router is a function, that function is used to construct a router. The function must take one argument: the fully expanded routing table. The constructor function must return a value that satisfies the api:Router[ns=io.pedestal.http.route.router] protocol.

So the function is passed the routing table and returns a Router for those routes. The Router is supplied with the incoming request, and returns the matching route map (a verbose route map extended with extracted parameters from the path).

Routing Interceptor

The function api:router[ns=io.pedestal.http.route] is where it all comes together; this function is passed the route specification and, optionally, the router type; from that it creates the routing table, and passes that through the correct Router constructor function, obtaining at the end an interceptor that performs routing, which it returns.

During request execution, on a successful routing, the following keys are added in the context map:

Key / Key PathValue

:route

The verbose route map

[:request :path-parameters]

Path parameters extracted from the request path

In addition, additional interceptors, specific to the route, will have been scheduled for execution via the api:enqueue[ns=io.pedestal.interceptor.chain] function.

On failure, when the router does not match any route, the context key :route is set to nil.


1. Not clear why this is here.

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close