Sign In/My Account | View Cart  
advertisement


Listen Print

Programming with Mason

by Dave Rolsky
December 11, 2002

Dave Rolsky and Ken Williams are the authors of Embedding Perl in HTML with Mason.

Mason is a powerful framework for generating dynamic text, and is especially useful when creating complex, featureful Web sites. For those (hopefully few) folks who haven't yet heard of Mason, it is a Perl-based templating framework comparable to frameworks such as Apache::ASP, Embperl, and Template Toolkit. Like the first two, and unlike the latter, Mason operates by embedding Perl in text.

Mason is based around the idea of a component. A component is roughly equivalent to a Perl subroutine, and can contain text and/or code. Here is a very simple, but complete component that has both text and code:


 % my $planet = "World";
 Hello, <% $planet %>!

When Mason runs this code, the output is:


 Hello, World!

Related Reading

Embedding Perl in HTML with Mason

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

Table of Contents
Index
Sample Chapter

Read Online--Safari Search this book on Safari:
 

Code Fragments only

The rest of this article assumes at least a minimal familiarity with Mason, though if you're at all familiar with other templating systems, you'll probably be able to grok the code we show. For more details, I would of course recommend Embedding Perl in HTML with Mason, written by Ken Williams and myself. Mason also comes with its own documentation, which can be seen online at www.masonhq.com.

As with any powerful and flexible system, Mason is applicable to a lot of problems, and there is always more than one way to do it. It is a Perl-based system, after all!

Below you'll find some cookbook recipes for solving a few typical Web application problems. All the recipes assume that you are using the latest version of Mason, which at the time of this writing is 1.15, though most of them will work untouched with older versions.

Putting a Session ID in All URLs

If you've ever written a dynamic Web application, then it's likely that you've used sessions to store data as the user moves through the application. Typically, sessions are identified by session IDs that are stored in a cookie.

If you cannot use cookies, then you can store the session ID in the URL. There are security and application problems with this approach (as well as with the user of cookies), but those are outside the scope of this article. The mod_perl user list archives at marc.theaimsgroup.com/?l=apache-modperl contain a number of discussions related to this topic.

Putting the session ID in the URL can be a hassle, because it means that you have to somehow process all the URLs you generate. Using Mason, this isn't as difficult as it would be otherwise. There are at least two ways to do this.

The first would be to put a filter in your top level autohandler component:


  <%filter>
   s/href="([^"])+"/'href="' . add_session_id($1) . '"'/eg;
   s/action="([^"])+"/'href="' . add_session_id($1) . '"'/eg;
  </%filter>
