O'Reilly logo

Embedding Perl in HTML with Mason by Ken Williams, Dave Rolsky

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Request: A Request Object with a Built-in Session

Wouldn’t it be cool to have a request object with a built-in session? “Yes, it would,” you answer. “Child’s play,” we say.

When a request is made using this object, it should either find an old session or create a new one. Then in our components we will simply call $m->session() to get back a hash reference that will persist between requests.

For simplicity’s sake, we won’t make this class configurable as to what type of session to use, though it could be done.[27]

package HTML::Mason::Request::WithSession;
$VERSION = '0.01';

use strict;

# Import a subroutine error( ) which throws an HTML::Mason::Exception
# object
use HTML::Mason::Exceptions ( abbr => [ 'error' ] );

use HTML::Mason::ApacheHandler;
use base qw(HTML::Mason::Request);

One problem unique to subclassing to the Request object is that Mason already comes with two of its own Request subclasses. These are HTML::Mason::Request::ApacheHandler and HTML::Mason::Request::CGIHandler, which are used by the ApacheHandler and CGIHandler, respectively.

In order to cooperate with the ApacheHandler and CGIHandler modules, we want to subclass the appropriate class. However, we can’t know which one to subclass when we are loaded, because it is possible that we will be loaded before the ApacheHandler or CGIHandler module. We’ll take care of this in our new() method, which will be discussed momentarily.

Our session will be implemented using cookies and Cache::FileCache for storage, just as we saw in Chapter 11:

use Apache::Cookie;
use Cache::FileCache;
use Digest::SHA1;

We solve our subclassing problem with the following code. There is nothing wrong with changing a class’s inheritance dynamically in Perl, so that’s what we do. The alter_superclass() method is provided by the HTML::Mason::Request base class, and does the right thing even given multiple inheritance. It also cooperates with Class:Container to make sure that it sees any changes made to the inheritance hierarchy:

sub new {
    my $class = shift;

	$class->alter_superclass( $HTML::Mason::ApacheHandler::VERSION ?
                              'HTML::Mason::Request::ApacheHandler' :
                              $HTML::Mason::CGIHandler::VERSION ?
                              'HTML::Mason::Request::CGI' :
                              'HTML::Mason::Request' );

    return $class->SUPER::new(@_);
}

We make a session, call exec() in our parent class, taking care to preserve the caller’s scalar/list context, and then save the session. If an exception is thrown, we simply rethrow it:

sub exec {
    my $self = shift;

    $self->_make_session;

    my @result;
    if (wantarray) {
        @result = eval { $self->SUPER::exec(@_) };
    } elsif (defined wantarray) {
        $result[0] = eval { $self->SUPER::exec(@_) };
    } else {
        eval { $self->SUPER::exec(@_) };
    }

    # copy this in case _save_session overwrites $@
    my $e = $@;

    $self->_save_session;

    die $e if $e;

    return wantarray ? @result : defined wantarray ? $result[0] : undef;
}

Making a new session for subrequests is probably incorrect behavior, so we simply reuse our parent’s session object if a subrequest is exec( )‘d:

sub _make_session {
    my $self = shift;

    if ( $self->is_subrequest ) {
        $self->{session} = $self->parent_request->session;
        return;
    }

This code is pulled almost verbatim from Chapter 11:

my %c = Apache::Cookie->fetch;
my $session_id =
    exists $c{masonbook_session} ? $c{masonbook_session}->value : undef;

$self->{session_cache} =
    Cache::FileCache->new( { cache_root => '/tmp',
                             namespace  => 'Mason-Book-Session',
                             default_expires_in  => 60 * 60 * 24, # 1 day
                             auto_purge_interval => 60 * 60 * 24, # 1 day
                             auto_purge_on_set => 1 } );

my $session;
if ($session_id) {
    $session = $self->{session_cache}->get($session_id);
}

unless ($session) {
    $session = { _session_id => Digest::SHA1::sha1_hex( time, rand, $$ ) };
}

Apache::Cookie->new( $self->apache_req,
                     name => 'masonbook_session',
                     value => $session->{_session_id},
                     path => '/',
                     expires => '+1d',
                   )->bake;

$self->{session} = $session;
  }

Also just like Chapter 11:

sub _save_session {
    my $self = shift;

    $self->{session_cache}->set
        ( $self->{session}{_session_id} => $self->{session} );
}

And to finish it off, a simple accessor method:

sub session { $_[0]->{session} }

Wow, nice and simple. Of course, this would need to be customized for your environment, or you can use the previously mentioned HTML::Mason::Request::WithApacheSession module available from CPAN.

Once again, you have two options to use this new subclass. If you are configuring Mason via your httpd.conf file, do this:

PerlSetVar  MasonRequestClass  HTML::Mason::Request::WithSession

or in your handler.pl you can load the module and then pass a request_class parameter to the HTML::Mason::Interp class’s constructor.



[27] This is left as an exercise... Actually, this was left to the one of the authors. Dave Rolsky recently created HTML::Mason::Request::WithApacheSession, which is a highly configurable module that expands on the example shown in this section. This module is available from a CPAN mirror near you.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required