Pedestal

An overview of the Pedestal libraries (http://pedestal.io)

First things first

These are my notes based on the following video, and my understanding of it and other materials I've come across on Pedestal. This video is a must watch (until a newer, more updated one appears - something I'll try to keep track of too.) I've created this document solely to aid my own understanding of Pedestal and not at all to make any kind of authoritative or reference document.

Background

Pedestal is a set of libraries for web-development with Clojure.

Pedestal and Ring

Pedestal is more than Ring by itself. Ring's middlewares and handlers work synchronously. Pedestal abstracts middlewares/handlers as interceptors, which function independently/asynchronously. Interceptors work on contexts, without knowing what interceptors came before or will come later.

It's very interesting (and useful) to note that Pedestal is strictly compatible with Ring on handlers. Which means that Ring's handlers can be freely used inside of Pedestal apps!

Pedestal: Terms

Routes, child-routes
URI paths handed off to the right sub-system (of functions)
Interceptors
Functions that transform requests and responses

Routes

Routing table

Pedestal uses what it calls a routing table for declaring paths and handlers for servicing HTTP requests. A routing table is a sequence of routes, where each route is a map specifying a URI-path to match along with the HTTP action (the HTTP verbs), and associated handler-functions.

Routes

Routes (using the minimalistic form) have the following anatomy


  ["/hello" {:get hello-handler}]
  ;; [path-pattern handler-map]

path-pattern matches the path for appropriate action as declared inside the handler-map~/ /~handler-map is a dictionary that maps from http verbs to handler functions.

A more complete route example (specifying the optional context parameters) would look like the following


  [[:hello-world :http "hello.example.com"
    ["/hello" {:get hello-handler}]]]

In the above example, host-name matching from the HTTP (1.1+) header will happen for hello.example.com for the path /hello, and the request will be handled by the hello-handler function. But this will not match an HTTPS request, and will also never match when the Host: header does not equal hello.example.com. The one prior to this example, with the optional directives skipped, would mean a loosened constraint on both the Host: header and the protocol.

Routes can further have children. So, you can set the base context path with the parent route.

Hierarchical routes


  ["/"
   ["/child-route" {:get get-handler :post post-handler}]]

In this case, the handler-map has been skipped for the root path pattern /. So, you get an idea that you can declare handlers for child routes and not have a (default) handler for the root path pattern.

So, an entire set of routes can be defined as such


  (defroutes routes
    [[["/" {:get root-handler}
       ["/hello" {:get hello-get-handler :post hello-post-handler}]]]]

A more complete routing table example

Or, to take a longer example, without much of the (mostly understandable) context


  (defroutes routes
    ;; Defines "/" and "/about" routes with their associated :get handlers.
    ;; The interceptors defined after the verb map (e.g., {:get home-page}
    ;; apply to / and its children (/about).
    [[["/" {:get home-page}
       ;; Set the content-type to 'html' if none prescribed/requested
       ^:interceptors [(body-params/body-params) bootstrap/html-body]
       ["/about" {:get about-page}]
       ["/hello" ^:interceptors [capitalize-name] {:get hello-world}]
       ["/request" {:any handle-dump}]]]])

This is quite nested. It's a list of list of routes. The structure is actually so


[[context routes]]

As seen above, the context represents app-name, host, port, and so on. Pedestal thus allows you to host multiple applications on a single web-server.


[[:public "example.com" public-routes]
 [:api "api.example.com" api-routes]]

The routing infrastructure as declared is also used for URL generation - a useful fact to note.

Destination interceptors

A very detailed explanation of the interceptors as used in the context of the routing table and as associated with the verbs, exists at Pedestal's Github documentation on routing. Additional notes on interceptors follow later in this document.

Small sample pedestal application



  (ns todoit.core
    (:require [io.pedestal.http.route.definition :refer [defroutes]]
              [io.pedestal.http.route :refer [router]]
              [io.pedestal.http :as http]))

  (defn hello-world [req]
    {:status 200
     :body "Hello, World"
     :headers {}})

  (defroutes routes
    [[["/"
       ["/hello" {:get hello-world}]]]])

  (def service
    {::http/interceptors [(router routes)]
     ::http/port 8080})

  (defn -main [& args]
    (-> service
        http/create-server
        http/start))

Interceptors

    Interceptors are made up of three types of functions
  • The enter functions - Inspects and annotates the request
  • The leave functions - Inspects and annotates the response
  • The error functions - If any interceptor or handler raises and exception (it allows for bubbling up)

Routers are an interceptor too - the router function takes a route table as input, and builds an interceptor.

Interceptors can even be directly placed in the route-map, attaching them to individual components (pretty flexible)

Examples


(def service {::http/interceptors [... (router routes) ...]})

Or, we can put them via var-annotations, thus


  [[
    ["/" ^:interceptors [...]
     ["/hello" {:get hello-world}]
     ["/goodbye" ...]]]]

The above will apply to all children routes. But we can associate interceptors with individual elements too


[[
["/"
^:interceptors [...] ["/hello" {:get hello-world}]]]]

OR


  [[
    ["/"
     [...] ["/hello" ^:interceptors [...] {:get hello-world}]]]]

Request and Response

Links/References