Pass $env in more Middleware APIs so subclasses can do more things flexibly in the...
[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($env, $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, $env) = @_;
89
90     my $session = $env->{'psgix.session'};
91     my $options = $env->{'psgix.session.options'};
92
93     if ($options->{expire}) {
94         $self->store->remove($options->{id});
95     } else {
96         $self->store->store($options->{id}, $session);
97     }
98 }
99
100 sub finalize {
101     my($self, $env, $response) = @_;
102
103     my $session = $env->{'psgix.session'};
104     my $options = $env->{'psgix.session.options'};
105
106     $self->commit($env) unless $options->{no_store};
107     if ($options->{expire}) {
108         $self->expire_session($options->{id}, $response, $env);
109     } else {
110         $self->save_state($options->{id}, $response, $env);
111     }
112 }
113
114 sub expire_session {
115     my($self, $id, $res, $env) = @_;
116     $self->state->expire_session_id($id, $res, $env->{'psgix.session.options'});
117 }
118
119 sub save_state {
120     my($self, $id, $res, $env) = @_;
121     $self->state->finalize($id, $res, $env->{'psgix.session.options'});
122 }
123
124 1;
125
126 __END__
127
128 =pod
129
130 =head1 NAME
131
132 Plack::Middleware::Session - Middleware for session management
133
134 =head1 SYNOPSIS
135
136   use Plack::Builder;
137
138   my $app = sub {
139       my $env = shift;
140       my $session = $env->{'psgix.session'};
141       return [
142           200,
143           [ 'Content-Type' => 'text/plain' ],
144           [ "Hello, you've been here for ", $session->{counter}++, "th time!" ],
145       ];
146   };
147
148   builder {
149       enable 'Session';
150       $app;
151   };
152
153   # Or, use the File store backend (great if you use multiprocess server)
154   # For more options, see perldoc Plack::Session::Store::File
155   builder {
156       enable 'Session', store => 'File';
157       $app;
158   };
159
160 =head1 DESCRIPTION
161
162 This is a Plack Middleware component for session management. By
163 default it will use cookies to keep session state and store data in
164 memory. This distribution also comes with other state and store
165 solutions. See perldoc for these backends how to use them.
166
167 It should be noted that we store the current session as a hash
168 reference in the C<psgix.session> key inside the C<$env> where you can
169 access it as needed.
170
171 B<NOTE:> As of version 0.04 the session is stored in C<psgix.session>
172 instead of C<plack.session>.
173
174 Also, if you set I<session_class> option (see below), we create a
175 session object out of the hash reference in C<plack.session>.
176
177 =head2 State
178
179 =over 4
180
181 =item L<Plack::Session::State>
182
183 This will maintain session state by passing the session through
184 the request params. It does not do this automatically though,
185 you are responsible for passing the session param.
186
187 =item L<Plack::Session::State::Cookie>
188
189 This will maintain session state using browser cookies.
190
191 =back
192
193 =head2 Store
194
195 =over 4
196
197 =item L<Plack::Session::Store>
198
199 This is your basic in-memory session data store. It is volatile storage
200 and not recommended for multiprocessing environments. However it is
201 very useful for development and testing.
202
203 =item L<Plack::Session::Store::File>
204
205 This will persist session data in a file. By default it uses
206 L<Storable> but it can be configured to have a custom serializer and
207 deserializer.
208
209 =item L<Plack::Session::Store::Cache>
210
211 This will persist session data using the L<Cache> interface.
212
213 =item L<Plack::Session::Store::Null>
214
215 Sometimes you don't care about storing session data, in that case
216 you can use this noop module.
217
218 =back
219
220 =head1 OPTIONS
221
222 The following are options that can be passed to this mdoule.
223
224 =over 4
225
226 =item I<state>
227
228 This is expected to be an instance of L<Plack::Session::State> or an
229 object that implements the same interface. If no option is provided
230 the default L<Plack::Session::State::Cookie> will be used.
231
232 =item I<store>
233
234 This is expected to be an instance of L<Plack::Session::Store> or an
235 object that implements the same interface. If no option is provided
236 the default L<Plack::Session::Store> will be used.
237
238 It should be noted that this default is an in-memory volatile store
239 is only suitable for development (or single process servers). For a
240 more robust solution see L<Plack::Session::Store::File> or
241 L<Plack::Session::Store::Cache>.
242
243 =item I<session_class>
244
245 This can be used to create an actual session object in
246 C<plack.session> environment. Defaults to none, which means the
247 session object is not created but you can set C<Plack::Session> to
248 create an object for you.
249
250 =back
251
252 =head1 BUGS
253
254 All complex software has bugs lurking in it, and this module is no
255 exception. If you find a bug please either email me, or add the bug
256 to cpan-RT.
257
258 =head1 AUTHOR
259
260 Tatsuhiko Miyagawa
261
262 Stevan Little E<lt>stevan.little@iinteractive.comE<gt>
263
264 =head1 COPYRIGHT AND LICENSE
265
266 Copyright 2009, 2010 Infinity Interactive, Inc.
267
268 L<http://www.iinteractive.com>
269
270 This library is free software; you can redistribute it and/or modify
271 it under the same terms as Perl itself.
272
273 =cut
274
275