Copyright Notice

This text is copyright by InfoStrada Communications, Inc., and is used with their permission. Further distribution or use is not permitted.

This text has appeared in an edited form in Linux Magazine magazine. However, the version you are reading here is as the author originally submitted the article for publication, not after their editors applied their creativity.

Please read all the information in the table of contents before using this article.

Linux Magazine Column 80 (Apr 2006)

[Suggested title: ``Adapting CGI::Prototype for Apache Action Scripts'']

One of the things that surprises me about my CGI::Prototype framework is that I can use the framework to adapt to situations I hadn't predicted during the design. Recently, a client of mine wanted to interpret HTML files within their website through Template Toolkit at request time. There's nothing unusual about this request (I do this for stonehenge.com for example, using a facility similar to Apache::Template), but the client wanted to avoid mod_perl during intitial development, instead opting for a per-hit CGI program to manage the process.

At first, the client wished to use a mod_rewrite rewrite, mapping some files to a path that included a CGI script, providing the original path as the ``path info'' to the program. I instead suggested the use of mod_actions, yielding the syntax of:

  Action text/html /cgi-bin/handler

This Apache directive causes all HTML files being served to invoke the selected handler program instead, passing the original URL as ``path info'', just as the client had requested, but with a bit more control than a mod_rewrite directive would have offered. Thus, when I ask for /brochure/index.html, I get /cgi-bin/handler/brochure/index.html instead.

Now, the trick here is to invoke Template Toolkit on the file in a nice flexible way. When I started writing a custom program for this handler, I realized I was about to reinvent a good portion of what CGI::Prototype does already, so I pulled out my trusty framework and got to work.

My first demonstration was rather simple. Take the ``path translated'', and hand it as the template return value to the CGI::Prototype controller object. I quickly cobbled up as my handler program something like:

  #!/usr/bin/perl
  use strict;
  use base qw(CGI::Prototype);
  main->activate;
  sub template { shift->CGI->path_translated }

Here, I'm using main as the default class: cheap, but effective. The use base pulls in CGI::Prototype, and declares main to be a child class of that class. Then, when the activate method is invoked, the default dispatch returns main, and the default respond also returns main. (I'm presuming here that you are already familiar with the basic functionality of CGI::Prototype.)

We get down to the render method, which calls template to determine which template must be rendered. And this is where the fun begins. Instead of the default ``this page intentionally left blank'' template, we return the translated path for the path info value, which is the Unix filename of the file that would have been served. Thus, the URL will be ``served'' by passing it through Template Toolkit, sending the result to STDOUT. Not bad for a few lines of code.

And while that particular handler satisfies the initial requirements of my client, I saw quickly that some additional requirements required me to work a bit harder for my billable hours. Specifically, each page needed some common headers and footers, and some pages required additional code that would be difficult or impossible to write using the simple Template Toolkit language. Also, putting all the code in main like this would have made it hard to test the application with CGI::Prototype::Mecha.

Taking the last issue first, I refactored the demonstration code into a separate class:

  #!/usr/bin/perl
  use strict;
  use Site::App;
  Site::App->activate;

and then in Site/App.pm, I placed the remainder of the code:

  package Site::App;
  use strict;
  use base qw(CGI::Prototype);
  sub template { shift->CGI->path_translated }
  1;

There we go: same code, but now organized a lot more like a real application. However, now I have the problem, in that I have a locally introduced class file, but I wanted to be able to develop this code in multiple places (my laptop, their development webserver, their production webserver). I needed to add a proper use lib to get the location of the module. With a small amount of cleverness, I added this code above the use Site::App:

  use lib do {
    ($ENV = __FILE__) =~ s#(.*/).*#$1#
      or die "Cannot turn $ENV into base path";
    "$ENV/.perl-lib";
  };

This code adds the directory .perl-lib to the @INC path located in the same directory as the CGI script. Additionally, the truly global (but previously unused) $ENV variable is set to the directory in which the CGI script is located, so I can use it for other things.

For example, to Site/App.pm, I added:

  sub engine_config {
    return
      {
       INCLUDE_PATH => ["$ENV/.templates"],
       ABSOLUTE => 1,
       PROCESS => "process.tt",
       POST_CHOMP => 1,
      }
  }

Now Template Toolkit could find additional templates in the cgi-bin/.templates directory, and I could access additional absolute paths as well. Instead of processing the original file, I'll be processing process.tt (located in the templates library), and my favorite POST_CHOMP is also enabled.

Inside process.tt, I needed to add the required headers and footers, so I started with something like:

  [% content = PROCESS $template; %]
  [% INCLUDE header.tt %]
  [% $content %]
  [% INCLUDE footer.tt %]

I eventually moved away from this code, as I'll describe in a moment.

At this point, I have all HTML files processed through a CGI::Prototyped application, including a nice wrapper to add common elements, and my client was starting to see real progress. However, I then struggled a bit with how to handle the special cases, where the default application controller wouldn't do, or where additional heavy lifting code was necessary, or where specialized headers and footers were needed.

