Make session extension a more explicit even of State plugins as well
[catagits/Catalyst-Plugin-Session.git] / lib / Catalyst / Plugin / Session.pm
CommitLineData
9e447f9d 1#!/usr/bin/perl
2
3package Catalyst::Plugin::Session;
4use base qw/Class::Accessor::Fast/;
5
6use strict;
7use warnings;
8
9use NEXT;
10use Catalyst::Exception ();
9a9252c2 11use Digest ();
12use overload ();
d44bc687 13use Object::Signature ();
9e447f9d 14
5d8cc4c7 15our $VERSION = "0.05";
37160715 16
ab634fee 17my @session_data_accessors; # used in delete_session
9e447f9d 18BEGIN {
4207ce8d 19 __PACKAGE__->mk_accessors(
ab634fee 20 "_session_delete_reason",
21 @session_data_accessors = qw/
4207ce8d 22 _sessionid
23 _session
24 _session_expires
25 _session_data_sig
4207ce8d 26 _flash
f4d79f85 27 _flash_keep_keys
28 _flash_key_hashes
e5b2372a 29 _tried_loading_session
4207ce8d 30 /
31 );
9e447f9d 32}
33
34sub setup {
9a9252c2 35 my $c = shift;
36
37 $c->NEXT::setup(@_);
38
39 $c->check_session_plugin_requirements;
40 $c->setup_session;
41
42 return $c;
9e447f9d 43}
44
45sub check_session_plugin_requirements {
9a9252c2 46 my $c = shift;
9e447f9d 47
9a9252c2 48 unless ( $c->isa("Catalyst::Plugin::Session::State")
49 && $c->isa("Catalyst::Plugin::Session::Store") )
50 {
51 my $err =
52 ( "The Session plugin requires both Session::State "
53 . "and Session::Store plugins to be used as well." );
9e447f9d 54
9a9252c2 55 $c->log->fatal($err);
56 Catalyst::Exception->throw($err);
57 }
9e447f9d 58}
59
60sub setup_session {
9a9252c2 61 my $c = shift;
9e447f9d 62
9a9252c2 63 my $cfg = ( $c->config->{session} ||= {} );
9e447f9d 64
9a9252c2 65 %$cfg = (
66 expires => 7200,
67 verify_address => 1,
68 %$cfg,
69 );
9e447f9d 70
9a9252c2 71 $c->NEXT::setup_session();
9e447f9d 72}
73
19c130c2 74sub prepare_action {
75 my $c = shift;
76
4207ce8d 77 if ( $c->config->{session}{flash_to_stash}
e5b2372a 78 and $c->sessionid
4207ce8d 79 and my $flash_data = $c->flash )
80 {
19c130c2 81 @{ $c->stash }{ keys %$flash_data } = values %$flash_data;
82 }
83
84 $c->NEXT::prepare_action(@_);
85}
86
9e447f9d 87sub finalize {
9a9252c2 88 my $c = shift;
9e447f9d 89
9b0fa2a6 90 $c->_save_session;
91 $c->_save_flash;
92
93 $c->NEXT::finalize(@_);
94}
95
96sub _save_session {
97 my $c = shift;
4207ce8d 98
e5b2372a 99 if ( my $sid = $c->sessionid ) {
9e447f9d 100
6687905d 101 # all sessions are extended at the end of the request
102 my $now = time;
103
104 if ( my $expires = $c->session_expires ) {
105 $c->store_session_data( "expires:$sid" => $expires );
106 }
7a02371f 107
6687905d 108 if ( my $session_data = $c->_session ) {
d44bc687 109
110 no warnings 'uninitialized';
4207ce8d 111 if ( Object::Signature::signature($session_data) ne
112 $c->_session_data_sig )
113 {
d44bc687 114 $session_data->{__updated} = $now;
115 $c->store_session_data( "session:$sid" => $session_data );
116 }
ea972e9a 117 }
9a9252c2 118 }
9b0fa2a6 119}
9a9252c2 120
9b0fa2a6 121sub _save_flash {
122 my $c = shift;
123
e5b2372a 124 if ( my $sid = $c->sessionid ) {
23fbca00 125 if ( my $flash_data = $c->_flash ) {
f4d79f85 126
127 my $hashes = $c->_flash_key_hashes || {};
128 my $keep = $c->_flash_keep_keys || {};
129 foreach my $key ( keys %$hashes ) {
130 if ( !exists $keep->{$key} and Object::Signature::signature( \$flash_data->{$key} ) eq $hashes->{$key} ) {
131 delete $flash_data->{$key};
132 }
133 }
4207ce8d 134
23fbca00 135 if (%$flash_data) {
136 $c->store_session_data( "flash:$sid", $flash_data );
137 }
138 else {
139 $c->delete_session_data("flash:$sid");
140 }
ea972e9a 141 }
9b0fa2a6 142 }
9e447f9d 143}
144
e5b2372a 145sub _load_session_expires {
146 my $c = shift;
147
148 if ( my $sid = $c->sessionid ) {
149 my $expires = $c->get_session_data("expires:$sid") || 0;
150
151 if ( $expires >= time() ) {
152 return $c->extend_session_expires( $expires );
153 } else {
154 $c->delete_session( "session expired" );
155 return 0;
156 }
157 } else {
158 $c->_session_expires( 0 );
159 return 0;
160 }
161}
162
b7acf64e 163sub _load_session {
164 my $c = shift;
165
e5b2372a 166 if ( my $sid = $c->sessionid ) {
6687905d 167 if ( $c->session_expires ) { # > 0
0974ac06 168
2e412459 169 my $session_data = $c->get_session_data("session:$sid") || return;
6687905d 170 $c->_session($session_data);
3f182468 171
6687905d 172 no warnings 'uninitialized'; # ne __address
173 if ( $c->config->{session}{verify_address}
174 && $session_data->{__address} ne $c->request->address )
175 {
176 $c->log->warn(
177 "Deleting session $sid due to address mismatch ("
178 . $session_data->{__address} . " != "
47ca362e 179 . $c->request->address . ")"
6687905d 180 );
181 $c->delete_session("address mismatch");
182 return;
183 }
4207ce8d 184
6687905d 185 $c->log->debug(qq/Restored session "$sid"/) if $c->debug;
168d6819 186 $c->_session_data_sig( Object::Signature::signature($session_data) ) if $session_data;
6687905d 187 $c->_expire_session_keys;
4207ce8d 188
6687905d 189 return $session_data;
3f182468 190 }
9a9252c2 191 }
29d15411 192
4207ce8d 193 return;
b7acf64e 194}
9a9252c2 195
9b0fa2a6 196sub _load_flash {
197 my $c = shift;
198
e5b2372a 199 if ( my $sid = $c->sessionid ) {
4207ce8d 200 if ( my $flash_data = $c->_flash
201 || $c->_flash( $c->get_session_data("flash:$sid") ) )
202 {
f4d79f85 203 $c->_flash_key_hashes({ map { $_ => Object::Signature::signature( \$flash_data->{$_} ) } keys %$flash_data });
e5b2372a 204
9b0fa2a6 205 return $flash_data;
206 }
207 }
208
209 return undef;
210}
211
d44bc687 212sub _expire_session_keys {
b7acf64e 213 my ( $c, $data ) = @_;
214
215 my $now = time;
216
e5b2372a 217 my $expire_times = ( $data || $c->_session || {} )->{__expire_keys} || {};
218 foreach my $key ( grep { $expire_times->{$_} < $now } keys %$expire_times ) {
b7acf64e 219 delete $c->_session->{$key};
e5b2372a 220 delete $expire_times->{$key};
b7acf64e 221 }
9e447f9d 222}
223
224sub delete_session {
9a9252c2 225 my ( $c, $msg ) = @_;
9e447f9d 226
9a9252c2 227 # delete the session data
e5b2372a 228 my $sid = $c->sessionid || return;
4207ce8d 229 $c->delete_session_data("${_}:${sid}") for qw/session expires flash/;
9e447f9d 230
e5b2372a 231 $c->delete_session_id;
232
9a9252c2 233 # reset the values in the context object
ab634fee 234 # see the BEGIN block
235 $c->$_(undef) for @session_data_accessors;
6687905d 236
29d15411 237 $c->_session_delete_reason($msg);
238}
239
240sub session_delete_reason {
241 my $c = shift;
242
e5b2372a 243 $c->session_is_valid; # check that it was loaded
29d15411 244
4207ce8d 245 $c->_session_delete_reason(@_);
9e447f9d 246}
247
6687905d 248sub session_expires {
e5b2372a 249 my $c = shift;
6687905d 250
e5b2372a 251 if ( defined( my $expires = $c->_session_expires ) ) {
252 return $expires;
253 } else {
254 return $c->_load_session_expires;
255 }
256}
6687905d 257
e5b2372a 258sub extend_session_expires {
259 my ( $c, $expires ) = @_;
e5b2372a 260 $c->_session_expires( my $updated = $c->calculate_extended_session_expires( $expires ) );
4c79feb6 261 $c->extend_session_id( $c->sessionid, $updated );
e5b2372a 262 return $updated;
263}
6687905d 264
e5b2372a 265sub calculate_initial_session_expires {
266 my $c = shift;
267 return ( time() + $c->config->{session}{expires} );
268}
269
270sub calculate_extended_session_expires {
271 my ( $c, $prev ) = @_;
272 $c->calculate_initial_session_expires;
273}
274
275sub reset_session_expires {
276 my ( $c, $sid ) = @_;
277 $c->_session_expires( my $exp = $c->calculate_initial_session_expires );
278 $exp;
6687905d 279}
280
0974ac06 281sub sessionid {
4207ce8d 282 my $c = shift;
e5b2372a 283
284 return $c->_sessionid || do {
285 unless ( $c->_tried_loading_session ) {
286 $c->_tried_loading_session(1);
287 if ( defined( my $sid = $c->get_session_id ) ) {
288 if ( $c->validate_session_id($sid) ) {
289 $c->_sessionid( $sid );
290 return $sid;
291 } else {
292 my $err = "Tried to set invalid session ID '$sid'";
293 $c->log->error($err);
294 Catalyst::Exception->throw($err);
295 }
296 } else {
297 return;
298 }
4207ce8d 299 }
300 }
e5b2372a 301}
302
303sub session_is_valid {
304 my $c = shift;
4207ce8d 305
e5b2372a 306 $c->_load_session unless $c->_session; # check expiry and also __address, etc
4207ce8d 307
e5b2372a 308 if ( $c->_session ) {
309 return 1;
310 } else {
311 return;
312 }
0974ac06 313}
314
315sub validate_session_id {
4207ce8d 316 my ( $c, $sid ) = @_;
0974ac06 317
4207ce8d 318 $sid and $sid =~ /^[a-f\d]+$/i;
0974ac06 319}
320
9e447f9d 321sub session {
9a9252c2 322 my $c = shift;
9e447f9d 323
29d15411 324 $c->_session || $c->_load_session || do {
78476ce0 325 $c->create_session_id;
29d15411 326 $c->initialize_session_data;
4207ce8d 327 };
9e447f9d 328}
329
f4d79f85 330sub keep_flash {
331 my ( $c, @keys ) = @_;
2e412459 332 my $href = $c->_flash_keep_keys || $c->_flash_keep_keys({});
333 (@{$href}{@keys}) = ((undef) x @keys);
f4d79f85 334}
335
873f7011 336sub flash {
337 my $c = shift;
78476ce0 338 $c->_flash || $c->_load_flash || do {
339 $c->create_session_id;
340 $c->_flash( {} );
4207ce8d 341 }
873f7011 342}
343
b7acf64e 344sub session_expire_key {
345 my ( $c, %keys ) = @_;
346
347 my $now = time;
4207ce8d 348 @{ $c->session->{__expire_keys} }{ keys %keys } =
349 map { $now + $_ } values %keys;
b7acf64e 350}
351
9e447f9d 352sub initialize_session_data {
9a9252c2 353 my $c = shift;
9e447f9d 354
9a9252c2 355 my $now = time;
9e447f9d 356
4207ce8d 357 return $c->_session(
358 {
359 __created => $now,
360 __updated => $now,
361
362 (
363 $c->config->{session}{verify_address}
364 ? ( __address => $c->request->address )
365 : ()
366 ),
367 }
368 );
9e447f9d 369}
370
9e447f9d 371sub generate_session_id {
372 my $c = shift;
373
374 my $digest = $c->_find_digest();
375 $digest->add( $c->session_hash_seed() );
376 return $digest->hexdigest;
377}
378
78476ce0 379sub create_session_id {
380 my $c = shift;
381
e5b2372a 382 my $sid = $c->generate_session_id;
78476ce0 383
e5b2372a 384 $c->log->debug(qq/Created session "$sid"/) if $c->debug;
78476ce0 385
e5b2372a 386 $c->_sessionid($sid);
387 $c->reset_session_expires;
388 $c->set_session_id($sid);
389
390 return $sid;
78476ce0 391}
392
9e447f9d 393my $counter;
9a9252c2 394
9e447f9d 395sub session_hash_seed {
9a9252c2 396 my $c = shift;
397
398 return join( "", ++$counter, time, rand, $$, {}, overload::StrVal($c), );
9e447f9d 399}
400
401my $usable;
9a9252c2 402
9e447f9d 403sub _find_digest () {
9a9252c2 404 unless ($usable) {
d44bc687 405 foreach my $alg (qw/SHA-1 SHA-256 MD5/) {
4207ce8d 406 if ( eval { Digest->new($alg) } ) {
5faaa4b0 407 $usable = $alg;
408 last;
409 }
7d139eeb 410 }
4207ce8d 411 Catalyst::Exception->throw(
9a9252c2 412 "Could not find a suitable Digest module. Please install "
4207ce8d 413 . "Digest::SHA1, Digest::SHA, or Digest::MD5" )
414 unless $usable;
9a9252c2 415 }
9e447f9d 416
417 return Digest->new($usable);
418}
419
99b2191e 420sub dump_these {
421 my $c = shift;
422
423 (
424 $c->NEXT::dump_these(),
425
426 $c->sessionid
427 ? ( [ "Session ID" => $c->sessionid ], [ Session => $c->session ], )
428 : ()
429 );
430}
431
e5b2372a 432
433sub get_session_id { shift->NEXT::get_session_id(@_) }
434sub set_session_id { shift->NEXT::set_session_id(@_) }
435sub delete_session_id { shift->NEXT::delete_session_id(@_) }
4c79feb6 436sub extend_session_id { shift->NEXT::extend_session_id(@_) }
e5b2372a 437
9e447f9d 438__PACKAGE__;
439
440__END__
441
442=pod
443
444=head1 NAME
445
446Catalyst::Plugin::Session - Generic Session plugin - ties together server side
fb1a4ac3 447storage and client side state required to maintain session data.
9e447f9d 448
449=head1 SYNOPSIS
450
8f0b4c16 451 # To get sessions to "just work", all you need to do is use these plugins:
452
453 use Catalyst qw/
454 Session
455 Session::Store::FastMmap
456 Session::State::Cookie
457 /;
458
459 # you can replace Store::FastMmap with Store::File - both have sensible
460 # default configurations (see their docs for details)
461
462 # more complicated backends are available for other scenarios (DBI storage,
463 # etc)
464
465
466 # after you've loaded the plugins you can save session data
467 # For example, if you are writing a shopping cart, it could be implemented
468 # like this:
9e447f9d 469
229a5b53 470 sub add_item : Local {
471 my ( $self, $c ) = @_;
472
473 my $item_id = $c->req->param("item");
474
8f0b4c16 475 # $c->session is a hash ref, a bit like $c->stash
476 # the difference is that it' preserved across requests
229a5b53 477
478 push @{ $c->session->{items} }, $item_id;
479
480 $c->forward("MyView");
481 }
482
483 sub display_items : Local {
484 my ( $self, $c ) = @_;
485
486 # values in $c->session are restored
487 $c->stash->{items_to_display} =
8f0b4c16 488 [ map { MyModel->retrieve($_) } @{ $c->session->{items} } ];
229a5b53 489
490 $c->forward("MyView");
491 }
492
9e447f9d 493=head1 DESCRIPTION
494
495The Session plugin is the base of two related parts of functionality required
496for session management in web applications.
497
498The first part, the State, is getting the browser to repeat back a session key,
499so that the web application can identify the client and logically string
500several requests together into a session.
501
502The second part, the Store, deals with the actual storage of information about
503the client. This data is stored so that the it may be revived for every request
504made by the same client.
505
506This plugin links the two pieces together.
507
8f0b4c16 508=head1 RECCOMENDED BACKENDS
509
510=over 4
511
512=item Session::State::Cookie
513
514The only really sane way to do state is using cookies.
515
516=item Session::Store::File
517
518A portable backend, based on Cache::File.
519
520=item Session::Store::FastMmap
521
522A fast and flexible backend, based on Cache::FastMmap.
523
524=back
525
9e447f9d 526=head1 METHODS
527
528=over 4
529
530=item sessionid
531
532An accessor for the session ID value.
533
534=item session
535
536Returns a hash reference that might contain unserialized values from previous
537requests in the same session, and whose modified value will be saved for future
538requests.
539
540This method will automatically create a new session and session ID if none
541exists.
542
ab634fee 543=item session_expires
544
545=item session_expires $reset
546
547This method returns the time when the current session will expire, or 0 if
548there is no current session. If there is a session and it already expired, it
549will delete the session and return 0 as well.
550
551If the C<$reset> parameter is true, and there is a session ID the expiry time
552will be reset to the current time plus the time to live (see
553L</CONFIGURATION>). This is used when creating a new session.
554
07e714d2 555=item flash
556
557This is like Ruby on Rails' flash data structure. Think of it as a stash that
44ab6d1c 558lasts for longer than one request, letting you redirect instead of forward.
559
560The flash data will be cleaned up only on requests on which actually use
561$c->flash (thus allowing multiple redirections), and the policy is to delete
bf6bd311 562all the keys which haven't changed since the flash data was loaded at the end
563of every request.
07e714d2 564
565 sub moose : Local {
566 my ( $self, $c ) = @_;
567
568 $c->flash->{beans} = 10;
569 $c->response->redirect( $c->uri_for("foo") );
570 }
571
572 sub foo : Local {
573 my ( $self, $c ) = @_;
574
575 my $value = $c->flash->{beans};
576
577 # ...
578
579 $c->response->redirect( $c->uri_for("bar") );
580 }
581
582 sub bar : Local {
583 my ( $self, $c ) = @_;
584
585 if ( exists $c->flash->{beans} ) { # false
586
587 }
588 }
589
bf6bd311 590=item keep_flash @keys
591
592If you wawnt to keep a flash key for the next request too, even if it hasn't
593changed, call C<keep_flash> and pass in the keys as arguments.
594
9e447f9d 595=item session_delete_reason
596
597This accessor contains a string with the reason a session was deleted. Possible
598values include:
599
600=over 4
601
602=item *
603
604C<address mismatch>
605
606=item *
607
608C<session expired>
609
610=back
611
b7acf64e 612=item session_expire_key $key, $ttl
613
614Mark a key to expire at a certain time (only useful when shorter than the
615expiry time for the whole session).
616
617For example:
618
619 __PACKAGE__->config->{session}{expires} = 1000000000000; # forever
620
621 # later
622
623 $c->session_expire_key( __user => 3600 );
624
625Will make the session data survive, but the user will still be logged out after
626an hour.
627
628Note that these values are not auto extended.
629
8f0b4c16 630=back
631
10c72079 632=head1 INTERNAL METHODS
8f0b4c16 633
634=over 4
635
9e447f9d 636=item setup
637
638This method is extended to also make calls to
639C<check_session_plugin_requirements> and C<setup_session>.
640
641=item check_session_plugin_requirements
642
643This method ensures that a State and a Store plugin are also in use by the
644application.
645
646=item setup_session
647
648This method populates C<< $c->config->{session} >> with the default values
649listed in L</CONFIGURATION>.
650
651=item prepare_action
652
68fd02ae 653This methoid is extended.
654
655It's only effect is if the (off by default) C<flash_to_stash> configuration
656parameter is on - then it will copy the contents of the flash to the stash at
657prepare time.
9e447f9d 658
659=item finalize
660
661This method is extended and will extend the expiry time, as well as persist the
662session data if a session exists.
663
664=item delete_session REASON
665
666This method is used to invalidate a session. It takes an optional parameter
667which will be saved in C<session_delete_reason> if provided.
668
669=item initialize_session_data
670
671This method will initialize the internal structure of the session, and is
672called by the C<session> method if appropriate.
673
68fd02ae 674=item create_session_id
675
676Creates a new session id using C<generate_session_id> if there is no session ID
677yet.
678
ab634fee 679=item validate_session_id SID
680
681Make sure a session ID is of the right format.
682
683This currently ensures that the session ID string is any amount of case
684insensitive hexadecimal characters.
685
229a5b53 686=item generate_session_id
687
688This method will return a string that can be used as a session ID. It is
689supposed to be a reasonably random string with enough bits to prevent
690collision. It basically takes C<session_hash_seed> and hashes it using SHA-1,
691MD5 or SHA-256, depending on the availibility of these modules.
692
693=item session_hash_seed
694
695This method is actually rather internal to generate_session_id, but should be
696overridable in case you want to provide more random data.
697
698Currently it returns a concatenated string which contains:
699
700=over 4
701
702=item *
703
704A counter
705
706=item *
707
708The current time
709
710=item *
711
712One value from C<rand>.
713
714=item *
715
716The stringified value of a newly allocated hash reference
717
718=item *
719
720The stringified value of the Catalyst context object
721
722=back
723
724In the hopes that those combined values are entropic enough for most uses. If
725this is not the case you can replace C<session_hash_seed> with e.g.
726
727 sub session_hash_seed {
728 open my $fh, "<", "/dev/random";
729 read $fh, my $bytes, 20;
730 close $fh;
731 return $bytes;
732 }
733
734Or even more directly, replace C<generate_session_id>:
735
736 sub generate_session_id {
737 open my $fh, "<", "/dev/random";
738 read $fh, my $bytes, 20;
739 close $fh;
740 return unpack("H*", $bytes);
741 }
742
743Also have a look at L<Crypt::Random> and the various openssl bindings - these
744modules provide APIs for cryptographically secure random data.
745
99b2191e 746=item dump_these
747
748See L<Catalyst/dump_these> - ammends the session data structure to the list of
749dumped objects if session ID is defined.
750
9e447f9d 751=back
752
a92c8aeb 753=head1 USING SESSIONS DURING PREPARE
754
755The earliest point in time at which you may use the session data is after
756L<Catalyst::Plugin::Session>'s C<prepare_action> has finished.
757
758State plugins must set $c->session ID before C<prepare_action>, and during
759C<prepare_action> L<Catalyst::Plugin::Session> will actually load the data from
760the store.
761
762 sub prepare_action {
763 my $c = shift;
764
765 # don't touch $c->session yet!
b1cd7d77 766
a92c8aeb 767 $c->NEXT::prepare_action( @_ );
768
769 $c->session; # this is OK
770 $c->sessionid; # this is also OK
771 }
772
9e447f9d 773=head1 CONFIGURATION
774
229a5b53 775 $c->config->{session} = {
776 expires => 1234,
777 };
9e447f9d 778
779All configuation parameters are provided in a hash reference under the
780C<session> key in the configuration hash.
781
782=over 4
783
784=item expires
785
786The time-to-live of each session, expressed in seconds. Defaults to 7200 (two
787hours).
788
789=item verify_address
790
8c7e922c 791When true, C<<$c->request->address>> will be checked at prepare time. If it is
792not the same as the address that initiated the session, the session is deleted.
9e447f9d 793
68fd02ae 794=item flash_to_stash
795
796This option makes it easier to have actions behave the same whether they were
797forwarded to or redirected to. On prepare time it copies the contents of
798C<flash> (if any) to the stash.
799
9e447f9d 800=back
801
802=head1 SPECIAL KEYS
803
804The hash reference returned by C<< $c->session >> contains several keys which
805are automatically set:
806
807=over 4
808
809=item __expires
810
ab634fee 811This key no longer exists. Use C<session_expires> instead.
9e447f9d 812
813=item __updated
814
d44bc687 815The last time a session was saved to the store.
9e447f9d 816
817=item __created
818
819The time when the session was first created.
820
821=item __address
822
823The value of C<< $c->request->address >> at the time the session was created.
8c7e922c 824This value is only populated if C<verify_address> is true in the configuration.
9e447f9d 825
826=back
827
c80e9f04 828=head1 CAVEATS
829
a552e4b5 830=head2 Round the Robin Proxies
831
c80e9f04 832C<verify_address> could make your site inaccessible to users who are behind
833load balanced proxies. Some ISPs may give a different IP to each request by the
834same client due to this type of proxying. If addresses are verified these
835users' sessions cannot persist.
836
837To let these users access your site you can either disable address verification
838as a whole, or provide a checkbox in the login dialog that tells the server
839that it's OK for the address of the client to change. When the server sees that
840this box is checked it should delete the C<__address> sepcial key from the
841session hash when the hash is first created.
842
a552e4b5 843=head2 Race Conditions
844
845In this day and age where cleaning detergents and dutch football (not the
846american kind) teams roam the plains in great numbers, requests may happen
847simultaneously. This means that there is some risk of session data being
848overwritten, like this:
849
850=over 4
851
852=item 1.
853
854request a starts, request b starts, with the same session id
855
856=item 2.
857
858session data is loaded in request a
859
860=item 3.
861
862session data is loaded in request b
863
864=item 4.
865
866session data is changed in request a
867
868=item 5.
869
870request a finishes, session data is updated and written to store
871
872=item 6.
873
874request b finishes, session data is updated and written to store, overwriting
875changes by request a
876
877=back
878
879If this is a concern in your application, a soon to be developed locking
880solution is the only safe way to go. This will have a bigger overhead.
881
882For applications where any given user is only making one request at a time this
883plugin should be safe enough.
884
d45028d6 885=head1 AUTHORS
886
baa9db9c 887=over 4
888
889=item Andy Grundman
890
891=item Christian Hansen
892
893=item Yuval Kogman, C<nothingmuch@woobling.org> (current maintainer)
894
895=item Sebastian Riedel
896
897=back
898
899And countless other contributers from #catalyst. Thanks guys!
d45028d6 900
cc40ae4b 901=head1 COPYRIGHT & LICENSE
d45028d6 902
903 Copyright (c) 2005 the aforementioned authors. All rights
904 reserved. This program is free software; you can redistribute
905 it and/or modify it under the same terms as Perl itself.
906
9e447f9d 907=cut
908
909