[ANN] Ring: A Web application library for Clojure.

263 views
Skip to first unread message

Mark McGranaghan

unread,
Jan 12, 2009, 11:45:34 PM1/12/09
to Clojure
Hi All,

I'm happy to announce the alpha release of 'Ring', a library inspired
by Python's WSGI and Ruby's Rack for developing web applications in
Clojure.

I've made it as easy as humanly possible for you to try it out:

git clone git://github.com/mmcgrana/ring.git
cd ring
java -Djava.ext.dirs=jars clojure.main src/ring/examples/
hello_world.clj

And your up and running with your first Ring web app, which you can
see at http://localhost:8080/ in your browser.

The basic idea of Ring is that web apps are just Clojure functions
that take a standardized request as a single argument and return and
standardized response. For example, the hello_world.clj script from
above is:

(ns ring.examples.hello-world
(:require ring.jetty)
(:import java.util.Date java.text.SimpleDateFormat))

(def formatter (SimpleDateFormat. "HH:mm:ss"));

(defn app
[req]
{:status 200
:headers {"Content-Type" "text/html"}
:body (str "<h3>Hello World from Ring</h3>"
"<p>The current time is "
(.format formatter (Date.)) ".</p>")})

(ring.jetty/run {:port 8080} app)

Its nice to be able to get to "Hello World" so quickly, but the real
power of Ring is that apps are just functions - hence we can combine,
wrap, curry, and generally manipulate them as first class values.

For example, someone asked in #clojure today how they could make their
web app provide a cleaned backtrace as an HTML response when it raised
exceptions. To add such exception handling to our Hello World Ring app
we would just use the ring.backtrace middleware:

(ring.jetty/run {:port 8080} app)

becomes

(ring.jetty/run {:port 8080}
(ring.backtrace/wrap
app))

Similarly, one might want to have changes to a web app's code be
reflected in real time in the development environment, so as to avoid
constantly having to reboot the webserver. The ring.reload middleware
accomplishes exactly that:

