Session support for Dancer, 1.0 is near

I’m glad to write this blog entry: Dancer now supports sessions.

Before giving you all the crunchy details about the implementation of that feature, let’s see some working code:

    post '/login' => sub {
        my $user = authenticate_user(params);
        if (defined $user) {
            session user_id => $user->id;
            return "Welcome aboard!";
        }
        else {
          return "Login failed";
        }
    };

    get '/home' => sub {
        if (not session('user_id')) {
            pass;
        }
        "Home sweet home";
    };

As I’m pretty happy with the implementation I came up with, I’m going to describe how sessions are handled in Dancer, and how one can write his own session engine.

First, I’d like to share the little brainstorming I had when I was thinking about how to bring session support into Dancer. At first, I wanted to depend on CGI::Session, assuming that users who want to use sessions within their Dancer app will have to install this module as well.

But afterall, I realized that writing a session engine was not such a big deal: it’s basically a question of storing a session id on the client side and using that session id as a key, for retreiving a previously serialized object on the server side.
There’s nothing much more complicated there.

My first reaction was to look at Sinatra, in order to see how it solves that issue.

Well, the fact is that Sinatra doesn’t hanlde sessions at all, Rack does. That means that you can’t write a session-aware app with Sinatra without installing Rack. Too bad.

By the way, will PSGI/Plack do the same? I’m curious about that…

Anyways, I took a look at Rack then, and discovered its own implementation of session handling. It’s pretty clean I must say: there is an abstract session class, that represents a typical session engine and a couple of session engines, implementing that class for particular session storages.

At that point, I decided to drop CGI::Session out of my dependency bag and start writing some code. As I liked Rack’s implementation, I pretty much did the same:

  • Dancer::Session – The gateway between the Dancer API and the chosen session engine.
  • Dancer::Session::Abstract – The abstract class representing a session engine interface, any new session engine must inherits from it and implement a set of methods (create, retreive, destroy and flush).
  • Dancer::Session::YAML – a YAML-file-based session engine, pretty convinient for development purposes
  • Dancer::Session::Memcached – a Memcached-based session engine

Here is as an example, the whole content of the Memcached implementation (I’ve just removed the bunch of initialization and sanity checks that occurs in init() for more readability).

package Dancer::Session::Memcached;

use strict;
use warnings;
use base 'Dancer::Session::Abstract';
use Dancer::Config 'setting';
use Dancer::ModuleLoader;

# singleton for the Memcached hanlde
my $MEMCACHED = undef;

sub init {
    my ($class) = @_;
    die "Cache::Memcached is needed and is not installed"
        unless Dancer::ModuleLoader->load('Cache::Memcached');

    my $servers = setting("memcached_servers");
    # ... check settings and initialize $servers correctly
    $MEMCACHED = Cache::Memcached->new(servers => $servers);
}

sub create {
    my ($class) = @_;
    my $self = $class->new;
    $MEMCACHED->set($self->id => $self);
    return $self;
}

sub retreive($$) {
    my ($class, $id) = @_;
    return $MEMCACHED->get($id);
}

sub destroy {
    my ($self) = @_;
    $MEMCACHED->delete($self->id);
}

sub flush {
    my $self = shift;
    $MEMCACHED->set($self->id => $self);
    return $self;
}

As you can see, there is really few code, we find here only the interesting parts: how to store and retreive information with a memcached backend.

Now, a user can use sessions with a memcached backend thanks to the following configuration file:

    # $appdir/config.yml
    session: "memcached"
    memcached_servers: "127.0.0.1:11211"

All of this is already available on GitHub and will be shiped as soon as the cookies support is finished, but that’s another topic ;)

This entry was posted in Programming and tagged , , . Bookmark the permalink. . 1961 views.

7 Responses to Session support for Dancer, 1.0 is near

  1. Yes, we have a plan to add Plack::Middleware::Session much like Rack does :)

    We actually had that stuff in HTTP::Engine::Middleware, which uses the generic HTTP::Session module:
    http://search.cpan.org/~yappo/HTTP-Engine-Middleware-0.17/lib/HTTP/Engine/Middleware/HTTPSession.pm
    http://search.cpan.org/~tokuhirom/HTTP-Session-0.32/

  2. > That means that you can’t write a session-aware app with Sinatra without installing Rack. Too bad.

    Not really, since Rack is Rack, and most users would just use Rack. And even if you run in non-Rack with servers, $env['rack.session'] is an abstract interface to get/set objects, so it should be easy (or at least possible) to write a middleware can that sets the env variable that does duck typing.

  3. sukria says:

    Thanks for the precisions Tatsuhiko!

  4. Kai Carver says:

    > Sinatra doesn’t hanlde sessions at all

    Sure?

    “Sinatra ships with basic support for cookie-based sessions”
    http://www.sinatrarb.com/book.html#sessions

    Also, “retrieve” is the more common spelling :-)

  5. sukria says:

    Kai, Tatsuhiko: here is what I meant:

    sinatra$ ack session lib/
    lib/sinatra/base.rb
    106:    # Access the underlying Rack session.
    107:    def session
    108:      env['rack.session'] ||= {}
    921:        builder.use Rack::Session::Cookie if sessions?
    999:    set :sessions, false
    1063:    set :sessions, false
    

    If Rack is not installed (the gem I mean) then sinatra cannot provide session support. This was my point here.

  6. Kai Carver says:

    ok I see.

    But do please consider changing the name of the “retreive” function to “retrieve”.

    Otherwise you will confuse people forever (like the guy who made the “referer” misspelling a permanent part of the HTTP spec…). I know this is inconsistent with the spelling of “receive”, but that’s an unfixable bug in the English language…

  7. sukria says:

    Kai I’m going to fix that, thanks! :)