Apache2-Controller
view release on metacpan or search on metacpan
lib/Apache2/Controller/Session.pm view on Meta::CPAN
sub get_options {
my ($self) = @_;
my $r = $self->{r};
eval {
$r->pnotes->{a2c}{dbh} ||= DBI->connect(
'dbi:mysql:database=myapp;host=mydbhost';
'myuser', 'mypassword'
);
};
a2cx "cannot connect to DB: $EVAL_ERROR" if $EVAL_ERROR;
my $dbh = $r->pnotes->{a2c}{dbh}; # save handle for later use
# in controllers, etc.
return {
Handle => $dbh,
LockHandle => $dbh,
};
}
If you do it this way or use Apache::DBI,
be careful about transactions. See L<DATABASE TRANSACTION SAFETY> below.
# ...
In your controller module, access the session in C<< pnotes->{a2c}{session} >>.
package MyApp::Controller::SomeWhere::Overtherainbow;
use base qw( Apache2::Controller Apache2::Request );
# ...
sub default {
my ($self) = @_;
my $session = $self->pnotes->{a2c}{session};
$session->{foo} = 'bar';
# session will be saved by a PerlLogHandler
# that was automatically pushed by Apache2::Controller::Session
# and in my example
return Apache2::Const::HTTP_OK;
}
=head1 DATABASE TRANSACTION SAFETY
When this handler runs, it ties the session into a special
hash that it keeps internally, and loads a copy into
C<< $r->pnotes->{a2c}{session} >>. So, modifying the session hash
is fine, as long as you do not dereference it, or as long
as you save your changes back to C<< $r->pnotes->{a2c}{session} >>.
No changes are auto-committed. The one in pnotes is
copied back into the tied session hash in a C<PerlLogHandler>,
after the server finishes output but I<before> it closes
the connection to the client. If the connection is detected
to be aborted in the C<PerlLogHandler> phase, changes are NOT
saved into the session object.
If you implemented C<get_options()> as per above and decided
to save your $dbh for later use in your controllers, feel free
to start transactions and use them normally. Just make sure you
use L<perlfunc/eval> correctly and roll back or commit your
transactions.
If you decide to push a C<PerlLogHandler>
to roll back transactions for broken connections or something,
or C<PerlCleanupHandler> to do something else (don't use
post-connection phases for database transactions or you'll get out of sync),
be aware
that this handler 'unshifts' a log handler closure that
saves the copy in pnotes back into the tied hash.
It does this by re-ordering the C<PerlLogHandler> stack with
L<Apache2::RequestUtil/get_handlers> and C<set_handlers()>.
So if you push another post-response handler that wants to
choose whether to save the session or not, be aware that
it may not work as you expect unless you re-order that
phase's handler stack again.
=head1 TO SAVE OR NOT TO SAVE
Generally in your code, it's complicated to decide whether everything
has worked before you save anything to the session. It's easier just
to save stuff, and then if something goes wrong, it is as if this
rolls back.
A C<PerlLogHandler> subroutine is 'unshifted' to the request stack
which decides whether to save changes to the session. By default,
it saves changes only if A) the connection is not aborted,
and B) your controller set HTTP status < 300,
i.e. it returned C<OK> (0), one of the C<HTTP_CONTINUE> family (100+)
or one of the C<HTTP_OK> family (200+).
So for an C<HTTP_SERVER_ERROR>, or throwing an exception, redirecting,
forbidding access, etc (>= 300), it normally would not save changes.
If your L<Apache2::Controller> controller module returns one of these
non-OK statuses, but you want to force the saving of the session contents,
set C<< $self->pnotes->{a2c}{session_force_save} = 1 >> before
your response phase controller returns a status to L<Apache2::Controller>.
If the connection is aborted mid-way (i.e. the pipe was broken
due to a network failure or the user clicked 'stop'
in the browser), then the session will not be saved,
whether you set the force save flag or not.
(If this is not useful and correct behavior contact me and I
will add another switch, but it seems right to me.)
It actually re-orders the C<PerlLogHandler> stack so that
its handlers run first, before the handler pushed by
L<Apache2::Controller::DBI::Connector> commits the database
transaction, for example.
This used to push a C<PerlCleanupHandler> to save the session,
which made sense at the time, but the OpenID auth tests revealed
that the Cleanup handler is apparently assigned a thread to
process it independently, even under prefork with C<Apache::Test>.
So, the test script was firing off a new request
before the old request Cleanup handler ran to save the session,
which resulted in sporadic and inconsistent failures...
yeah, THOSE kind, you know the type, the most maddening ones.
Apache::Session does not always save automatically, for
example if you change something in the bottom tier of
a multi-level hash. If you want to, set the directive flag
C<A2C_Session_Always_Save> and this will set a top-level
timestamp C<< $r->pnotes->{a2c}{session}{a2c_timestamp} >>
on the way out to trigger L<Apache::Session> to save everything.
But if you are potentially accessing the session contents without
setting it every time, you should just set a top-level timestamp
manually to indicate to L<Apache::Session> that you want
things saved at the end of every request, but this may
slow you down on a busy site, so it is not the default.
See L<Apache2::Controller::Directives/A2C_Session_Always_Save>
and L<Apache::Session/BEHAVIOR>.
=head1 IMPLEMENTING TRACKER SUBCLASSES
See L<Apache2::Controller::Session::Cookie> for how to implement
a custom tracker subclass. This implements C<$sid = get_session_id()>
which gets a session id from a cookie, and C<set_session_id($sid)>
which sets the session id in the cookie.
Perhaps some custom tracker subclass would implement
C<get_session_id()> to get the session_id out of the request
query params, and C<set_session_id()> would push a C<PerlOutputFilterHandler>
to post-process all other handler output and append the session id param
onto any url links that refer to our site. That would be cool...
release your own plug-in.
( run in 0.758 second using v1.01-cache-2.11-cpan-39bf76dae61 )