Developing at the REPL

Introduction

Exploring your application at the REPL is fundamental to Clojure development. In the context of Pedestal applications, we often want to interrogate the routing system and invoke interceptors (or handlers) in isolation. These explorations often form the basis for more robust automated tests, which will be covered in the Unit testing guide.

What You Will Learn

After reading this guide, you will be able to perform the following tasks at the REPL:

  • Start and stop the HTTP server.

  • Reload your routes without restarting the server.

  • List your application’s routes.

  • Find a route by name.

  • Test the response of a single route.

  • Test for route recognition.

  • Test url generation.

  • Invoke an interceptor or handler. [TODO]

  • Examine the interceptor stack. [TODO]

  • Connect to a running application with Socket REPL. [TODO]

  • Capture the last request and examine it at the REPL. [TODO]

Guide Assumptions

This guide assumes you have a working REPL in your Pedestal application. If you generated your application using the pedestal-service Leiningen template, this might be as simple as running lein repl at the command line. Perhaps your editor or IDE provides a facility for launching a REPL for your project. A great resource for understanding and configuring your REPL environment can be found at Lambda Island’s Ultimate Guide to Clojure REPLs.

Getting Help if You’re Stuck

include::partial$getting-help.adoc

Before We Begin

In this guide, we will develop a few simple utility functions that simplify interacting with your Pedestal application from the REPL. For the sake of having a consistent baseline setup, we’ll use a clean project for this guide. Generate a new Pedestal service using the following command:

lein new pedestal-service pedrepl; cd pedrepl

The default project template creates two files: server.clj and service.clj. We’re now ready to begin exploring the Pedestal application. Ladies and gentlemen, start your REPLs!

Starting and stopping the HTTP server

The first thing we’ll want to do from the REPL is to control the HTTP server. By default, your REPL should start in the pedrepl.server namespace. The Leiningen template generates a useful function named run-dev which will configure and start the HTTP server in development mode, preventing the HTTP server thread from being joined and locking up your REPL.

In development mode, routes are reloaded when the service.clj is re-required.

At the REPL, invoke the run-dev function to start the server:

pedrepl.server> (def serv (run-dev))

Now fire up a browser and navigate to http://localhost:8080 to see the default, "Hello, World!" message.

Within service.clj, update the home-page handler to respond with "Hello Pedestal User". Re-require/reload the service.clj, and when you reload http://localhost:8080 you’ll see the change immediately.

In dev-mode, you should never have to restart the server - changes are picked up as soon as they’re required/reloaded.

Should you ever need to stop the server, you can do so with the following invocation:

pedrepl.server> (server/stop serv)

Working with Routes

Now that we’re able to control the HTTP server, we’ll want some additional amenities. Next we’re going to add some route introspection utilities. Pedestal ships with all of the raw materials we need. Our job is simply to package these tools together into more convenient forms adapted to our specific needs.

Pedestal supports a few different route definition formats. Version 0.5.0 saw the advent of a new table format which is now the default generated by the pedestal-service Leiningen template. Let’s add a reference to the table format namespace from server.clj. Your namespace declaration should now look like this:

(ns pedrepl.server
  (:gen-class)
  (:require [io.pedestal.http :as server]
            [io.pedestal.http.route :as route]
            [io.pedestal.http.route.definition.table :refer [table-routes]] ; <-- new
            [pedrepl.service :as service]
            [clojure.java.io :as io]))

With that reference in place, we can add a new utility to print our application’s routes at the REPL:

(defn print-routes
  "Print our application's routes"
  []
  (route/print-routes (table-routes service/routes)))

Try this new function out at the REPL:

pedrepl.server> (print-routes)

[:get /about :pedrepl.service/about-page]
[:get / :pedrepl.service/home-page]
nil

This simple output lists the HTTP method, relative path, and handler function for each one of our routes. This is a great start, but as our application grows we may want to find a route by name. Let’s add another utility to do just that:

