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 70 (May 2005)
[Suggested title: ``Introduction to CGI::Prototype (part 1)'']
It's a rite of passage, they say.
Every new Perl programmer seems to want to create their own CGI form
parser. And their own templating system. And their own way to parse
@ARGV
. And eventually, they even get fancy, and try to write their
own SQL-eliminating database-as-objects wrapper. Or accessor
autogenerator. Or lazy loader.
Or CGI framework. Yes, I'm now guilty of that, having created
CGI::Prototype
in the recent months, and shared it with the rest of
the world. I hope to explain in the following paragraphs exactly why,
and what it does, and how it does it, and why it works better than
anything else I could find at the time. Please read on.
The problem
The main problem with web program design is that web applications are backwards. Let me explain.
In a traditional GUI application, the program is in control. The program puts up a widget onto the display, and drives the user into action. When the user is finished, the application then decides what to do, and displays another GUI widget for the user to then deal with. In other words, the application drives the user.
In a web application, the user says ``hey, I'll visit a web site''. The web application then has to respond to the user, providing some sort of web form (the web equivalent of the GUI) back to the user, after which, the web app is gone, dead, terminated. The user then examines the form, and if they so choose, fills out values and submits that form. A new web application invocation has to respond to this form, hopefully relying on notes that the previous application invocation left laying around in some server side database, and this happens repeatedly. In other words, the user drives the application.
So, every web application must figure out how to make sense of these seemingly unrelated hits coming inward. How does the web application maintain state, including credentials, intermediate results, database connections, and so on? This is a big problem, because although the highest level logic is common, the specifics are, well, specific to each application.
At the end of 2003, I was hired by a large unnamed university on the east coast to help develop a web application, getting as far as I could within the week that they had allocated for me. The application had some interesting goals: it had to talk to an existing Oracle application through a database; the look of the web app had already been prescribed by some HTML designers (a traditional multi-page form with the freedom to wander to any tab at any time); and it had to be flexible, because it was likely that the interfaces would change as more information was understood.
After spending the morning of the first day with the client trying to figure out what they really hired me to do, I got on IRC and PerlMonks and asked about existing frameworks with some particular requirements. After examining a few solutions, I realized by mid-afternoon that nobody had yet created a framework in a way that could be applied to this situation as I had wanted. So, being the resourceful guy I am, I wrote my own.
I knew I wanted to use Class::DBI for the database interface, and Template Toolkit for the display driver (both having large user communities, a bonus for my client). I also thought that I'd rely on the ol' workhorse of CGI.pm, mostly because it's had the most goings-over of any CGI form processor and HTML generator in the business, and I wanted to be conservative for my client.
This gave me the ``M'' and ``V'' of the famous ``MVC'' triad (a classic way of breaking down GUI frameworks into manageable pieces). A Model for the data, a View for the results. But where was the Controller? There didn't seem to be any detailed controllers in the CPAN that fit my client's needs directly (or configurably), nor anything like an abstract controller that I could subclass and extend as I saw fit. And that's where I sat back to think about every web application I had written.
Every web application
Every web application seems to have the following steps:
-
Look at the incoming hit, and figure out the approximate rough state that we are in (initial hit, responding to some form, etc). Then dispatch to the code that will handle that broad category of hit.
-
Attempt to respond to the hit, figuring out the details of the incoming parameters and cookies, and figure out what state we're now in, and select code to handle that.
-
Finally, render a response, displaying results, errors, and most likely new forms to fill out.
The problem is, that errors can happen, and usually at bad times. For example, let's take the typical two-page ``form-and-response'' written with CGI.pm:
use CGI qw(param);
unless (param) { # no incoming parameters ... display the form ... } else { # incoming parameters ... respond to the incoming params ... }
And this works fine. The first hit has no params, so we show the form. The response points back at the same script, so we see the params, and handle the form.
But what if the form wasn't filled out properly? Now we either have to figure out how to detect that before we dispatch to the ``display the form'' or ``respond to the params'' part, or we have to somehow get back up to the upper code after the lower code has been selected. Ugh.
My implementation
I decided that I wanted a very generic CGI controller, using an
object-oriented model. Further, I had recently played around a bit
with Class::Prototyped
, and had really liked the way that
subclasses could be created on the fly and that accessors were smart
and autogenerated and dynamic. So, I decided that my top-level
generic controller would be a Class::Prototyped
-based object, and
that helped define the name: CGI::Protoype
.
The basic strategy is simple. The top-level application gets
activated by the incoming hit, and calls the application's
dispatch method. The dispatch method is responsible for
determining the application's state, and then determine a
particular controller to return to respond to that state. The
respond controller then examines all of the available inputs, updating
any external data, and then decides the new state, in the form of
returning a render controller. The render controller spits out a
page to send back to the user, usually by calling Template Toolkit on
a particular template, and sending the result to STDOUT
.
That sounds very complicated in theory, but in practice, it works out
to be rather straightforward. You create an application class that
inherits from CGI::Prototype
. You then create one or more
``page/state'' classes that inherit from your application class, and a
template for each page class that will be rendered to the user. You
also need to create a dispatcher to decide which page class gets
selected for a given state.
A sample program
The most trivial application you can create using CGI::Prototype
is one that uses all of the defaults:
#!/usr/bin/perl use base CGI::Prototype; main->activate;
Here, I've cheated, using main
as my application class. Generally,
you'll want to push this out into a real class for better testing.
But this is indeed a complete application, and when hit, results in a
small plain-text page that says ``This page intentionally left blank'',
using CGI.pm for the header. Yes, cute. I know.
But this isn't a very interesting application. However, by overriding just one method, I can get a template of my choosing instead of the built-in template:
sub template { 'mypage.tt' }
Now the main
class has an overridden template
method, which
defines the template to hand to Template Toolkit. There, I've now
got a template-running CGI application.
Looking under the hood
Let's look at what activate
is doing, in essence:
my $self = shift; my $this_page = $self->dispatch; my $next_page = $this_page->respond; $next_page->render;
First, the application's dispatch
method is run to get a page
controller object, which generally inherits from the application
controller itself. (This makes it easy to move page-created methods up
to the application level so that all pages can share them if needed.)
Next, the page controller object is asked to respond
to the
incoming parameters. When it completes, it is expected to return an
object that will be rendered. Again, this is typically a page
controller object that inherits from this application controller, and
frequently even just itself ("$next_page" eq "$this_page"
),
especially when there are errors.
Finally, the $next_page
object is asked to render
something to
the user, usually resulting in Template Toolkit being called on the
result of $next_page
's template
method.
So how did the empty application work? Through the clever use
of defaults. The default dispatch
and respond
return $self
.
The default template
method returns the ``this page intentionally
left blank'' template. So, it all ``just works''.
Of course, in practice, this is all enclosed in an eval
block,
like so:
eval { my $self = shift; my $this_page = $self->dispatch; my $next_page = $this_page->respond; $next_page->render; }; $self->error($@) if $@;
The default error method simply displays the string to STDOUT
, with
a text/plain
MIME type so that most browsers will leave it alone.
You'll almost certainly want to override that.
I've run out of space, and barely scratched the surface! Next time,
I'll talk about overriding the dispatch and respond methods to create
a real application, and about before and after hooks on all the steps
to hang interesting behavior alterations. I'll also look at
Class::Prototyped::Hidden
, to handle the state recognition in an
extensible consistent way. Until next time, enjoy!