The Functional Elegance of Ring Middleware

Higher-order functions are to me the most awe-inspiring feature of functional programming languages. However, like many incredibly elegant concepts, its greatness is not immediately obvious (recursion is another example that comes to mind). When I was exclusively working with imperative programming languages and came across the definition of higher-order functions, I thought the idea was in part trivial and in part irrelevant theoretical nonsense.

[…] Higher-order functions […] are functions which do at least one of the following:

  • take one or more functions as an input
  • output a function.

Wikipedia

The first part, I could relate to. This is like passing a function pointer in C or an anonymous inner class in Java as e.g. event handlers. In both cases the syntax is so cumbersome that is feels like honest hard work, not like an awesome technique based on theory.

The second part – output a function – why would I want that? The typical examples are as helpful as fibonacci numbers are for explaining recursion to someone who is biased towards a narrow notion of practicality. They often go look something like this:

(defn plus-x [x]
  (fn [y] (+ x y)))

(def plus2 (plus-x 2))

(plus2 3)
=> 5

The function plus-x takes a parameter x and returns a function that takes a parameter y and returns x + y.

(If you didn’t believe me about the cumbersome syntax, here is the above example translated into Java. Related: Execution in the Kingdom of Nouns)

I’m telling you this, because I want to highlight a particularly elegant and practical example of higher-order functions: Ring middleware. Ring is a Clojure library for writing web apps. It gives you an abstraction on top of HTTP similar to Rack for Ruby or WSGI for Python. Higher-level frameworks/libraries such as Compojure, Moustache, or Sandbar are built on top of Ring.

A simple “Hello, World” with Ring looks like this (from the README):

(use 'ring.adapter.jetty)

(defn app [req]
  {:status  200
   :headers {"Content-Type" "text/html"}
   :body    "Hello World from Ring"})

(run-jetty app {:port 8080})

A ring handler (app in the example) is simply a function that takes a map representing the incoming request and returns a map representing the response. Such a handler can be given to an adapter (Jetty in this case) that deals with the actual HTTP connection and calls the handler function. Thus the adapter is a higher-order function of the kind I once thought to be trivial.

If we want to serve static files with this app, we can add middleware that wraps our app to look for requests to files in a given directory:

(use 'ring.middleware.file 'ring.adapter.jetty)

(defn hello [req]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "Hello World"})

(def app (wrap-file hello "public"))

(run-jetty app {:port 8080})

Here, we have a higher-order function of the second kind: wrap-file. hello is a handler function in its own right – it is equivalent to app in the previous example – but we do not pass it the the adapter directly. The result of wrap-file is a new handler function, it has to be, otherwise we couldn’t pass it to the adapter.

The implementation of the wrapper is simple and clean:

(defn wrap-file
  [app #^String root-path]
  (ensure-dir root-path)
  (fn [req]
    (if-not (= :get (:request-method req))
      (app req)
      (let [path (.substring (codec/url-decode (:uri req)) 1)]
        (or
          (response/file-response path
            {:root root-path :index-files? true :html-files? true})
          (app req))))))

First the function checks if the the directory is actually there by calling ensure-dir. The rest of the code in wrap-file is building the resulting handler function. If the request method is not GET, the inner handler is called. Otherwise, we extract the path and try to make a response out of that using file-response. If that returns nil, the inner handler is called.

Now, this is all nice and well, but the awesome elegance of this approach to middleware becomes apparent, when we realize that wrappers can be wrapped around other wrappers in arbitrary numbers:

(def app (wrap-params (wrap-file-info (wrap-file hello "public"))))

The resulting handler has a wrapper that makes the request parameters easier to process and one that adds content-type headers to the response.

As the prefix notation is not the most readable way to express such a chain of call, you would rather use the threading macro to write this:

(def app (-> hello
             (wrap-file "public")
             (wrap-file-info)
             (wrap-params)))

But that is just syntactic sugar, the beauty of this solution is possible, because it uses higher-order functions to great effect.