Clojure Web Programming and Ring

Just some notes to myself… If you find it helpful, great! If you find it confusing, my apologies.

Ring is the lowest level integration point between a Clojure application and a web server; it’s at about the same level as Ruby’s Rack, or Python’s WSGI, or Tomcat’s AJP. However, unlike in those other languages, most Clojure web applications and frameworks don’t sit much higher than Ring, so it’s pretty important to understand how Ring works.

Warning: The Clojure code in here is very simple and pedestrian and will strike you as abnormally verbose if you already know Clojure. Clojure is one of those languages like Haskell or Perl where as your experience grows, your programs get shorter and shorter; this is called “Succinctness” when you’re experienced and somewhat implies Elegance. However, when you’re still learning a language, it is called “WTF does this mean, I give up, I’m going back to COBOL.”

Minimal Setup

Assuming you’re using Leiningen


laptop> lein new ring-play
laptop> cd ring-play

and then edit the project.clj file to include Ring; remember you can always find the latest version of a package by searching for it on Clojars.

Once you’ve edited your project.clj file, pull in the dependencies:


laptop> lein deps
Retrieving ring/ring/1.2.1/ring-1.2.1.pom from clojars
Retrieving ring/ring-core/1.2.1/ring-core-1.2.1.pom from clojars
...

And then start a REPL session:

laptop> lein repl
nREPL server started on port 52057
REPL-y 0.1.9
Clojure 1.5.1

user=>

Ring Conceptually

Technically, Ring isn’t a framework or an application, but rather a specification on how HTTP requests are to be handled by some server. Because of this, you don’t actually need to run a webserver to explore ring.

Ring specifies that a Request is processed by a Handler and returns a Response. It also specifies that there may be some Middleware involved, but that just confuses the picture by making things more complex; let’s ignore that for now.

Taking away the Clojure’isms and functional programming concepts, this actually describes pretty much all web applications… Say you want to search for this page. You open your web browser and enter http://www.google.com into the location bar, and Google’s home search page comes up.

  • The Request was for the URL “/” on the server www.google.com using the HTTP protocol.
  • The Handler was something run by Google that understood that “/” was the “home page.”
  • The Response was the HTML and Javascript that is displayed in your browser.

In pseudo-functional-programming, from Google’s perspective, there were two handlers here:

  1. One that knew that “/” was the home page, and
  2. One that returned the home page

In terms from other frameworks, the first handler was the Controller or the Router, and the second was the View or Provider.

50% of Ring

Let’s look first at the provider, or that which serves back a page. Obviously, I’m not really going to try and re-implement Google for this example, so the first handler we write in Clojure will simply return “I am awesome!” Note that I’m doing all this interactively by typing into the REPL, not by editing some file and then running it; I didn’t call this “ring-play” for nothing :)

Three things to notice here:

  1. I’m completely ignoring the Request parameter “req,” which is a hash,
  2. The Response is also a hash, and
  3. The response hash has three members: :status, :headers, and :body.

At this point, you have now seen and understood 50% of Ring: Responses are hashes with three keys, and Handlers are functions that take a Request and return a Response.

The next 25% of Ring

The previous section’s awesome-page Handler ignored its arguments entirely, but this is OK, because we were simply trying to see a Provider; we assumed that there was some Router somewhere that called awesome-page; something like the any-google-page function back in the pseudo-code example.

Let’s build that Router now and use it to understand the Request:

A quick test in the REPL:

Middleware: The Easy 10%

To me, what made Ring hard to understand was its succinctness and the rather tight relationship between Handlers and Middleware, so to make it easier to understand, I’m about to make it more complicated!

Up until now, we’ve used the concepts of a Provider, which returns a web page as a Response, and a Router, which determines which Provider to call based on the URL in the Request.

Let’s call a Router and a group of Providers a Solution, because we generally write web applications to solve some problem. You could also call them Business Logic, or Value Add, or Application Logic, or even Deep Fried Bacon Burger, but this is my web page, so I’m going to stick to Solution.

Middleware is everything that is necessary to provide the Solution, but is not part of the Solution itself. In other languages or frameworks, it’s sometimes called Filters, or Pre-Processors and Post-Processors, or Wrappers, or Determinants, or Gates, or Aspects. For me, it’s easiest to think of it as Pre- and Post-Processors around the Solution.

An example of a Pre-Processor could be ensuring that only users on a specified internal host can access URL’s that are under /admin/; assume that “continue” calls the next pre-processor in the chain, or calls the Solution if all the pre-processors are completed.

An example of a Post-Processor could be compressing the returned web page if it is over four kilobytes; note that it does not have a failure condition, but rather either changes some data or passes it through unchanged.

Note that in these two examples, the Pre-Processor preproc-admins-only could arguably be part of the Solution, whereas the Post-Processor postproc-compress is usually something associated with the Application-Server or Web-Server itself. Because Rack is such a low-level system, this mingling of concerns is normal and can add to the confusion.

Let’s actually write this pre-processor in non-ideomatic, non-ring-esque Clojure:


user=> (defn preproc-admins-only [req next-func]
#_=> (let [word1 (second (clojure.string/split (req :uri) #"/"))
#_=> src (req :remote-addr)]
#_=> (if (and (= word1 "admin")
#_=> (not (= src "192.168.15.55")))
#_=> {:status 401 :headers {} :body "Use the secure system"} ; THEN
#_=> (next-func req)))) ; ELSE
#'user/preproc-admins-only

Since it’s a Pre-Processor, it should go in front of our Router function “awesome-router,” so let’s try that out; Note that a 404 error is the correct response if we’re “authorized” for access because our awesome-router doesn’t have a rule for /admin:


user=> (preproc-admins-only { :uri "/" :remote_addr "192.168.15.55" }
#_=> awesome-router)
{:status 200, :headers {}, :body "I am awesome!"}

user=> (preproc-admins-only { :uri "/admin/foo" :remote-addr "192.168.15.55" }
#_=> awesome-router)
{:status 404, :headers {}, :body "Page not found"}

user=> (preproc-admins-only { :uri "/admin/foo" :remote-addr "10.0.0.1" }
#_=> awesome-router)
{:status 401, :headers {}, :body "Use the secure system"}

So yes, our pre-processing middleware seems to work. We can also see there’s a bit of a signature there: pre-processors take the Request and a next-function to call if the middleware passes. As long as all the pre-processors follow this pattern, we can chain them along to perform multiple functions…

This is an important place to stop and make sure you understand everything up to this point:

  1. A Solution is a Router that looks at incoming URLs and dispatches off to the appropriate Provider.
  2. A Pre-Processor runs before the Solution, has access to the Request, and can either modify that same Request or can prevent the Solution from being run.
  3. A Post-Processor runs after the Solution, has access to the Response (and technically, also to the Request,) and can modify the Response, and could even prevent the Response from being sent to the User.
  4. Pre- and Post-Processors run in some order, with each getting the output of the previous step as input.

In diagram form:

Middleware: Tying it together for the next 5%

Take another look at the little diagram above and think about what’s actually happening if we try to express the first half, the Pre-Processors, in pseudo-code:

There’s definitely a pattern there, where we’re feeding one Pre-Processor to the next one. What if we put all the Pre-Processors into an Array?

The Post-Processors are pretty much the same, except they would go in reverse order:

Our processing logic is thus:

Middleware: The 5% step towards enlightenment

If you have sharp eyes and a good memory, you may be thinking “Hey, Darren said earlier that a Post-Processor could optionally also look at the Request, but I don’t see that in the process function up there. Darren obviously got lost and completely forgot about that. This page is nowhere near as entertaining as cracked.com And you’d be right.

Remember how I said I was going to make it more complicated, and then started talking about Pre- and Post-Processors? Here’s where I try to de-complicate things…

A Middleware Fucntion is conceptually a combination of a Pre-Processor and a Post-Processor, where either of them (but not both) can do absolutely nothing. Almost.

These conceptual-almost-middleware (CAM) functions are called on each other in a chain, where the last element in the chain is the Solution function. The pre-processing steps all happen before the Solution is called as we go down the function call chain, and the post-processing steps all happen as we unwind the stack after calling the Solution function.

Middleware: 99% towards understanding Ring

Now would be a good time to ensure you understand everything up to this point because in reality, the last leap isn’t one of understanding, it’s one of understanding the implementation in Clojure.

Ready? Good.

Middleware has the same effect as the conceptual-almost-middleware described above, but it is done by generating new composed functions. Or, more specifically, Middleware is a function that takes a Handler and returns another Handler.

Referring way back in this long and boring page, I said right near the start:

Ring specifies that a Request is processed by a Handler and returns a Response. It also specifies that there may be some Middleware involved, but that just confuses the picture by making things more complex; let’s ignore that for now.

So the first part, which was the easy part, says that there is a pattern Handler(Request) => Response.

We can now say that there is a second pattern Middleware(Handler) => Handler. Middleware is a pattern for generating a Handler from another Handler. Let’s take a look at the preproc-admins-only function we defined earlier, and convert it to a real Middleware function; note that wrap-X is the naming convention for middeware in Ring.

As Middleware

Our Previous Pre-Processor

As you can see, the only real differences are that we take one parameter (the handler) instead of two, and we define and return an anonymous function which takes one parameter (the request). So the function we return is another Handler.

The Last 1%

Earlier I defined a Solution as a Router and a Provider. Now that we’re near the end of this long long page, we can be honest:

  • A provider is really just a Handler
  • What we called a pouter is often really done with Middleware

There is still a fairly close relationship between routers and providers, since a router actually has to know what providers exist so it can call them, but implementation-wise, the router is usually Middleware. The rest of the Middleware doesn’t usually know or care about what’s above it or below it, since it just cares about going down the chain.

And now, at this point, we’re finally at the point where we (okay, I) can mostly comprehend what’s at the bottom of the official Ring Concepts documentation page:

Recall that -> is more or less defined as:

  1. Take the first argument
  2. Pass it as a parameter to the second argument
  3. Pass the result of that to the third argument
  4. Pass the result of that to the fourth argument

We know that a Handler is a function that takes a Request and returns a Response, and we know that wrap-X is a metadata function, so following the threading macro as if it was iterative. Note especially where handler is passed to wrap-content-type: As the first argument.

We can see that the LAST Metadata function passed to -> is actually the FIRST one called on the way in, and the LAST one called on the way out because it’s effectively h3 calling h2. Or, to put it another way, Your Handler is obviously called at the bottom of the “call stack.” and since is the FIRST argument to the -> macro, things are called from end to beginning.

Leave a Reply