The add_session_id() subroutine, which should be defined in a module, might look something like this:

  sub add_session_id {
      my $url = shift;

      return $url if $url =~ m{^\w+://}; # Don't alter external URLs

      if ($url =~ /\?/) {
  	  $url =~ s/\?/?session_id=$MasonBook::Session{_session_id}&/;
      } else {
          $url .= "?session_id=$MasonBook::Session{_session_id}";
      }

      return $url;
  }

This routine accounts for external links as well as links with or without an existing query string.

The drawback to putting this in a <%filter> section is that it only filters URLs in the content generated by components, and misses any URLs that might be in headers, such as in a redirect. Therefore, you'd need to handle those cases separately with this solution.

Another solution would be to create all URLs (including those intended for redirects) via a dedicated component or subroutine that adds the session id. This latter solution is probably a better idea, as it handles redirects properly. The drawback with this strategy is that you'll have a Mason component call for every link, instead of just regular HTML.

Here is just such a component:


  <%args>
   $scheme   => 'http'
   $username => undef
   $password => ''
   $host     => undef
   $port     => undef
   $path
   %query    => ()
   $fragment => undef
  </%args>
  <%init>
   my $uri = URI->new;

   if ($host) {
       $uri->scheme($scheme);

       if (defined $username) {
           $uri->authority( "$username:$password" );
       }

       $uri->host($host);
       $uri->port($port) if $port;
   }

   # Sometimes we may want to include a path in a query string as part
   # of the path but the URI module will escape the question mark.
   my $q;

   if ( $path =~ s/\?(.*)$// ) {
       $q = $1;
   }

   $uri->path($path);

   # If there was a query string, we integrate it into the query
   # parameter.
   if ($q) {
       %query = ( %query, split /[&=]/, $q );
   }

   $query{session_id} = $UserSession{session_id};

   # $uri->query_form doesn't handle hash ref values properly
   while ( my ( $key, $value ) = each %query ) {
       $query{$key} = ref $value eq 'HASH' ? [ %$value ] : $value;
   }

   $uri->query_form(%query) if %query;

   $uri->fragment($fragment) if $fragment;
  </%init>
  <% $uri->canonical | n %>\

If you didn't want to put the session ID in the query string, then you might instead make it part of the URL path. The application could retrieve the session id from incoming requests by using a mod_perl handler during the URL translation stage of request handling.

This component provides a programmatic interface to URL generation. Here is an example of how to use it, assuming that you've saved it as a component called /url:


   ... some HTML ...
   Look at <a href="<& /url, path => "books.html" &>">our books</a>
   or <a href="<& /url, host => "www.oreilly.com"
                        path => "/catalog" &>">O'Reilly's</a>.
   ... some HTML ...

Making Use of Autoflush

Every once in a while, you may have to output a very large component or a file to the client. If you simply let this accumulate in the output buffer, you could use up a lot of memory. Furthermore, the slow response time may make the user think that the site has stalled.

Here is an example that sends out the contents of a potentially large file without sucking up lots of memory.


  <%args>
   $filename
  </%args>
  <%init>
   local *FILE;
   open FILE, "< $filename" or die "Cannot open $filename: $!";
   $m->autoflush(1);
   while (<FILE>) {
       $m->print($_);
   }
   $m->autoflush(0);
  </%init>

If each line wasn't too huge, then you might just flush the buffer every once in a while:


  <%args>
   $filename
  </%args>
  <%init>
   local *FILE;
   open FILE, "< $filename" or die "Cannot open $filename: $!";
   while (<FILE>) {
       $m->print($_);
       $m->flush_buffer unless $. % 10;
   }
   $m->flush_buffer;
  </%init>

The unless $. % 10 bit makes use of the special Perl variable $., which is the current line number of the file being read. If this number modulo 10 is equal to zero, then we flush the buffer. This means that we flush the buffer every 10 lines. (Replace the number 10 with any desired value.)

User Authentication and Authorization

One problem that Web sites have to solve over and over again is user authentication and authorization. These two topics are related but not the same, as some might think. Authentication is the process of figuring out if someone is who they say they are, and usually involves checking passwords or keys. Authorization comes after this, when we want to determine whether a particular person is allowed to perform a certain action.

There are a number of modules on CPAN that are intended to help do these things under mod_perl. In fact, Apache has separate request-handling phases for both authentication and authorization that mod_perl can handle. It is certainly possible to use these modules with Mason.

You can also do authentication and authorization using Mason components. Authentication will usually involve some sort of request for a login and a password, after which you give the user some sort of token (either in a cookie or a session) that indicates that they have been authenticated. You can then check the validity of this token for each request.

If you have such a token, then authorization simply consists of checking that the user to whom the token belongs is allowed to perform a given action.

Using Apache::AuthCookie

The Apache::AuthCookie module, available from CPAN, is a module that handles both authentication and authorization via mod_perl and can be easily hooked into Mason. Rather than go through all the details of configuring Apache::AuthCookie, which requires various settings in your server config file, let's just skip all that and show you how you'd make the interface to Mason.

Apache::AuthCookie requires that you create a "login script" that will be executed the first time a browser tries to access a protected area. Calling this a script is actually somewhat misleading since it is really a page rather than a script (though it could be a script that generates a page). Regardless, using a Mason component for your "login script" merely requires that you specify the path to your Mason component for the login script parameter.

We'll call this "script" AuthCookieLoginForm.comp:


  <html>
  <head>
  <title>Mason Book AuthCookie Login Form</title>
  </head>
  <body>
  <p>
  Your attempt to access this document was denied
  (<% $r->prev->subprocess_env("AuthCookieReason") %>).  Please enter
  your username and password.
  </p>

  <form action="/AuthCookieLoginSubmit">
  <input type="hidden" name="destination" value="<% $r->prev->uri %>">
  <table align="left">
   <tr>
    <td align="right"><b>Username:</b></td>
    <td><input type="text" name="credential_0" size="10" maxlength="10"></td>
   </tr>
   <tr>
    <td align="right"><b>Password:</b></td>
    <td><input type="password" name="credential_1" size="8" maxlength="8"></td>
   </tr>
   <tr>
    <td colspan="2" align="center"><input type="submit" value="Continue"></td>
   </tr>
  </table>
  </form>

  </body>
  </html>

This component is modified version of the example login script included with the Apache::AuthCookie distribution.

The action used for this form, , is configured as part of your AuthCookie configuration in your httpd.conf file.

That's about all it takes to glue Apache::AuthCookie and Mason together. The rest of authentication and authorization is handled by configuring mod_perl to use Apache::AuthCookie to protect anything on your site that needs authorization. A very simple configuration might include the following directives:


  PerlSetVar MasonBookLoginScript /AuthCookieLoginForm.comp

  <location /authcookieloginsubmit>
    AuthType MasonBook::AuthCookieHandler
    AuthName MasonBook
    SetHandler  perl-script
    PerlHandler MasonBook::AuthCookieHandler->login
  </location>

  <location /protected>
    AuthType MasonBook::AuthCookieHandler
    AuthName MasonBook
    PerlAuthenHandler MasonBook::AuthCookieHandler->authenticate
    PerlAuthzHandler  MasonBook::AuthCookieHandler->authorize
    require valid-user
  </location>
The MasonBook::AuthCookieHandler module would look like this:

  package MasonBook::AuthCookieHandler;

  use strict;

  use base qw(Apache::AuthCookie);

  use Digest::SHA1;

  my $secret = "You think I'd tell you?  Hah!";

  sub authen_cred {
      my $self = shift;
      my $r = shift;
      my ($username, $password) = @_;

      # implementing _is_valid_user() is out of the scope of this chapter
      if ( _is_valid_user($username, $password) ) {
          my $session_key =
            $username . '::' . Digest::SHA1::sha1_hex( $username, $secret );
          return $session_key;
      }
  }

  sub authen_ses_key {
      my $self = shift;
      my $r = shift;
      my $session_key = shift;

      my ($username, $mac) = split /::/, $session_key;

      if ( Digest::SHA1::sha1_hex( $username, $secret ) eq $mac ) {
          return $session_key;
      }
  }

This provides the minimal interface an Apache::AuthCookie subclass needs to provide to get authentication working.

Pages: 1, 2

Next Pagearrow