[Catalyst] Session trouble

Bill Moseley moseley at hank.org
Fri Jul 13 07:25:23 GMT 2007


I'm bringing this over from a discussion on IRC with nothingmuch.

All up-to-date as of yesterday (fresh install on new machine).

I set a flag in the session that adjusts how long cookies persist.
Then override this method in my application:

    sub calculate_session_cookie_expires {
        my $c = shift;

        return $c->session->{remember_me}
            ? $c->session_expires
            : $c->NEXT::calculate_session_cookie_expires;
    }



If I wait a while when the session expires (but the cookie hasn't
expired yet) I get this behavior:

    1) session expired.
    2) request comes in with expired session id in the cookie.
    3) session/cookie cleared
    4) new session created
    5) cookie set in headers
    6) session data cleared.
    7) session data requested -- but session data already clear
    8) so, create new session id
    9) write session to store, but using new session id, not
      one set in cookie header.


Here's a Cat application to duplicate (which I've been able to do on
two machines).

The trick to make it fail is to have an invalid cookie.  You can
generate a few requests and see that the same cookie is sent each
time.

Then edit your browser's cookie to make it invalid (change a single
digit).  Then reload /bar and watch how a new cookie is generated each
time.

(Note: If the cookie is an invalid *format* you get:
[error] Caught exception in Foo->bar "Tried to set invalid session ID '7xxx0da4dfba9790d232f9dfc479ecc23bc0c2d83db' at /usr/local/share/perl/5.8.8/Catalyst/Action.pm line 47"
which is another problem.  Seems like should just ignore it and set a
new cookie.)


package Foo;
use strict;
use warnings;
use Catalyst::Runtime;
use Catalyst (
    'Session',
    'Session::State::Cookie',
    'Session::Store::FastMmap',
    'Cache::FastMmap',
);
__PACKAGE__->config( name => 'Foo' );
__PACKAGE__->setup;

__PACKAGE__->config->{session} = {
    cookie_expires => 0,   # Session cookie
    expires => 604800,
    cookie_name => 'my_cookie',
};

sub bar : Local {
    my ( $self, $c ) = @_;
    $c->session->{stuff} = 'keep this';
    $c->res->body('in bar');
}

sub calculate_session_cookie_expires {
    my $c = shift;

    warn ">>>>>App::calculate_session_cookie_expires\n";

    my $x = $c->session->{foo};  # force a reload of the session.

    return $c->NEXT::calculate_session_cookie_expires;
}

sub finalize_body {
    my $c = shift;


    warn join( "\n",
        '','------ finalize_body -----------------',
        $c->req->path,
        'Request:', $c->req->headers->as_string,
        'Response:', $c->res->headers->as_string,
    );


    return $c->NEXT::finalize_body( @_ );
}

1;



Here's an annotated dump of a single request in the session code.
Note also how many sessions are created.



Request Cookie: my_cookie=810906f7a29c3f46f8b61dcf564f426c5209b254

# Now try and fetch the expired session:

Session::_load_session: get_session_id returned: 810906f7a29c3f46f8b61dcf564f426c5209b254
Store::get_session_data: fetched [expires:810906f7a29c3f46f8b61dcf564f426c5209b254]: [undef]
Session::_load_session_expires: expires = 0 810906f7a29c3f46f8b61dcf564f426c5209b254.

# Session is expired so purge it:

 calling delete_session()<<
State::Cookie::update_session_cookie: setting cookie {
    expires => 0,
    value => "810906f7a29c3f46f8b61dcf564f426c5209b254",
}
Session::_clear_session_instance_data
>>
Session::_load_session_expires: after calling delete_session

# And create a new session:

Session::session(): creating session
Session::generate_session_id = 6d30ea44e84f658ae647f249bd7237e8a117740b

# Now my overridden calculate_session_cookie_expires() is called:

>>>>> App::calculate_session_cookie_expires

# Which triggers a session fetch, but there is no session data yet:

Store::get_session_data: fetched [expires:6d30ea44e84f658ae647f249bd7237e8a117740b]: [undef]
Session::_load_session_expires: expires = 0 6d30ea44e84f658ae647f249bd7237e8a117740b.


# So it's considered expired and deletes the session again:

 calling delete_session()<<
State::Cookie::update_session_cookie: setting cookie { expires => 0, value => "6d30ea44e84f658ae647f249bd7237e8a117740b" }
Session::_clear_session_instance_data
>>
Session::_load_session_expires: after calling delete_session


# And yet another session is created and the cookie is set:


Session::session(): creating session
Session::generate_session_id = 4cc5a1526a6958df043edfcd77de025ece5e6334

# But it's the previous session id:

State::Cookie::update_session_cookie: setting cookie {
  expires => undef,
  value   => "6d30ea44e84f658ae647f249bd7237e8a117740b",
}
Session::prepare_action start for path: login

# And when the data is finally stored it's using the latest session
id, but not the same one used for the cookie:

Store::store_session_data: setting [session:4cc5a1526a6958df043edfcd77de025ece5e6334] [HASH(0x9d937e8)]
Store::store_session_data: setting [expires:4cc5a1526a6958df043edfcd77de025ece5e6334] [1184906823]
------- Finalize body -------

# And here's the cookie in the response headers.  Matches the last
update_session_cookie() but not the session written to the store.

Set-Cookie: my_cookie=6d30ea44e84f658ae647f249bd7237e8a117740b; path=/





-- 
Bill Moseley
moseley at hank.org




More information about the Catalyst mailing list