Checking in changes prior to tagging of version 0.09_02. Changelog diff is:
[catagits/Web-Session.git] / lib / Plack / Middleware / Session.pm
index 9bbe61c..8d9a3ee 100644 (file)
@@ -2,43 +2,106 @@ package Plack::Middleware::Session;
 use strict;
 use warnings;
 
-use Plack::Session;
-use Plack::Request;
-use Plack::Response;
-use Plack::Session::State::Cookie;
-use Plack::Session::Store;
+our $VERSION   = '0.09_02';
+our $AUTHORITY = 'cpan:STEVAN';
+
+use Plack::Util;
+use Scalar::Util;
 
 use parent 'Plack::Middleware';
 
-use Plack::Util::Accessor qw( state store );
+use Plack::Util::Accessor qw(
+    state
+    store
+);
 
 sub prepare_app {
     my $self = shift;
-    unless ($self->state) {
-        $self->state( Plack::Session::State::Cookie->new );
-    }
 
-    unless ($self->store) {
-        $self->store( Plack::Session::Store->new );
-    }
+    $self->state( 'Cookie' ) unless $self->state;
+    $self->state( $self->inflate_backend('Plack::Session::State', $self->state) );
+    $self->store( $self->inflate_backend('Plack::Session::Store', $self->store) );
+}
+
+sub inflate_backend {
+    my($self, $prefix, $backend) = @_;
+
+    return $backend if defined $backend && Scalar::Util::blessed $backend;
+
+    my @class;
+    push @class, $backend if defined $backend; # undef means the root class
+    push @class, $prefix;
+
+    Plack::Util::load_class(@class)->new();
 }
 
 sub call {
     my $self = shift;
     my $env  = shift;
 
-    $env->{'plack.session'} = Plack::Session->new(
-        state   => $self->state,
-        store   => $self->store,
-        request => Plack::Request->new( $env )
-    );
+    my($id, $session) = $self->get_session($env);
+    if ($id && $session) {
+        $env->{'psgix.session'} = $session;
+    } else {
+        $id = $self->generate_id($env);
+        $env->{'psgix.session'} = {};
+    }
+
+    $env->{'psgix.session.options'} = { id => $id };
 
     my $res = $self->app->($env);
-    $self->response_cb($res, sub {
-        my $res = Plack::Response->new(@{$_[0]});
-        $env->{'plack.session'}->finalize( $res );
-        @{$_[0]} = @{$res->finalize};
-    });
+    $self->response_cb($res, sub { $self->finalize($env, $_[0]) });
+}
+
+sub get_session {
+    my($self, $env) = @_;
+
+    my $id = $self->state->extract($env)   or return;
+    my $session = $self->store->fetch($id) or return;
+
+    return ($id, $session);
+}
+
+sub generate_id {
+    my($self, $env) = @_;
+    $self->state->generate($env);
+}
+
+sub commit {
+    my($self, $env) = @_;
+
+    my $session = $env->{'psgix.session'};
+    my $options = $env->{'psgix.session.options'};
+
+    if ($options->{expire}) {
+        $self->store->remove($options->{id});
+    } else {
+        $self->store->store($options->{id}, $session);
+    }
+}
+
+sub finalize {
+    my($self, $env, $res) = @_;
+
+    my $session = $env->{'psgix.session'};
+    my $options = $env->{'psgix.session.options'};
+
+    $self->commit($env) unless $options->{no_store};
+    if ($options->{expire}) {
+        $self->expire_session($options->{id}, $res, $env);
+    } else {
+        $self->save_state($options->{id}, $res, $env);
+    }
+}
+
+sub expire_session {
+    my($self, $id, $res, $env) = @_;
+    $self->state->expire_session_id($id, $res, $env->{'psgix.session.options'});
+}
+
+sub save_state {
+    my($self, $id, $res, $env) = @_;
+    $self->state->finalize($id, $res, $env->{'psgix.session.options'});
 }
 
 1;
@@ -54,10 +117,15 @@ Plack::Middleware::Session - Middleware for session management
 =head1 SYNOPSIS
 
   use Plack::Builder;
-  use Plack::Middleware::Session;
 
   my $app = sub {
-      return [ 200, [ 'Content-Type' => 'text/plain' ], [ 'Hello Foo' ] ];
+      my $env = shift;
+      my $session = $env->{'psgix.session'};
+      return [
+          200,
+          [ 'Content-Type' => 'text/plain' ],
+          [ "Hello, you've been here for ", $session->{counter}++, "th time!" ],
+      ];
   };
 
   builder {
@@ -65,12 +133,26 @@ Plack::Middleware::Session - Middleware for session management
       $app;
   };
 
+  # Or, use the File store backend (great if you use multiprocess server)
+  # For more options, see perldoc Plack::Session::Store::File
+  builder {
+      enable 'Session', store => 'File';
+      $app;
+  };
+
 =head1 DESCRIPTION
 
 This is a Plack Middleware component for session management. By
-default it will use cookies to keep session state and store data
-in memory. This distribution comes also comes with other state
-and store solutions.
+default it will use cookies to keep session state and store data in
+memory. This distribution also comes with other state and store
+solutions. See perldoc for these backends how to use them.
+
+It should be noted that we store the current session as a hash
+reference in the C<psgix.session> key inside the C<$env> where you can
+access it as needed.
+
+B<NOTE:> As of version 0.04 the session is stored in C<psgix.session>
+instead of C<plack.session>.
 
 =head2 State
 
@@ -104,11 +186,9 @@ This will persist session data in a file. By default it uses
 L<Storable> but it can be configured to have a custom serializer and
 deserializer.
 
-=item L<Plack::Session::Store::CHI>
+=item L<Plack::Session::Store::Cache>
 
-This will persist session data using the L<CHI> module. This
-offers a lot of flexibility due to the many excellent L<CHI>
-drivers available.
+This will persist session data using the L<Cache> interface.
 
 =item L<Plack::Session::Store::Null>
 
@@ -117,6 +197,31 @@ you can use this noop module.
 
 =back
 
+=head1 OPTIONS
+
+The following are options that can be passed to this mdoule.
+
+=over 4
+
+=item I<state>
+
+This is expected to be an instance of L<Plack::Session::State> or an
+object that implements the same interface. If no option is provided
+the default L<Plack::Session::State::Cookie> will be used.
+
+=item I<store>
+
+This is expected to be an instance of L<Plack::Session::Store> or an
+object that implements the same interface. If no option is provided
+the default L<Plack::Session::Store> will be used.
+
+It should be noted that this default is an in-memory volatile store
+is only suitable for development (or single process servers). For a
+more robust solution see L<Plack::Session::Store::File> or
+L<Plack::Session::Store::Cache>.
+
+=back
+
 =head1 BUGS
 
 All complex software has bugs lurking in it, and this module is no
@@ -131,7 +236,7 @@ Stevan Little E<lt>stevan.little@iinteractive.comE<gt>
 
 =head1 COPYRIGHT AND LICENSE
 
-Copyright 2009 Infinity Interactive, Inc.
+Copyright 2009, 2010 Infinity Interactive, Inc.
 
 L<http://www.iinteractive.com>