I realized that what I wanted was that a particular page or set of pages should be able to yell out ``hey, I'm special, please use a slightly different controller here''. In Template Toolkit, a META directive can be used for this information, as in:

  [% META controller = 'mailform' %]

This value can then be accessed from within the process.tt as template.controller. But I needed the value even earlier than that. I needed to know the controller in my dispatch routine to pick the right subclass controller for my respond and render methods. Again, Template Toolkit had just the right hooks. In Site/App.pm, I overrode the default dispatch with this method:

  sub dispatch {
    my $self = shift;
 
    my $translated = $self->CGI->path_translated
      or die "missing path_translated";
    my $template = $self->engine->service->context->template($translated);
    my $controller = $template->controller;
    $controller = "default"
      unless defined $controller and $controller =~ /\A\w+\z/;
    $controller = "Site::Controller::$controller";
    eval "require $controller; 1" or die $@;
    $self->reflect->addSlot(template => $template);
    return $controller->reflect->object;
  }

Lots of stuff going on here, so let's take it line by line. The first thing to keep in mind is that the main job of dispatch is to return the controller for the respond phase. In this design, the controller will come from the META controller = ... directive in the file, prefixed by Site::Controller::. If no such META directive is present in the file, we'll use Site::Controller::default. The dispatcher must also load the module: we presume we're in a CGI environment and should load everything as late as possible.

So, this method loads the translated path info $translated, then uses the Template Toolkit engine to parse and compile that file into a Template::Document object (in $template). That step may fail, throwing an exception. We'll presume that the right error method is set up for CGI::Prototype to keep things simple here.

Once we have a $template object, we can ask it for the controller META variable simply by calling that as a method name. If it doesn't exist, we'll get an undef back here, which we check for. Also, we'll verify that the controller name is sane (a simple Perl word) before inserting Site::Controller:: in front of the name. If anything goes wrong here, we'll use default for the name. The eval loads the module, raising an exception if something goes wrong there.

The call to reflect returns a Class::Prototyped::Mirror object for the application, with which we can add a dynamic definition for the template slot. We give the slot the value of our Template::Document object, which will be used by the render step when we finally spit the page out.

The last step is to create a proper singleton object from the named class we have for the controller object. This is necessary because some Template Toolkit method calls will need an object, not a class name. So again, we create a mirror object, then ask the mirror for the singleton object, and that does what we need.

The default controller class must now be created for us to serve the majority of the pages:

  package Site::Controller::default;
  use strict;
  use base 'Site::App';
  1;

For a specific page which requires special handling, we can create and designate a distinct controller:

  [% META controller = "mailform" %]
  [% IF self.state == 'show_form' %]
  [%   MACRO e(field) BLOCK %]
  [%     txt = self.errors.$field; IF txt.length > 0 %]
  <b>Error: [% txt %]</b>
  [%     END %]
  [%   END %]
  [%   self.CGI.startform %]
  From: [% self.CGI.textfield('from', '', 40); e('from') %]<br>
  Subject: [% self.CGI.textfield('subject', '', 40); e('subject') %]<br>
  Message: [% self.CGI.textarea('message', '', 5, 40); e('message') %]<br>
  [%   self.CGI.submit(self.submit) %]
  [%   self.CGI.endform %]
  [% ELSE %]
  Thank you!  Someone will be in touch with you shortly.
  [% END %]

And then add our controller:

  package Site::Controller::mailform;
  use base 'Site::App';
  use strict;
  sub submit { 'Send' }
  sub respond {
    my $self = shift;
    $self->reflect->addSlots
      (state => 'show_form',
       errors => {},
      );
    my $errors = $self->errors; # hashref
    if ($self->param($self->submit)) { # it's a response via submit
      ## validate parameters, note errors
      unless ($self->param('from') =~ /\S/) {
        $errors->{from} = "Missing From";
      }
      unless ($self->param('subject') =~ /\S/) {
        $errors->{subject} = "Missing Subject";
      }
      unless ($self->param('message') =~ /\S/) {
        $errors->{message} = "Missing Message";
      }
      unless (keys %$errors) {
        ## no errors? then send email and return response form
        ## XXX send mail
        $self->state('show_response');
      }
    }
    return $self;                       # use same controller for render
  }
  1;

Thus, with CGI::Prototype, I was able to quickly adapt my flexible framework of dispatch-respond-render using Template Toolkit and lightweight objects to the clients needs. Hope this was information that you can use. Until next time, enjoy!


Randal L. Schwartz is a renowned expert on the Perl programming language (the lifeblood of the Internet), having contributed to a dozen top-selling books on the subject, and over 200 magazine articles. Schwartz runs a Perl training and consulting company (Stonehenge Consulting Services, Inc of Portland, Oregon), and is a highly sought-after speaker for his masterful stage combination of technical skill, comedic timing, and crowd rapport. And he's a pretty good Karaoke singer, winning contests regularly.

Schwartz can be reached for comment at merlyn@stonehenge.com or +1 503 777-0095, and welcomes questions on Perl and other related topics.