Huge rewrite of the code: $session->get, ->set etc. do not read from
Tatsuhiko Miyagawa [Sat, 9 Jan 2010 06:09:38 +0000 (22:09 -0800)]
store as it's called anymore. Session is instantiated in the request
initialization, and committed to the data store in the finalization
phase.

This is more "normal" in terms of web application programming to read,
handle and commit session; imagine you have two concurrent requests
updates multiple keys with multiple ->set() call: the previous
implmentation would update one key from a request and another key from
another. That said, we *might* still need some kind of locking if we
want to provide stricter race condition guard for concurrent requests
with the identical session ids.

Deprecated lots of methods like ->persist, ->delete,
->get_session_id_from_request and changed the API params for ->fetch,
->store etc. Store modules need to be updated but the changes are not
so big.

lib/Plack/Middleware/Session.pm
lib/Plack/Session.pm
lib/Plack/Session/State.pm
lib/Plack/Session/State/Cookie.pm
lib/Plack/Session/Store.pm
lib/Plack/Session/Store/Cache.pm
lib/Plack/Session/Store/File.pm
lib/Plack/Session/Store/Null.pm
t/006_basic_w_null_store.t [changed mode: 0644->0755]
t/lib/TestSession.pm

index d3f44a5..6179ca4 100644 (file)
@@ -45,11 +45,9 @@ sub call {
     my $self = shift;
     my $env  = shift;
 
-    $env->{'psgix.session'} = $env->{'plack.session'} = $self->session_class->new(
-        state   => $self->state,
-        store   => $self->store,
-        request => Plack::Request->new( $env )
-    );
+    my $session = $self->session_class->fetch_or_create( Plack::Request->new($env), $self );
+
+    $env->{'psgix.session'} = $env->{'plack.session'} = $session;
 
     my $res = $self->app->($env);
     $self->response_cb($res, sub {
index 2f87c2d..6fd60d4 100644 (file)
@@ -5,48 +5,66 @@ use warnings;
 our $VERSION   = '0.03';
 our $AUTHORITY = 'cpan:STEVAN';
 
-use Plack::Util::Accessor qw[
-    id
-    store
-    state
-];
+use Plack::Util::Accessor qw( id is_new manager );
+
+sub fetch_or_create {
+    my($class, $request, $manager) = @_;
+
+    my $id = $manager->state->extract($request);
+    if ($id) {
+        my $store = $manager->store->fetch($id);
+        return $class->new( id => $id, _stash => $store, manager => $manager );
+    } else {
+        $id = $manager->state->generate($request);
+        return $class->new( id => $id, _stash => {}, manager => $manager, is_new => 1 );
+    }
+}
 
 sub new {
     my ($class, %params) = @_;
-    my $request = delete $params{'request'};
-    $params{'id'} = $params{'state'}->get_session_id( $request );
     bless { %params } => $class;
 }
 
 ## Data Managment
 
+sub dump {
+    my $self = shift;
+    $self->{_stash};
+}
+
 sub get {
     my ($self, $key) = @_;
-    $self->store->fetch( $self->id, $key )
+    $self->{_stash}{$key};
 }
 
 sub set {
     my ($self, $key, $value) = @_;
-    $self->store->store( $self->id, $key, $value );
+    $self->{_stash}{$key} = $value;
 }
 
 sub remove {
     my ($self, $key) = @_;
-    $self->store->delete( $self->id, $key );
+    delete $self->{_stash}{$key};
+}
+
+sub keys {
+    my $self = shift;
+    keys %{$self->{_stash}};
 }
 
 ## Lifecycle Management
 
 sub expire {
     my $self = shift;
-    $self->store->cleanup( $self->id );
-    $self->state->expire_session_id( $self->id );
+    $self->{_stash} = {};
+    $self->manager->store->cleanup( $self->id );
+    $self->manager->state->expire_session_id( $self->id );
 }
 
 sub finalize {
     my ($self, $response) = @_;
-    $self->store->persist( $self->id, $response );
-    $self->state->finalize( $self->id, $response );
+    $self->manager->store->store( $self->id, $self );
+    $self->manager->state->finalize( $self->id, $response );
 }
 
 1;
@@ -107,10 +125,11 @@ an object with an equivalent interface.
 
 =back
 
-=head2 Session Data Storage
+=head2 Session Data Management
 
-These methods delegate to appropriate methods on the C<store>
-to manage your session data.
+These methods allows you to read and write the session data like
+Perl's normal hash. The operation is not synced to the storage until
+you call C<finalize> on it.
 
 =over 4
 
@@ -120,6 +139,8 @@ to manage your session data.
 
 =item B<remove ( $key )>
 
+=item B<keys>
+
 =back
 
 =head2 Session Lifecycle Management
@@ -136,7 +157,7 @@ the C<$response>.
 =item B<finalize ( $response )>
 
 This method should be called at the end of the response cycle. It
-will call the C<persist> method on the C<store> and the
+will call the C<store> method on the C<store> and the
 C<expire_session_id> method on the C<state>, passing both of them
 the session id. The C<$response> is expected to be a L<Plack::Response>
 instance or an object with an equivalent interface.
index b9787d0..3632625 100644 (file)
@@ -49,20 +49,13 @@ sub validate_session_id {
 
 sub get_session_id {
     my ($self, $request) = @_;
-    $self->extract( $request )
-        ||
-    $self->generate( $request )
-}
-
-sub get_session_id_from_request {
-    my ($self, $request) = @_;
-    $request->param( $self->session_key );
+    return $request->param( $self->session_key );
 }
 
 sub extract {
     my ($self, $request) = @_;
 
-    my $id = $self->get_session_id_from_request( $request );
+    my $id = $self->get_session_id( $request );
     return unless defined $id;
 
     $self->validate_session_id( $id )
@@ -150,13 +143,6 @@ This is a regex used to validate requested session id.
 
 =item B<get_session_id ( $request )>
 
-Given a C<$request> this will first attempt to extract the session,
-if the is expired or does not exist, it will then generate a new
-session. The C<$request> is expected to be a L<Plack::Request> instance
-or an object with an equivalent interface.
-
-=item B<get_session_id_from_request ( $request )>
-
 This is the method used to extract the session id from a C<$request>.
 Subclasses will often only need to override this method and the
 C<finalize> method.
index 3cff564..f6078d9 100644 (file)
@@ -20,7 +20,7 @@ sub expire_session_id {
     $self->expires( 0 );
 }
 
-sub get_session_id_from_request {
+sub get_session_id {
     my ($self, $request) = @_;
     ( $request->cookie( $self->session_key ) || return )->value;
 }
index cdf7515..4001776 100644 (file)
@@ -14,18 +14,13 @@ sub new {
 }
 
 sub fetch {
-    my ($self, $session_id, $key) = @_;
-    $self->_stash->{ $session_id }->{ $key }
+    my ($self, $session_id) = @_;
+    $self->_stash->{ $session_id };
 }
 
 sub store {
-    my ($self, $session_id, $key, $data) = @_;
-    $self->_stash->{ $session_id }->{ $key } = $data;
-}
-
-sub delete {
-    my ($self, $session_id, $key) = @_;
-    delete $self->_stash->{ $session_id }->{ $key };
+    my ($self, $session_id, $session) = @_;
+    $self->_stash->{ $session_id } = $session->dump;
 }
 
 sub cleanup {
@@ -33,16 +28,6 @@ sub cleanup {
     delete $self->_stash->{ $session_id }
 }
 
-sub persist {
-    my ($self, $session_id, $response) = @_;
-    ()
-}
-
-sub dump_session {
-    my ($self, $session_id) = @_;
-    $self->_stash->{ $session_id } || {};
-}
-
 1;
 
 __END__
@@ -92,16 +77,14 @@ No parameters are expected to this constructor.
 
 =head2 Session Data Management
 
-These methods fetch data from the session storage. It can only fetch,
-store or delete a single key at a time.
+These methods fetch data from the session storage. It's designed to
+store or delete multiple keys at a time.
 
 =over 4
 
-=item B<fetch ( $session_id, $key )>
-
-=item B<store ( $session_id, $key, $data )>
+=item B<fetch ( $session_id )>
 
-=item B<delete ( $session_id, $key )>
+=item B<store ( $session_id, $data )>
 
 =back
 
@@ -109,24 +92,11 @@ store or delete a single key at a time.
 
 =over 4
 
-=item B<persist ( $session_id, $response )>
-
-This method will perform any data persistence nessecary to maintain
-data across requests. This method is called by the L<Plack::Session>
-C<finalize> method. The C<$response> is expected to be a L<Plack::Response>
-instance or an object with an equivalent interface.
-
 =item B<cleanup ( $session_id )>
 
 This method is called by the L<Plack::Session> C<expire> method and
 is used to remove any session data.
 
-=item B<dump_session ( $session_id )>
-
-This method is mostly for debugging purposes, it will always return
-a HASH ref, even if no data is actually being stored (in which case
-the HASH ref will be empty).
-
 =back
 
 =head1 BUGS
index 9018950..3a2397e 100644 (file)
@@ -24,31 +24,13 @@ sub new {
 }
 
 sub fetch {
-    my ($self, $session_id, $key) = @_;
-    my $cache = $self->cache->get($session_id);
-    return unless $cache;
-    return $cache->{ $key };
+    my ($self, $session_id ) = @_;
+    $self->cache->get($session_id);
 }
 
 sub store {
-    my ($self, $session_id, $key, $data) = @_;
-    my $cache = $self->cache->get($session_id);
-    if ( !$cache ) {
-        $cache = {$key => $data};
-    }
-    else {
-        $cache->{$key} = $data;
-    }
-    $self->cache->set($session_id => $cache);
-}
-
-sub delete {
-    my ($self, $session_id, $key) = @_;
-    my $cache = $self->cache->get($session_id);
-    return unless exists $cache->{$key};
-
-    delete $cache->{ $key };
-    $self->cache->set($session_id => $cache);
+    my ($self, $session_id, $session) = @_;
+    $self->cache->set($session_id => $session->dump);
 }
 
 sub cleanup {
@@ -56,11 +38,6 @@ sub cleanup {
     $self->cache->remove($session_id);
 }
 
-sub dump_session {
-    my ($self, $session_id) = @_;
-    $self->cache->get( $session_id ) || {};
-}
-
 1;
 
 __END__
index 2c15ea1..cd33e4a 100644 (file)
@@ -30,25 +30,13 @@ sub new {
 }
 
 sub fetch {
-    my ($self, $session_id, $key) = @_;
-    my $store = $self->_deserialize( $session_id );
-    return unless exists $store->{ $key };
-    return $store->{ $key };
+    my ($self, $session_id) = @_;
+    return $self->_deserialize( $session_id );
 }
 
 sub store {
-    my ($self, $session_id, $key, $data) = @_;
-    my $store = $self->_deserialize( $session_id );
-    $store->{ $key } = $data;
-    $self->_serialize( $session_id, $store );
-}
-
-sub delete {
-    my ($self, $session_id, $key) = @_;
-    my $store = $self->_deserialize( $session_id );
-    return unless exists $store->{ $key };
-    delete $store->{ $key };
-    $self->_serialize( $session_id, $store );
+    my ($self, $session_id, $session) = @_;
+    $self->_serialize( $session_id, $session->dump );
 }
 
 sub cleanup {
@@ -74,14 +62,6 @@ sub _deserialize {
     $self->deserializer->( $file_path );
 }
 
-sub dump_session {
-    my ($self, $session_id) = @_;
-    my $file_path = $self->_get_session_file_path( $session_id );
-    return {} unless -f $file_path;
-    $self->deserializer->( $file_path );
-}
-
-
 1;
 
 __END__
index 329fc9e..cded1ec 100644 (file)
@@ -8,11 +8,7 @@ our $AUTHORITY = 'cpan:STEVAN';
 sub new     { bless {} => shift }
 sub fetch   {}
 sub store   {}
-sub delete  {}
 sub cleanup {}
-sub persist {}
-
-sub dump_session { +{} }
 
 1;
 
old mode 100644 (file)
new mode 100755 (executable)
index 2efba7a..41b7828
@@ -10,9 +10,11 @@ use Plack::Request;
 use Plack::Session;
 use Plack::Session::State;
 use Plack::Session::Store::Null;
+use Plack::Middleware::Session;
 
 my $storage         = Plack::Session::Store::Null->new;
 my $state           = Plack::Session::State->new;
+my $m               = Plack::Middleware::Session->new(store => $storage, state => $state);
 my $request_creator = sub {
     open my $in, '<', \do { my $d };
     my $env = {
@@ -31,11 +33,7 @@ my $request_creator = sub {
 {
     my $r = $request_creator->();
 
-    my $s = Plack::Session->new(
-        state   => $state,
-        store   => $storage,
-        request => $r,
-    );
+    my $s = Plack::Session->fetch_or_create($r, $m);
 
     ok($s->id, '... got a session id');
 
@@ -45,7 +43,7 @@ my $request_creator = sub {
         $s->set( foo => 'bar' );
     } '... set the value successfully in session';
 
-    ok(!$s->get('foo'), '... still no value stored in foo for session (null store)');
+    is($s->get('foo'), 'bar', 'No store, but session works in the session lifetime');
 
     lives_ok {
         $s->remove('foo');
index 579689f..aeb8923 100644 (file)
@@ -4,6 +4,7 @@ use warnings;
 
 use Test::More;
 use Test::Exception;
+use Plack::Middleware::Session;
 
 sub run_all_tests {
     my %params = @_;
@@ -20,6 +21,8 @@ sub run_all_tests {
         response_test
     ]};
 
+    my $m = Plack::Middleware::Session->new(state => $state, store => $storage);
+
     $response_test = sub {
         my ($response, $session_id, $check_expired) = @_;
     };
@@ -28,11 +31,7 @@ sub run_all_tests {
     {
         my $r = $request_creator->();
 
-        my $s = Plack::Session->new(
-            state   => $state,
-            store   => $storage,
-            request => $r,
-        );
+        my $s = Plack::Session->fetch_or_create($r, $m);
 
         push @sids, $s->id;
 
@@ -58,7 +57,7 @@ sub run_all_tests {
             $s->finalize( $resp );
         } '... finalized session successfully';
 
-        is_deeply( $s->store->dump_session( $sids[0] ), { foo => 'bar', bar => 'baz' }, '... got the session dump we expected');
+        is_deeply( $s->dump, { foo => 'bar', bar => 'baz' }, '... got the session dump we expected');
 
         $response_test->( $resp, $sids[0] );
     }
@@ -66,11 +65,7 @@ sub run_all_tests {
     {
         my $r = $request_creator->();
 
-        my $s = Plack::Session->new(
-            state   => $state,
-            store   => $storage,
-            request => $r,
-        );
+        my $s = Plack::Session->fetch_or_create($r, $m);
 
         push @sids, $s->id;
 
@@ -89,7 +84,7 @@ sub run_all_tests {
             $s->finalize( $resp );
         } '... finalized session successfully';
 
-        is_deeply( $s->store->dump_session( $sids[1] ), { foo => 'baz' }, '... got the session dump we expected');
+        is_deeply( $s->dump, { foo => 'baz' }, '... got the session dump we expected');
 
         $response_test->( $resp, $sids[1] );
     }
@@ -97,16 +92,13 @@ sub run_all_tests {
     {
         my $r = $request_creator->({ plack_session => $sids[0] });
 
-        my $s = Plack::Session->new(
-            state   => $state,
-            store   => $storage,
-            request => $r,
-        );
+        my $s = Plack::Session->fetch_or_create($r, $m);
 
         is($s->id, $sids[0], '... got a basic session id');
 
         is($s->get('foo'), 'bar', '... got the value for foo back successfully from session');
 
+
         lives_ok {
             $s->remove( 'foo' );
         } '... removed the foo value successfully from session';
@@ -119,7 +111,7 @@ sub run_all_tests {
             $s->finalize( $resp );
         } '... finalized session successfully';
 
-        is_deeply( $s->store->dump_session( $sids[0] ), { bar => 'baz' }, '... got the session dump we expected');
+        is_deeply( $s->dump, { bar => 'baz' }, '... got the session dump we expected');
 
         $response_test->( $resp, $sids[0] );
     }
@@ -128,11 +120,7 @@ sub run_all_tests {
     {
         my $r = $request_creator->({ plack_session => $sids[1] });
 
-        my $s = Plack::Session->new(
-            state   => $state,
-            store   => $storage,
-            request => $r,
-        );
+        my $s = Plack::Session->fetch_or_create($r, $m);
 
         is($s->id, $sids[1], '... got a basic session id');
 
@@ -144,7 +132,7 @@ sub run_all_tests {
             $s->finalize( $resp );
         } '... finalized session successfully';
 
-        is_deeply( $s->store->dump_session( $sids[1] ), { foo => 'baz' }, '... got the session dump we expected');
+        is_deeply( $s->dump, { foo => 'baz' }, '... got the session dump we expected');
 
         $response_test->( $resp, $sids[1] );
     }
@@ -152,11 +140,7 @@ sub run_all_tests {
     {
         my $r = $request_creator->({ plack_session => $sids[0] });
 
-        my $s = Plack::Session->new(
-            state   => $state,
-            store   => $storage,
-            request => $r,
-        );
+        my $s = Plack::Session->fetch_or_create($r, $m);
 
         is($s->id, $sids[0], '... got a basic session id');
 
@@ -172,7 +156,7 @@ sub run_all_tests {
             $s->finalize( $resp );
         } '... finalized session successfully';
 
-        is_deeply( $s->store->dump_session( $sids[0] ), { bar => 'baz', baz => 'gorch' }, '... got the session dump we expected');
+        is_deeply( $s->dump, { bar => 'baz', baz => 'gorch' }, '... got the session dump we expected');
 
         $response_test->( $resp, $sids[0] );
     }
@@ -180,11 +164,7 @@ sub run_all_tests {
     {
         my $r = $request_creator->({ plack_session => $sids[0] });
 
-        my $s = Plack::Session->new(
-            state   => $state,
-            store   => $storage,
-            request => $r,
-        );
+        my $s = Plack::Session->fetch_or_create($r, $m);
 
         is($s->get('bar'), 'baz', '... got the bar value back successfully from session');
 
@@ -198,7 +178,7 @@ sub run_all_tests {
             $s->finalize( $resp );
         } '... finalized session successfully';
 
-        is_deeply( $s->store->dump_session( $sids[0] ), {}, '... got the session dump we expected');
+        is_deeply( $s->dump, {}, '... got the session dump we expected');
 
         $response_test->( $resp, $sids[0], 1 );
     }
@@ -206,11 +186,7 @@ sub run_all_tests {
     {
         my $r = $request_creator->({ plack_session => $sids[0] });
 
-        my $s = Plack::Session->new(
-            state   => $state,
-            store   => $storage,
-            request => $r,
-        );
+        my $s = Plack::Session->fetch_or_create($r, $m);
 
         push @sids, $s->id;
         isnt($s->id, $sids[0], 'expired ... got a new session id');
@@ -223,7 +199,7 @@ sub run_all_tests {
             $s->finalize( $resp );
         } '... finalized session successfully';
 
-        is_deeply( $s->store->dump_session( $sids[2] ), {}, '... got the session dump we expected');
+        is_deeply( $s->dump, {}, '... got the session dump we expected');
 
         $response_test->( $resp, $sids[2] );
     }
@@ -231,11 +207,7 @@ sub run_all_tests {
     {
         my $r = $request_creator->({ plack_session => $sids[1] });
 
-        my $s = Plack::Session->new(
-            state   => $state,
-            store   => $storage,
-            request => $r,
-        );
+        my $s = Plack::Session->fetch_or_create($r, $m);
 
         is($s->id, $sids[1], '... got a basic session id');
 
@@ -247,7 +219,7 @@ sub run_all_tests {
             $s->finalize( $resp );
         } '... finalized session successfully';
 
-        is_deeply( $s->store->dump_session( $sids[1] ), { foo => 'baz' }, '... got the session dump we expected');
+        is_deeply( $s->dump, { foo => 'baz' }, '... got the session dump we expected');
 
         $response_test->( $resp, $sids[1] );
     }
@@ -256,12 +228,7 @@ sub run_all_tests {
         # wrong format session_id
         my $r = $request_creator->({ plack_session => '../wrong' });
 
-        my $s = Plack::Session->new(
-            state   => $state,
-            store   => $storage,
-            request => $r,
-        );
-
+        my $s = Plack::Session->fetch_or_create($r, $m);
 
         isnt('../wrong' => $s->id, '... regenerate session id');
 
@@ -279,7 +246,7 @@ sub run_all_tests {
             $s->finalize( $resp );
         } '... finalized session successfully';
 
-        is_deeply( $s->store->dump_session( $s->id ), { foo => 'baz' }, '... got the session dump we expected');
+        is_deeply( $s->dump, { foo => 'baz' }, '... got the session dump we expected');
 
         $response_test->( $resp, $s );
     }