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