Refactored Middleware::Session to call ->state and ->store inside more
[catagits/Web-Session.git] / lib / Plack / Middleware / Session.pm
1 package Plack::Middleware::Session;
2 use strict;
3 use warnings;
4
5 our $VERSION   = '0.03';
6 our $AUTHORITY = 'cpan:STEVAN';
7
8 use Plack::Request;
9 use Plack::Response;
10 use Plack::Util;
11 use Scalar::Util;
12
13 use parent 'Plack::Middleware';
14
15 use Plack::Util::Accessor qw(
16     state
17     store
18     session_class
19 );
20
21 sub prepare_app {
22     my $self = shift;
23
24     $self->state( 'Cookie' ) unless $self->state;
25     $self->state( $self->inflate_backend('Plack::Session::State', $self->state) );
26     $self->store( $self->inflate_backend('Plack::Session::Store', $self->store) );
27
28     Plack::Util::load_class($self->session_class) if $self->session_class;
29 }
30
31 sub inflate_backend {
32     my($self, $prefix, $backend) = @_;
33
34     return $backend if defined $backend && Scalar::Util::blessed $backend;
35
36     my @class;
37     push @class, $backend if defined $backend; # undef means the root class
38     push @class, $prefix;
39
40     Plack::Util::load_class(@class)->new();
41 }
42
43 sub call {
44     my $self = shift;
45     my $env  = shift;
46
47     my $request = Plack::Request->new($env);
48
49     my($id, $session) = $self->get_session($request);
50     if ($id && $session) {
51         $env->{'psgix.session'} = $session;
52     } else {
53         $id = $self->generate_id($request);
54         $env->{'psgix.session'} = {};
55     }
56
57     $env->{'psgix.session.options'} = { id => $id };
58
59     if ($self->session_class) {
60         $env->{'plack.session'} = $self->session_class->new($env);
61     }
62
63     my $res = $self->app->($env);
64     $self->response_cb($res, sub {
65         my $res = Plack::Response->new(@{$_[0]});
66         $self->finalize($request, $res);
67         $res = $res->finalize;
68         $_[0]->[0] = $res->[0];
69         $_[0]->[1] = $res->[1];
70     });
71 }
72
73 sub get_session {
74     my($self, $request) = @_;
75
76     my $id = $self->state->extract($request) or return;
77     my $session = $self->store->fetch($id)   or return;
78
79     return ($id, $session);
80 }
81
82 sub generate_id {
83     my($self, $request) = @_;
84     $self->state->generate($request);
85 }
86
87 sub commit {
88     my($self, $session, $options) = @_;
89     if ($options->{expire}) {
90         $self->store->cleanup($options->{id});
91     } else {
92         $self->store->store($options->{id}, $session);
93     }
94 }
95
96 sub finalize {
97     my($self, $request, $response) = @_;
98
99     my $session = $request->env->{'psgix.session'};
100     my $options = $request->env->{'psgix.session.options'};
101
102     $self->commit($session, $options) unless $options->{no_store};
103     if ($options->{expire}) {
104         $self->expire_session($options->{id}, $response, $session, $options);
105     } else {
106         $self->save_state($options->{id}, $response, $session, $options);
107     }
108 }
109
110 sub expire_session {
111     my($self, $id, $res, $session, $options) = @_;
112     $self->state->expire_session_id($options->{id}, $res, $options);
113 }
114
115 sub save_state {
116     my($self, $id, $res, $session, $options) = @_;
117     $self->state->finalize($id, $res, $options);
118 }
119
120 1;
121
122 __END__
123
124 =pod
125
126 =head1 NAME
127
128 Plack::Middleware::Session - Middleware for session management
129
130 =head1 SYNOPSIS
131
132   use Plack::Builder;
133
134   my $app = sub {
135       my $env = shift;
136       my $session = $env->{'psgix.session'};
137       return [
138           200,
139           [ 'Content-Type' => 'text/plain' ],
140           [ "Hello, you've been here for ", $session->{counter}++, "th time!" ],
141       ];
142   };
143
144   builder {
145       enable 'Session';
146       $app;
147   };
148
149   # Or, use the File store backend (great if you use multiprocess server)
150   # For more options, see perldoc Plack::Session::Store::File
151   builder {
152       enable 'Session', store => 'File';
153       $app;
154   };
155
156 =head1 DESCRIPTION
157
158 This is a Plack Middleware component for session management. By
159 default it will use cookies to keep session state and store data in
160 memory. This distribution also comes with other state and store
161 solutions. See perldoc for these backends how to use them.
162
163 It should be noted that we store the current session as a hash
164 reference in the C<psgix.session> key inside the C<$env> where you can
165 access it as needed.
166
167 B<NOTE:> As of version 0.04 the session is stored in C<psgix.session>
168 instead of C<plack.session>.
169
170 Also, if you set I<session_class> option (see below), we create a
171 session object out of the hash reference in C<plack.session>.
172
173 =head2 State
174
175 =over 4
176
177 =item L<Plack::Session::State>
178
179 This will maintain session state by passing the session through
180 the request params. It does not do this automatically though,
181 you are responsible for passing the session param.
182
183 =item L<Plack::Session::State::Cookie>
184
185 This will maintain session state using browser cookies.
186
187 =back
188
189 =head2 Store
190
191 =over 4
192
193 =item L<Plack::Session::Store>
194
195 This is your basic in-memory session data store. It is volatile storage
196 and not recommended for multiprocessing environments. However it is
197 very useful for development and testing.
198
199 =item L<Plack::Session::Store::File>
200
201 This will persist session data in a file. By default it uses
202 L<Storable> but it can be configured to have a custom serializer and
203 deserializer.
204
205 =item L<Plack::Session::Store::Cache>
206
207 This will persist session data using the L<Cache> interface.
208
209 =item L<Plack::Session::Store::Null>
210
211 Sometimes you don't care about storing session data, in that case
212 you can use this noop module.
213
214 =back
215
216 =head1 OPTIONS
217
218 The following are options that can be passed to this mdoule.
219
220 =over 4
221
222 =item I<state>
223
224 This is expected to be an instance of L<Plack::Session::State> or an
225 object that implements the same interface. If no option is provided
226 the default L<Plack::Session::State::Cookie> will be used.
227
228 =item I<store>
229
230 This is expected to be an instance of L<Plack::Session::Store> or an
231 object that implements the same interface. If no option is provided
232 the default L<Plack::Session::Store> will be used.
233
234 It should be noted that this default is an in-memory volatile store
235 is only suitable for development (or single process servers). For a
236 more robust solution see L<Plack::Session::Store::File> or
237 L<Plack::Session::Store::Cache>.
238
239 =item I<session_class>
240
241 This can be used to create an actual session object in
242 C<plack.session> environment. Defaults to none, which means the
243 session object is not created but you can set C<Plack::Session> to
244 create an object for you.
245
246 =back
247
248 =head1 BUGS
249
250 All complex software has bugs lurking in it, and this module is no
251 exception. If you find a bug please either email me, or add the bug
252 to cpan-RT.
253
254 =head1 AUTHOR
255
256 Tatsuhiko Miyagawa
257
258 Stevan Little E<lt>stevan.little@iinteractive.comE<gt>
259
260 =head1 COPYRIGHT AND LICENSE
261
262 Copyright 2009, 2010 Infinity Interactive, Inc.
263
264 L<http://www.iinteractive.com>
265
266 This library is free software; you can redistribute it and/or modify
267 it under the same terms as Perl itself.
268
269 =cut
270
271