Sessions for Ring

670 views
Skip to first unread message

James Reeves

unread,
Feb 3, 2010, 8:27:25 AM2/3/10
to Clojure
Hi folks,

Mark McGranaghan and I have recently been working on new functionality
for Ring, a web application library for Clojure. Ring is similar to
Rack on Ruby, and provides a simple, functional interface for handling
HTTP requests.

We've recently added support for urlencoded parameters and cookies,
and next we plan on adding session support to the mix. What we'd like
is to get some feedback from the Clojure community on how best to
implement session support.

Currently we have two possible designs:

Design 1:
The session is stored as an mutable atom map in (request :session). To
update the session, use the standard Clojure swap! function, e.g.

(defn handler [request]
(let [session (request :session)
counter (@session :counter)]
(swap! session assoc :counter (inc counter))
{:status 200
:headers {}
:body (str "You've visited this page " counter " times!")}))

The reasoning behind this approach is that sessions are mutable maps
associated with the request, and thus the most natural way to
represent them is via an (atom {}) on the request.

Design 2:
The session is an immutable map in (request :session). To update the
session, add the updated session to the response, e.g.

(defn handler [request]
(let [session (request :session)
counter (session :counter)]
{:status 200
:headers {}
:session (assoc session :counter (inc counter))
:body (str "You've visited this page " counter " times!")}))

The reasoning behind this approach is that a more functional design
has more in common with other parts of Ring, and allows handlers to be
pure functions, even if they use sessions.

I favour design 1, whilst Mark favours design 2.

I've also had an email suggesting that instead of the session being
completely replaced by the session map on the response, it is instead
only updated. This could be achieved by adding an update function to
the :session key, or perhaps to merge the response session with the
existing session, removing any key with a value of nil.

Please let us know what you think is the best approach listed here, or
if you have an entirely new design we've not thought of.

Thanks in advance,

- James

Eric Lavigne

unread,
Feb 3, 2010, 9:21:30 AM2/3/10
to clo...@googlegroups.com
> Currently we have two possible designs:
>
> Design 1:
> The session is stored as an mutable atom map in (request :session). To
> update the session, use the standard Clojure swap! function, e.g.
>
> Design 2:
> The session is an immutable map in (request :session). To update the
> session, add the updated session to the response, e.g.

I prefer design 1. As much as I would like to think of (request ->
response) as a pure function, there are often other things going on
such as talking to a database. What if the session is changed by two
requests that are being handled simultaneously? Ring can't
automatically re-try because then a database update may be repeated.

With the same issue in mind, I propose design 3. Like design 1 except
(ref {}) instead of (atom {}) because I may want to coordinate changes
between the session and some other transaction-aware state.

James Reeves

unread,
Feb 3, 2010, 9:59:53 AM2/3/10
to Clojure
On Feb 3, 9:21 am, Eric Lavigne <lavigne.e...@gmail.com> wrote:
> With the same issue in mind, I propose design 3. Like design 1 except
> (ref {}) instead of (atom {}) because I may want to coordinate changes
> between the session and some other transaction-aware state.

Sessions can be implemented with many storage engines, and not all of
them will have transactions. Even those that do support transactions,
such as SQL databases, do not tie in with Clojure's STM mechanism, so
making the session storage a ref would only work for in-memory session
storage.

In my view, we need to take a lowest-common-denominator approach: if
two threads try to write the same session data at the same time, then
the last one to write wins. This approach will work with every sort of
storage engine, and will be sufficient for most session uses. Should
something more fine grained be required, a value can be added to the
session that references a more sophisticated concurrency primitive.

- James

Rich Hickey

unread,
Feb 3, 2010, 11:31:33 AM2/3/10
to clo...@googlegroups.com

The last idea is on the right track. In any case, any reference type
surrounding the session data isn't really the session data of record,
it will be backed by something else (cookies/db etc), so such use of
references is not right.

OTOH, the Clojure model of sending change into state can help
encapsulate how it is stored, and possibly avoid some
read-modify-write and last-one-wins issues.

