URI Prefix Middleware for Clojure Ring


So I’ve been work­ing with Clojure webapps again lately and I noticed how I keep for­get­ting what mid­dle­ware is, what it does, and how to use it. This is a shame because it’s actu­ally not very com­pli­cated and it’s quite use­ful once you get it. So I’m going to place here a write-up on mid­dle­ware and an exam­ple of its use.

Any­way, mid­dle­ware is largely a part of the Ring library which is the foun­da­tion of almost every Clo­jure webapp or webapp frame­works avail­able. Ring acts as the inter­face between var­i­ous web servers which Clo­jure can use (mainly Java servlets) and higher level frame­works and libraries such as Noir or Moustache. Ring deals with the work of con­vert­ing http requests into a form which Clo­jure pro­grams can use, hand­ing them off to a han­dler func­tion in the Clo­jure webapp, con­vert­ing the return value of the han­dler to a proper http respon­se, and pass­ing that response to the server so that it can be returned over the Inter­net. What hap­pens in the han­dler depends entirely on either on your spe­cific frame­work or your appli­ca­tion if you don’t use a framework.1

There are three things to be wor­ried about here:

This is all well and good but what does it have to do with mid­dle­ware? Well, some­times you want to mod­ify the way requests or responses are dealt with,2 with­out muck­ing with the inter­nals of the han­dler. This might be because the of way your frame­work rewrites or dis­patches URIs or it might just be the sim­plest place to insert some func­tion­al­i­ty. Either way, mid­dle­ware pro­vides a sim­ple way to do this by sim­ply wrapping the handler. A mid­dle­ware func­tion is a func­tion which receives a han­dler func­tion as its first argu­ment and any num­ber of fur­ther argu­ments, and returns a dif­fer­ent han­dler func­tion as its return val­ue. So for a triv­ial example:

(defn wrap-example-middleware [handler]
  (fn [request]
    (handler request)))

This might be applied by wrap­ping a han­dler func­tion before pass­ing it to ring:

(defservice (wrap-example-middleware handler))

Now that was a triv­ial exam­ple and lit­er­ally did noth­ing so I’ll show you an exam­ple I cre­ated today.

One piece of func­tion­al­ity I often find con­spic­u­ously miss­ing in webapp frame­works is the abil­ity to place the entire web appli­ca­tion under a URI pre­fix. So for exam­ple, if I wanted to host sev­eral webapps under the same domain name I could place them under “http://example.com/app1”, “http://example.com/app2”, etc, with­out embed­ding the pre­fix into the appli­ca­tion’s own rout­ing log­ic. See­ing as I only have one server and a whole bunch of projects I’m work­ing on at any one moment, this is a very use­ful fea­ture for me. So for­tu­nate­ly, while the web frame­work Noir, which I’m using at the moment does­n’t sup­port this, mid­dle­ware makes it sim­ple to add:

(defn wrap-uri-prefix [handler prefix]
  (fn [request]
    (handler (assoc request
               :uri (string/replace-first (:uri request) 
                                      (re-pattern (str "^" prefix "/?"))
				      "/")))))

This func­tion receives a han­dler func­tion and a pre­fix string, and returns a han­dler which removes the pre­fix from incom­ing URIs before doing any­thing else. This way the appli­ca­tion can dis­patch on “/home” or “/users” and ignore the “/ap­pli­ca­tion-­foo” pre­fix on all the requests it’s receiv­ing. Notice that a request object is sim­ply a Clo­jure map with entries cor­re­spond­ing to http request fields. The :uri field is replaced before the request is passed to the han­dler.

Now, this isn’t quite com­plete because even though we’ve dealt with incom­ing requests, we haven’t yet dealt with links in the appli­ca­tion’s html or with redi­rects, both of which need the pre­fix to be added to them for the appli­ca­tion to work cor­rect­ly. Now, links are tricky here because they don’t actu­ally exist in the request or response objects. They exist in the html in the response object, but pars­ing and edit­ing the html in each response is the wrong the way to accom­plish this. For­tu­nately for me, I’m using the Hiccup html gen­er­a­tion library pro­vides a “with­-base-url” macro which adds a pre­fix to all links gen­er­ated by the library within its scope, so I can eas­ily avoid address­ing this par­tic­u­lar prob­lem.

I still need to work a lit­tle for redi­rects though. To deal with the­se, I first look only for redi­rects with a response sta­tus of 30* (redi­rect) and then replace the “Lo­ca­tion” head­er, which con­tains the redi­rect URI, with a URI with the pre­fix added. The final code looks like this:

(ns application.uri-prefix
   (:require [noir.server :as server]
             [hiccup.core :as hiccup]))

(defn wrap-uri-prefix [handler prefix]
  (fn [request]                             ; start defining function to return
    (hiccup/with-base-url prefix            ; set prefix for generated links
      (let [response (handler (assoc request
                                :uri (string/replace-first (:uri request) 
                                                       (re-pattern (str "^" prefix "/?"))
				                       "/")))]
        (if (<= 300 (:status response) 308) ; Only rewrite redirects
          (assoc response
        :headers (assoc (:headers response)
                     "Location" (string/replace-first (get-in response [:headers "Location"])
	                                              #"^/"
					              (str prefix "/"))))
          response)))))

(server/add-middleware wrap-uri-prefix prefix)   ;noir specific function to add middleware

So there you have it. Mid­dle­ware and how to use it.

  1. Generally there is a dispatching function which looks at the incoming URI and chooses another sub-handler to based on it. So, “http://example.com/home” would be handled by one function and “http://example.com/users” would be handled by another. 
  2. Such as causing the app to log each request or rewrite URIs. 

Last update: 02/12/2011

blog comments powered by Disqus