(defn named-route
  "Finds a route by name"
  [route-name]
  (->> service/routes
       table-routes
       (filter #(= route-name (:route-name %)))
       first))

Let’s test our new function:

pedrepl.server> (named-route ::service/home-page)

This function returns a map describing the home page route in greater detail than print-routes. Besides the :path and :method keys, we’re given access to the list of :interceptors that will be invoked when this route is requested. It’s not uncommon for Pedestal services to have quite a few interceptors, so the raw output from named-route can get a little unwieldy. Let’s see if we can produce some friendlier output.

Interceptors and Handlers

Interceptors are central to Pedestal applications. Not only do they provide the same pre- and post-processing of requests that Ring middleware does, but they also provide the main functionality behind every request. Everything in Pedestal is an interceptor, created by a single protocol - IntoInterceptor. When a route is compiled in Pedestal, it’s compiled with the full list of interceptors that run for that endpoint. It is often useful to see all interceptors that get run for a single endpoint. You can do that by looking through the route description or programmatically at the repl. Below is one way you might inspect and format that information.

(defn print-route
  "Prints a route and its interceptors"
  [rname]
  (letfn [(joined-by
            [s coll]
            (apply str (interpose s coll)))

          (repeat-str
            [s n]
            (apply str (repeat n s)))

          (interceptor-info
            [i]
            (let [iname  (or (:name i) "<handler>")
                  stages (joined-by
                          ","
                          (keys
                           (filter
                            (comp (complement nil?) val)
                            (dissoc i :name))))]
              (str iname " (" stages ")")))]
    (when-let [rte (named-route rname)]
      (let [{:keys [path method route-name interceptors]} rte
            name-line (str "[" method " " path " " route-name "]")]
        (joined-by
         "\n"
         (into [name-line (repeat-str "-" (count name-line))]
               (map interceptor-info interceptors)))))))

Let’s use our new utility to print the ::service/home-page route information.

pedrepl.server> (print-route ::service/home-page)

This call produces the following output:

 [:get / :pedrepl.service/home-page]
 --------------------------------
 :io.pedestal.http.body-params/body-params (:enter)
 :io.pedestal.http/html-body (:leave)
 <handler> (:enter)

Of course, this is a completely arbitrary representation of your route definition. Your own needs and aesthetic sensibilities should inform the structure of your application’s output.

Testing Route Responses

It’s not even necessary to start a server to see responses of your Pedestal service.

Pedestal ships with a response-for function that will return the response of an endpoint, exactly how it’s passed to the Servlet/Chain-Provider.

Let’s jump into the REPL:

pedrepl.server> (require '[io.pedestal.test :refer [response-for]])
nil
pedrepl.server> (def tempserv (::server/service-fn (server/create-servlet service/service)))
pedrepl.server> (response-for tempserv :get "/")

You can also use the same response-for function to test against a live server. Assuming you still have a running server from above (e.g. (def serv (run-dev))), You can do the following:

pedrepl.server> (response-for (::server/service-fn serv) :get "/")

Route Recognition and Generation

One of Pedestal’s benefits is that its routing table is bidirectional. Not only does Pedestal use the route definition to recognize routes based on request data, but it can also generate URLs to known route handlers. Let’s take a closer look at both of these capabilities in turn.

Route Recognition

When developing Pedestal applications, we often want to verify that a given HTTP verb and relative path will invoke the correct handler. Again, Pedestal provides all of the plumbing we need to answer these questions from the comfort of the REPL. Add the following function to server.clj:

(defn recognize-route
  "Verifies the requested HTTP verb and path are recognized by the router."
  [verb path]
  (route/try-routing-for (table-routes service/routes) :prefix-tree path verb))

With this function in place, we can test for route recognition from the REPL:

pedrepl.server> (recognize-route :get "/about")

=>
{:path "/about",
 :method :get,
 :path-re #"/\Qabout\E",
 :path-parts ["about"],
 :interceptors
 [{:name :io.pedestal.http.body-params/body-params,
   :enter #function[io.pedestal.interceptor.helpers/on-request/fn--8294],
   :leave nil,
   :error nil}
  {:name :io.pedestal.http/html-body,
   :enter nil,
   :leave #function[io.pedestal.interceptor.helpers/on-response/fn--8311],
   :error nil}
  {:name nil, :enter #function[io.pedestal.interceptor/eval155/fn--156/fn--157], :leave nil, :error nil}],
 :route-name :pedrepl.service/about-page,
 :path-params {},
 :io.pedestal.http.route.prefix-tree/satisfies-constraints? #function[clojure.core/constantly/fn--4614]}

Route Generation

When we generate urls during the lifetime of a request, we can simply use Pedestal’s url-for function. However, this function relies on the url-for variable which is dynamically bound during the life of a request. In order to test URL generation from the REPL, we’ll define the following utility:

(defn dev-url-for
  "Returns a url string for the named route"
  [route-name & opts]
  (let [f (route/url-for-routes (table-routes service/routes))
        defaults   {:host "localhost" :scheme :http :port 8080}
        route-opts (flatten (seq (merge defaults (apply hash-map opts))))]
    (apply f route-name route-opts)))

Let’s generate the URL for the about page:

pedrepl.server> (dev-url-for ::service/about-page)

=> "/about"

pedrepl.server> (dev-url-for ::service/about-page :absolute? true)

=> "http://localhost:8080/about"

Useful Techniques

  • Capture request

Connecting to a Running Application

Next Steps

In this guide we learned how to explore our Pedestal application from the REPL. Some of these explorations may serve as the basis for unit tests. Head over to the Unit testing guide to learn how to test the complete interceptor chain for a given route and much more.

Alternatively, you may want to learn to manage your applications stateful components (like the HTTP server) more effectively. Check out the guide on Integrating Pedestal with Component to learn how easy it can be to incorporate Stuart Sierra’s Reloaded workflow into your Pedestal development, and read Reloading at the REPL for a non-component take on how to make live changes without restarting.