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