One idea is to pass commute-like and deref-like functions to the
handling code for sending change to, and accessing, the session state.
The state will be an immutable value as usual. This encapsulates
everything about the session data.

A second option is the request has the 'current' session state value,
and people can (functionally) append to the response session-commuting
fns and args, to be applied to the session state on successful
response.

In either case, moving to a commute model is a good idea, even if not
all backends can support it (in which case they devolve to
last-one-wins).

Rich

Vagif Verdi

unread,
Feb 3, 2010, 4:27:47 PM2/3/10
to Clojure
Whatever you guys chose, do not go the immutable road. Compojure took
that approach and now many people (including me) are stuck with
situations where they need to update session in a middle and pass it
somewhere else, and they can't. Session is a data storage, just like a
database.

One of the strong points of compojure is the layers of middleware.
Unfortunately there's no way to communicate between the layers,
because current session updating mechanism makes it impossible. You
cannot use the session to signal to the middleware that this
particular case requires different handling.

So one has to resort to communicating through other shared data
storages like database and then practically duplicate the session
mechanism.

Mark McGranaghan

unread,
Feb 3, 2010, 6:55:43 PM2/3/10
to Clojure
Hi Vagif,

On Feb 3, 4:27 pm, Vagif Verdi <vagif.ve...@gmail.com> wrote:
> Whatever you guys chose, do not go the immutable road. Compojure took
> that approach and now many people (including me) are stuck with
> situations where they need to update session in a middle and pass it
> somewhere else, and they can't. Session is a data storage, just like a
> database.

One thing that would help us a lot with choosing the right interface
for sessions is examples of session use from real application code. We
have considered simple examples like incrementing a counter and
logging in / logging out (http://gist.github.com/289993), but a
concrete example of read-write-read session usage would be really
helpful. Would you be willing to show us a case were you think
stateful sessions would be particularly nice?

> One of the strong points of compojure is the layers of middleware.
> Unfortunately there's no way to communicate between the layers,
> because current session updating mechanism makes it impossible. You
> cannot use the session to signal to the middleware that this
> particular case requires different handling.
>
> So one has to resort to communicating through other shared data
> storages like database and then practically duplicate the session
> mechanism.

It is possible to communicate between purely functional middleware
layers. The way one does this is by assoc'ing keys into the request
and/or response maps, and then get'ing and acting on those values
later in the request/response ring [1]. For example, to alter the the
behavior of middleware "wrap-second" from "wrap-first"

(defn wrap-first [handler]
(fn [req]
(handler (assoc req ::indicator true))))

(defn wrap-second [handler]
(fn [req]
(if (::indicator req)
(do-somthing handler req)
(do-something-else handler req))))

(def app
(-> core
wrap-first
wrap-second))

This type of middleware communication will work in some cases but may
not in others. In particular, it works best when the actions in
question either happen all on the way down the middleware stack from
the adapter to the handler, or all on the way up. Would this kind of
communication be helpful for the problems that you are having? Again,
specific examples would be great.

[1] illustration of a request/response ring: http://imgur.com/6zAYD.jpg

Vagif Verdi

unread,
Feb 3, 2010, 8:44:44 PM2/3/10
to Clojure
On Feb 3, 3:55 pm, Mark McGranaghan <mmcgr...@gmail.com> wrote:
> One thing that would help us a lot with choosing the right interface
> for sessions is examples of session use from real application code. We
> have considered simple examples like incrementing a counter and
> logging in / logging out (http://gist.github.com/289993), but a
> concrete example of read-write-read session usage would be really
> helpful. Would you be willing to show us a case were you think
> stateful sessions would be particularly nice?

There were a few corner cases with immutable sessions discussed in
compojure group:

Invalidating sessions do not work. James replied that it would be hard
to fix with immutable sessions:
http://groups.google.com/group/compojure/browse_thread/thread/b27ab40d6ebb8864/4ef387a8d0788b49

Another one is that (session-assoc) silently fails if you run it more
than once:
http://groups.google.com/group/compojure/browse_thread/thread/4b7c1734d2eb9fde/c584cc942ef4364d?q=#c584cc942ef4364d

Solution is.. not to run it more than once. :)) But imagine chained
function calls. Those session-assocs will clash.

