CGI::Prototype for Apache Action Scripts

Learn how to combine CGI::Prototype and Apache action scripts to serve custom and template web pages.
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, for example, a client of mine wanted to interpret HTML files within their web site through Template Toolkit at request time. There’s nothing unusual about such a request (I do this for http://www.stonehenge.com, for example, using a facility similar to Apache::Template), but the client wanted to avoid mod_perl during initial 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 and providing the original path as the “path info” to the program. I instead suggested the use of mod_actions, yielding the syntax:
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 offers. 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.

Found in Translation

My first demonstration was rather simple: Simply take the “path translated” and hand it as the template return value to the CGI::Prototype controller object. I quickly cobbled up a handler program that looked something like this:
use strict;
use base qw(CGI::Prototype);


sub template { shift->CGI->path_translated }
Here, I’m using main as the default class. It’s 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 workings of CGI::Prototype. If not, see the three-part series “Introduction to CGI::Prototype” beginning in May 2005, available online at http://www.linux-mag.com/2005-05/perl_01.html.)
When render() is invoked, it 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, template() returns the translated path for the path info value, which is the Unix filename of the file that would have been served. Thus, the URL is “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. Additionally, 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:
use strict;
use Site::App;

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 }

Same code, but now organized a lot more like a real application.
However, now I have the problem: I have a locally introduced class file, but I wanted to be able to develop this code in multiple places (my laptop, my client’s development web server, my client’s 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";
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 {
INCLUDE_PATH => ["$ENV/.templates"],
PROCESS => "process.tt",
Now Template Toolkit can 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). 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.

Hey, I’m Special!

At this point, I have all HTML files processed through a CGI::Prototyped application, including a nice wrapper to add common elements. 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 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 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 the method shown in Listing One:
LISTING ONE: Overriding the default dispatch() 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;

There’s lots of stuff going on in the listing, so let’s take it line by line. Keep in mind that the main job of dispatch() is to return the controller for the respond phase. In this design, the controller comes from the META controller=… directive in the file, prefixed by Site::Controller::. If no such META directive is present in the file, Site::Controller::default is used instead. The dispatcher must also load the module: presume you’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. To keep things simple, assume that the right error() method is set up for CGI::Prototype.
Once you have a $template object, you can ask it for the controller META variable simply by calling that as a method name. If it doesn’t exist, you’ll get an undef, which is checked for. Next, the code verifies that the controller name is sane (a simple Perl word) before inserting Site::Controller:: in front of the name. If anything goes wrong here, default is used for the name. The eval loads the module, raising an exception if something goes wrong.
The call to reflect() returns a Class::Prototyped::Mirror object for the application, with which you can add a dynamic definition for the template slot. The slot is given the value of the Template::Document object, which is used by the render step when the page is finally spit out.
The last step is to create a proper singleton object from the named class of the controller object. This is necessary because some Template Toolkit method calls need an object, not a class name. So again, a mirror object is created, which in turn yields the singleton.
The default controller class must now be created to serve the majority of the pages:
package Site::Controller::default;
use strict;
use base ’Site::App’;

For a specific page that requires special handling, you can create and designate a distinct controller, as shown in Listing Two and then add the controller shown in Listing Three.
LISTING TWO: Specifying a distinct controller for special pages using Template Toolkit

[% 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 %]

LISTING THREE: A custom controller

package Site::Controller::mailform;
use base ’Site::App’;
use strict;

sub submit { ’Send’ }

sub respond {
my $self = shift;

(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


return $self; # use same controller for render


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 client’s needs. I hope this was information that you can use. Until next time, enjoy!

Randal Schwartz is Chief Perl Guru at Stonehenge Consulting. You can reach Randal at class="emailaddress">merlyn@stonehenge.com.

Comments are closed.