(ring.jetty/run {:port 8080}
(ring.backtrace/wrap
(ring.reload/wrap '(ring.examples.hello-world)
app)

These are some of the features that originally motivated me to develop
Ring, but the complete list of functionality available to Ring apps is
larger and continues to grow:

* ring.jetty: Handler for the Jetty webserver.
* ring.file: Middleware that serves static files out of a public
directory.
* ring.file-info: Middleware that augments response headers with info
about File responses.
* ring.dump: Endpoint that dumps the Ring requests as HTML responses
for debugging.
* ring.show-exceptions: Middleware that catches exceptions and
displays readable backtraces for debugging.
* ring.reload: Middleware to automatically reload selected libs before
each requests, minimizing server restarts.
* ring.builder: Helpers for combining Ring endpoints and middleware
into Ring apps.
* ring.lint: Linter for the Ring interface, ensures compliance with
the Ring spec.
* ring.examples.*: Various example Ring apps.

You can find more details about Ring at its project page on GitHub,
including a README file for new users and a draft SPEC file that
documents the Ring interface:

http://github.com/mmcgrana/ring

I've built an open source web app on Ring: http://cljre.com. The
source for this simple app, available at http://github.com/mmcgrana/cljre.com,
could serve as a good introduction to how apps can consume Ring
requests and to the use of modular Ring middleware; see in particular
the src/cljre/app.clj file, where most of that application is defined.

Also, I think I should mention how I see Ring relating to the Java
Servlet abstraction and to existing and new Clojure web frameworks.
Ring heavily leverages the Servlet API internally, as I strongly
believe in not reinventing wheels such as basic HTTP parsing.
Furthermore, I think that the interface that Ring presents to Clojure
web application developers by pre-processing the servlet requests is
dramatically more useful than that of the raw servlet. Ring uses
Servlets for what they are really good at - implementing HTTP - and
presents a simple API against which additional logic can be defined in
the application layer.

In terms of Clojure web frameworks, I think that there is a lot to be
gained by leveraging the Ring interface, especially from the modular
functionality provided by Ring middleware. I'd like in particular to
be able to run Compojure apps in Ring - if the users and authors of
Compojure are interested I'd be happy to work with them to see what we
can do.

If you've made it this far, thanks a lot for reading! I welcome any
comments or suggestions that you have about Ring, the draft SPEC
document, or the Clojure web app space in general.

Thanks again,
- Mark

Matt Revelle

unread,
Jan 12, 2009, 11:59:01 PM1/12/09
to clo...@googlegroups.com
Mark,

This looks great! Thanks for writing and sharing.

-Matt

Dan Larkin

unread,
Jan 13, 2009, 12:16:39 AM1/13/09
to clo...@googlegroups.com
I'm incredibly impressed! Have only looked at the code briefly but I
read the whole post and I'm really excited for where this is going.


On Jan 12, 2009, at 11:45 PM, Mark McGranaghan wrote:

Paul Barry

unread,
Jan 13, 2009, 1:13:50 AM1/13/09
to clo...@googlegroups.com
What's does the req object that is passed into the function have in it?

Paul Barry

unread,
Jan 13, 2009, 1:19:54 AM1/13/09
to clo...@googlegroups.com
To answer my own question:

  [#^HttpServletRequest request]
  {:server-port (.getServerPort request)
   :server-name (.getServerName request)
   :remote-addr (.getRemoteAddr request)
   :uri (.getRequestURI request)
   :query-string (.getQueryString request)
   :scheme (keyword (.getScheme request))
   :request-method (keyword (.toLowerCase (.getMethod request)))
   :headers (reduce
                          (fn [header-map #^String header-name]
                            (assoc header-map
                              (.toLowerCase header-name)
                              (.getHeader request header-name)))
                          {}
                          (enumeration-seq (.getHeaderNames request)))
   :content-type (.getContentType request)
   :content-length (let [len (.getContentLength request)]
                         (if (>= len 0) len))
   :character-encoding (.getCharacterEncoding request)
   :body (.getInputStream request)})

Which by the way is awesome, because you function takes a map, which doesn't have to be created from an HttpServletRequest object.  For the purposes of testing, you can just construct a map with the parts you want and pass that to your function.  Great Work!

Mark McGranaghan

unread,
Jan 13, 2009, 1:59:23 AM1/13/09
to clo...@googlegroups.com
>> What's does the req object that is passed into the function have in it?

The contents of the incoming Ring request (and outgoing Ring response)
are described in the SPEC document. I should make this a little
clearer in the docs. Good for you for checking the source though!

> Which by the way is awesome, because you function takes a map, which doesn't
> have to be created from an HttpServletRequest object. For the purposes of
> testing, you can just construct a map with the parts you want and pass that
> to your function.

Exactly. By distilling the request/response process into a single
function call with a Clojure map as an argument, we get all the power
and flexibility of Clojure and its data structures as well as the
ability to concisely construct test cases for Ring components.

Note also that we are not limited to the values supplied by the
original Ring handler - middleware components may want to assoc in
values both on the way up to the endpoint app and on the way down back
to the client. For example, post body parsing middleware could process
the response body and assoc the parsed value into the request before
proxying back to the main app. On the response side, middleware might
assoc in additional headers to augment the response, as ring.file-info
does to File responses.

Thanks for your comments,
- Mark

Kees-Jochem Wehrmeijer

unread,
Jan 13, 2009, 6:41:45 AM1/13/09
to Clojure
This looks really cool. I've actually been experimenting with exactly
the same thing. One thing I thought about (but didn't implement), was
using some kind of lazy hash map, for the request, so that it only
calls the methods (like getServerPort) if you need them. I don't
really know how hard or easy that would be though.

cheers,
Kees
> On Tue, Jan 13, 2009 at 1:13 AM, Paul Barry <pauljbar...@gmail.com> wrote:
> > What's does the req object that is passed into the function have in it?
>
> > On Mon, Jan 12, 2009 at 11:45 PM, Mark McGranaghan <mmcgr...@gmail.com>wrote:
>
> >> Hi All,
>
> >> I'm happy to announce the alpha release of 'Ring', a library inspired
> >> by Python's WSGI and Ruby's Rack for developing web applications in
> >> Clojure.
>
> >> I've made it as easy as humanly possible for you to try it out:
>
> >>    git clone git://github.com/mmcgrana/ring.git
> >>    cd ring
> >>    java -Djava.ext.dirs=jars clojure.main src/ring/examples/
> >> hello_world.clj
>
> >> And your up and running with your first Ring web app, which you can
> >> see athttp://localhost:8080/in your browser.

Meikel Brandmeyer

unread,
Jan 13, 2009, 2:52:28 PM1/13/09
to clo...@googlegroups.com
Hi Kees-Jochem,

Am 13.01.2009 um 12:41 schrieb Kees-Jochem Wehrmeijer:

> This looks really cool. I've actually been experimenting with exactly
> the same thing. One thing I thought about (but didn't implement), was
> using some kind of lazy hash map, for the request, so that it only
> calls the methods (like getServerPort) if you need them. I don't
> really know how hard or easy that would be though.

I implemented the lazy-map package, which provides
lazy versions of all the map types of clojure: hash-map,
sorted-map and struct-map.

Complete with lazy map entries and lazy-assoc. A value
is only evaluated when really accessed.

In case you are interested, here's the link:
http://kotka.de/projects/clojure/lazy-map.html

Sincerely
Meikel

James Reeves

unread,
Jan 13, 2009, 4:13:45 PM1/13/09
to Clojure
On Jan 13, 4:45 am, Mark McGranaghan <mmcgr...@gmail.com> wrote:
> In terms of Clojure web frameworks, I think that there is a lot to be
> gained by leveraging the Ring interface, especially from the modular
> functionality provided by Ring middleware. I'd like in particular to
> be able to run Compojure apps in Ring - if the users and authors of
> Compojure are interested I'd be happy to work with them to see what we
> can do.

I think it's a good idea in principle to have a common functional way
of accessing servlets. However, I notice there's still some wheel
reinventing going on. The HttpServlet API provides methods for
parameters, cookies and sessions, but these are absent from Ring. I
notice that in your weld framework, you implement these pieces of
functionality in Clojure, but why? I can understand keeping a low-
level interface lightweight, but Ring appears to be lightweight than
the Servlet API it's based upon.

Other than that, I generally like what I see in Ring. It uses Clojure
data structures, is functional, and is refreshingly explicit.

- James

Mark McGranaghan

unread,
Jan 13, 2009, 4:56:54 PM1/13/09
to clo...@googlegroups.com
Hi James,

Thanks for taking the time to check out Ring.

> The HttpServlet API provides methods for
> parameters, cookies and sessions, but these are absent from Ring. I
> notice that in your weld framework, you implement these pieces of
> functionality in Clojure, but why? I can understand keeping a low-
> level interface lightweight, but Ring appears to be lightweight than
> the Servlet API it's based upon.

I choose not include the parameters, cookies, and sessions features
from the Servlet API because I'm not confident that the interfaces
presented by Servlets in these cases will be acceptable to all Ring
applications. For example, different Ring applications will want to
parse parameters in different ways and will have varying requirements
for session storage. I think it would be detrimental if Ring
applications were compromised by the limitations of the Servlet API
when these features can be defined entirely in terms of the more core
subset of the Servlet API reflected in the Ring request object.

The decision not to require that a Ring request support these extra
features of the HttpServlet API was a difficult one to make because
not having them will make integration with Compojure and other
Java/Servlet-leaning codebases more difficult. I do though still think
its worth pursuing in the case of Compojure - if you're interested
maybe we should start a discussion on the Compojure list?

- Mark

James Reeves

unread,
Jan 13, 2009, 6:15:38 PM1/13/09
to Clojure
On Jan 13, 9:56 pm, "Mark McGranaghan" <mmcgr...@gmail.com> wrote:
> I choose not include the parameters, cookies, and sessions features
> from the Servlet API because I'm not confident that the interfaces
> presented by Servlets in these cases will be acceptable to all Ring
> applications. For example, different Ring applications will want to
> parse parameters in different ways and will have varying requirements
> for session storage.

Sure, but that doesn't mean they have to use them.

It seems to me that Ring's approach works well if there's the
possibility of implementing Ring using technology other than Servlets.
In this case, it makes sense for Ring to act as a minimum common
interface. But if this isn't your goal, then you're just removing
functionality for aesthetic reasons.

I'm in two minds about this. I agree that session handling and
parameter parsing don't belong in a functional interface for HTTP
handling. But on the other hand, they certainly do belong in a
functional interface to the Java Servlet spec (flawed as that spec may
be). If you're attempting the former, then I agree with your decision.
If it's the latter, I think you should be more pragmatic.

> The decision not to require that a Ring request support these extra
> features of the HttpServlet API was a difficult one to make because
> not having them will make integration with Compojure and other
> Java/Servlet-leaning codebases more difficult.

It's true that many parts of the compojure.http library are currently
tightly coupled to the javax.servlet libraries, but this shouldn't be
the state of affairs for too much longer. I've been planning on
splitting out the compojure.http library into more generic parts for
some time, so it shouldn't be too hard to work Ring into my plans as
well - or at least to leave a Ring-shaped hole for someone to fill in.

> I do though still think its worth pursuing in the case of Compojure
> - if you're interested maybe we should start a discussion on the
> Compojure list?

Go for it :)

- James

Mark McGranaghan

unread,
Jan 13, 2009, 7:35:19 PM1/13/09
to clo...@googlegroups.com
> It seems to me that Ring's approach works well if there's the
> possibility of implementing Ring using technology other than Servlets.
> In this case, it makes sense for Ring to act as a minimum common
> interface. But if this isn't your goal, then you're just removing
> functionality for aesthetic reasons.
>
> I'm in two minds about this. I agree that session handling and
> parameter parsing don't belong in a functional interface for HTTP
> handling. But on the other hand, they certainly do belong in a
> functional interface to the Java Servlet spec (flawed as that spec may
> be). If you're attempting the former, then I agree with your decision.
> If it's the latter, I think you should be more pragmatic.

For Ring, the HttpServlet API is a means to an end, not an end in
itself. The interface that Servlets present for parameter parsing and
session handling are too flawed to merit their inclusion as default
choices for Ring apps and the binding of Ring to Servlets in general.

Thus I'd like to see Ring applications remain decoupled from the
Servlet API, while Ring handlers can leverage Servlet functionality
internally to implement for these apps the cleanest HTTP API possible.

James Reeves

unread,
Jan 13, 2009, 8:31:00 PM1/13/09
to Clojure
On Jan 14, 12:35 am, "Mark McGranaghan" <mmcgr...@gmail.com> wrote:
> For Ring, the HttpServlet API is a means to an end, not an end in
> itself. The interface that Servlets present for parameter parsing and
> session handling are too flawed to merit their inclusion as default
> choices for Ring apps and the binding of Ring to Servlets in general.

Okay, I think I'm convinced of your reasoning. Currently there are too
many practical benefits with servlets for me to drop them as the
primary interface to Compojure, but Ring is the more elegant
interface, and one that I think will be worth time integrating as an
alternative back end.

- James
Reply all
Reply to author
Forward
0 new messages