> It is possible to communicate between purely functional middleware
> layers. The way one does this is by assoc'ing keys into the request
> and/or response maps, and then get'ing and acting on those values
> later in the request/response ring [1].

Ah my bad. I shouldn't have said "impossible". Obviously it is
possible, because i do that too. But it feels like a kludge. Sometimes
you return body of html. Other times you have to return response
object where body is one of its value-key parts along with some other
keys.

>Would this kind of
> communication be helpful for the problems that you are having? Again,
> specific examples would be great.
>

Here's the problem i am having with passing a session:

Session is a long term data storage. This means that it should be
available be read / write from anywhere in the chain of processing the
request. In haskell you can pass session through a chain of functions
in a State monad. Any function in a chain can read or write thus
affecting others.

If we can achieve same thing in compojure it would be great, not
because it is not possible to operate other way. But because it would
be much more simpler.

BTW i do not see any point in trying to make session handling "pure".
We are dealing with fundamentally impure operation: handling a web
request. It practically always depends on more than input variables.
If it is a static file it depends on what's on the harddrive. Most
cases it reads/writes data to database. In fact, if not the
performance price for hitting database, no one would bother with
sessions. It is quite simple to store session data in database. The
reason we are holding that data in a session is not that it is somehow
different from other data that we store in db. The reason is that we
need that data in every request. We simply caching.

James Reeves

unread,
Feb 3, 2010, 10:18:57 PM2/3/10
to Clojure
On Feb 3, 8:44 pm, Vagif Verdi <vagif.ve...@gmail.com> wrote:
> Invalidating sessions do not work. James replied that it would be hard
> to fix with immutable sessions:http://groups.google.com/group/compojure/browse_thread/thread/b27ab40...

Actually, I was completely wrong in that instance. I'm not sure what I
was thinking, but it is perfectly possible to clear functional
sessions. The problem is limited only to the Compojure session
middleware, and is not indicative of an inherent flaw in the
functional session design.

> Another one is that (session-assoc) silently fails if you run it more

> than once:http://groups.google.com/group/compojure/browse_thread/thread/4b7c173...

This again is something that's limited to Compojure, and not
necessarily indicative of a problem with the functional approach. The
session-assoc function is essentially a Ring handler function that
modifies the request. Because each session-assoc is unconnected from
the next, there isn't a way to chain their output. However, this
doesn't mean that a more sophisticated approach isn't possible.

- James

James Reeves

unread,
Feb 3, 2010, 10:50:57 PM2/3/10
to Clojure
On Feb 3, 11:31 am, Rich Hickey <richhic...@gmail.com> wrote:
> One idea is to pass commute-like and deref-like functions to the
> handling code for sending change to, and accessing, the session state.
> The state will be an immutable value as usual. This encapsulates
> everything about the session data.

That's an interesting approach.

Would you consider it good or bad practise to inherit the session
store from IDeref? Clojure's concurrency primitives have different
functions to handle updates (swap!, alter, etc.), but universally use
deref to return their contents. Would it be worth following the same
convention for sessions?

- James

Vagif Verdi

unread,
Feb 3, 2010, 11:13:01 PM2/3/10
to Clojure
On Feb 3, 7:18 pm, James Reeves <weavejes...@googlemail.com> wrote:
> On Feb 3, 8:44 pm, Vagif Verdi <vagif.ve...@gmail.com> wrote:
>

> This again is something that's limited to Compojure, and not
> necessarily indicative of a problem with the functional approach.

I agree. As long as session implementation is robust and simple to use
without corner cases it does not really matter for me how they are
implemented internally.

Reply all
Reply to author
Forward
0 new messages