Introduction to CGI::Application

Chapter 11 - CGI Application Modules for CPAN
by?Sam Tregar?
Apress ? 2002
has companion web siteCompanion Web Site

The CGI::Application module represents an evolution in the way CGI programs (known as CGIs) are developed. CGIs provide services to users through their Web browsers, usually by displaying a series of screens containing forms for the user to interact with. CGIs are commonly coded in Perl by creating a script that generates HTML forms and processes the results of those forms. There are many drawbacks to this approach, as I'll explain in the upcoming text.

CGI::Application offers a different model. Instead of writing your CGI code in scripts specific to the task at hand, CGI::Application allows you to create flexible modules that can be used on multiple projects. Furthermore, CGI::Application provides a solution for some of the more common problems plaguing CGI development today.

A Typical CGI

Imagine, if you will, a typical CGI program—a simple bulletin board system. The program allows users to list all messages on the board, read a particular message, search for messages, enter new messages, and reply to existing messages. Of course a real bulletin board system would offer more features, but for this example I'll keep it simple. Figures 11-1, 11-2, 11-3, and 11-4 show the four screens of the application as they would appear in a Web browser.

click to expand
Figure 11-1: Bulletin board message list screen

click to expand
Figure 11-2: Bulletin board message viewer screen

click to expand
Figure 11-3: Bulletin board message entry screen

click to expand
Figure 11-4: Bulletin board message search screen

A typical implementation might start with a structure like this, implementing just the listing, reading, and saving of new messages:

 #!/usr/bin/perl use CGI; $query = CGI->new(); # instantiate CGI object # determine what to do if ($query->param("msg_id")) {       # user is reading to a message?   do_read(); } elsif ($query->param("subject")) { # user is saving a message do_save(); } else {                             # list messages by default   do_list(); } # code for do_read(), do_save() and do_list() ... 

The preceding code uses CGI.pm's param() method to examine incoming CGI parameters to determine what the user wants to do. Each subroutine called in the if-else block performs the required action using more CGI.pm calls, and ends by printing out the HTML for the page. Since I'm not trying to teach CGI programming, I've left out the implementation of these functions.

This code employs a set of heuristics to determine which action to perform for the user. For example, it knows the user is saving a message by the presence of a CGI parameter called subject. If a user wants to read a message, then the msg_id parameter will be included in the request and the script will know to act accordingly. This approach has a major flaw—it makes adding new features much harder than it should be.

For example, imagine adding support for replying to a message. This action will need only one parameter—the message ID for the message being replied to. Thus, the elsif block might look like the following:

 } elsif ($query->param("msg_id")) { # is the user replying to a message?   do_reply(); } 

But this won't work; the do_read() functionality is already using msg_id, and do_reply() will never be called! Instead you'd have to distort your parameter names to avoid clashing by adding a new parameter:

 } elsif ($query->param("reply_msg_id")) { # is the user replying to a message?   do_reply(); } 

The problem here is that the CGI script has no way to know what the user is really doing—it just looks at the incoming request and makes an educated guess. A small error on one form can lead your program down the wrong path with disastrous results. Also, understanding how the program works is unnecessarily difficult.

CGIs as State Machines

Many CGI programs, including the example shown earlier, can be viewed as finite state machines (or just state machines). A state machine is a system characterized by a series of discreet states and events that provide transitions between states. In a CGI like the BBS, the events are actions performed by the user, usually by clicking a button or following a link. In response, the application enters a particular state. In the preceding example, the do_save() subroutine is a state entered in response to the user saving a message. The output of this subroutine is displayed to the user. Thus, from a user's perspective, events are mouse-clicks and states are screens of the application.

State machines can be pictured visually using symbols from the Unified Modeling Language (UML), which was introduced briefly at the end of Figure 11-5 shows a state machine for the BBS application. The four boxes represent the four screens of the application. The arrows pointing from one state to the next are the events. From any given state, there are a fixed number of available events, or transitions. The diagram begins with an initial state, shown as a filled circle. The default event—list—leads from the initial state to the first real state. This means that when users first arrive at the application, they should be shown a list of messages.

click to expand
Figure 11-5: State machine for BBS application

By using a state machine to design your CGIs, you are able to view the application as a whole. Each event that is accessible from a particular state will generally show up in the final application as a button or link. With this knowledge, you can use state-transition diagrams to assist you in designing both your user interface and your program code. Also, the state-transition diagram can notify you if you're missing a button on a particular screen.

The CGI::Application module recognizes that Web applications are best understood as state machines. As you are about to see, this module provides a reusable alternative to rolling your own ad hoc flow control from if-elsif blocks.

CGI::Application to the Rescue

CGI::Application offers a cleaner solution to this CGI project. Listings 11-1 and 11-2 show the full CGI::Application version—browse through it and then I'll walk you through the code.

Listing 11-1: BBS.pm, the CGI::Application BBS

start example
 package BBS; use CGI::Application; @ISA = qw(CGI::Application); sub setup {   my $self = shift;   $self->mode_param("rm");   $self->start_mode("list");   $self->run_modes(list => "list",                    save => "save",                    new => "new_message",                    read => "read",                    reply => "reply",                    search => "search"); } # show list of messages sub list {   my $self = shift;   my $query = $self->query();   my $output;   # ...   return $output; } # save the message, then switch to list mode sub save {   my $self = shift;   my $query = $self->query();   # ...   return $self->list(); } # run a search and show the results sub search {   my $self = shift;   my $query = $self->query();   my $output;   # ...   return $output; } # view a message sub read {   my $self = shift;   my $query = $self->query();   my $output;   # ...   return $output; } # show edit screen with blank entry sub new_message {   my $self = shift;   return $self->_edit(); } # show edit screen with message quoted sub reply {   my $self = shift;   my $query = $self->query;   my $reply_id = $query->param('reply_id');   return $self->_edit(reply_to => $reply_id); } # private method to show edit screen for new_message and reply sub _edit {   my $self = shift;   my $query = $self->query();   my $output;   # ... return $output; } 1; 
end example

Listing 11-2: bbs.cgi, the BBS Stub Script

start example
 #!/usr/bin/perl use lib '.'; # load BBS.pm from the current directory use BBS; # instantiate the BBS application object and run it my $bbs = BBS->new(); $bbs->run(); 
end example

CGI::Application is an abstract base class in object-oriented terminology. This means it provides all its functionality by serving as a parent class. You can't use CGI::Application directly, you have to create a subclass. The start of Listing 11-1, placed in a file called BBS.pm, does just that:

 package BBS; use CGI::Application; @ISA = qw(CGI::Application); 

This creates a new module called BBS that inherits from CGI::Application.

Next, the class implements the one required method, setup(). The setup() method is called from CGI::Application->new(). It is responsible for setting up the run modes for the class. Run-modes provide a direct implementation of the events shown in the state machine for this application. They replace the if-elsif structure of the earlier example.

The setup() method works by calling methods inherited from CGI::Application, specifically mode_param(), start_mode(), and run_modes():

 sub setup {   my $self = shift;   $self->mode_param("rm");   $self->start_mode("list");   $self->run_modes(list => "list",                    save        => "save",                    new         => "new_message",                    read        => "read",                    reply       => "reply",                    search      => "search"); } 

The mode_param() method tells CGI::Application that the CGI parameter with the name rm will control the run mode of the program. This means that each HTML form sent to this CGI will have a parameter called rm set to a run mode. For example, this might take the form of a hidden input field:

 <input type="hidden" name="rm" value="save"> 

When the form containing this tag is submitted, the application will enter the "save" run mode. By tracking the run mode in a single parameter, CGI::Application always knows which event is being called. In contrast to the heuristic if-elsif structure seen earlier, this system is durable and simple to understand. CGI::Application simply looks up the value of the rm parameter in the table passed to run_modes() and finds a method to call in the application class.

The next call, to start_mode(), sets "list" as the default run mode. When the CGI is called without an rm parameter set, the run mode will be "list." This is generally what happens when the user first hits the application—which is why it's called start_mode() and not default_mode().

Finally, run_modes() sets up the run-mode table. The keys are names of run modes and the values are method names. Notice that the application defines a run mode named "new" but uses the method name new_message(). Using new() would have caused problems since CGI::Application defines a new() method already and the BBS class is derived from CGI::Application.

After that, the class implements each of its run modes as a separate method. For example, the list() method looks like this:

 # show list of messages sub list {   my $self = shift;   my $query = $self->query;   my $output;   # ...   return $output; } 

Since run modes are methods, they receive their object as the first parameter ($self). The inherited query() method is used to create and return a CGI.pm object. As a result, the internals of the function may be very similar to the pure CGI.pm implementation shown earlier. However, there is one major difference— CGI::Application run-mode methods must never print directly to STDOUT. Instead, they return their output.

Caution 

Never print to STDOUT from a CGI::Application run mode. All output must be returned as a string.

Since all run modes are methods, transferring control from one to another is easy. For example, the save() method is intended to show the list of messages when it finishes saving the message. To do this it just calls list() at the end of the method and returns the result:

 # save the message, then switch to list mode sub save {   my $self = shift;   my $query = $self->query();   # ...   return $self->list(); } 

Just as in the earlier example, the "new" and "reply" run modes share the same underlying functionality. With CGI::Application this is easily done by having them both call the same private method, _edit():

 # show edit screen with blank entry sub new_message {   my $self = shift;   return $self->_edit(); } # show edit screen with message quoted sub reply {   my $self = shift;   my $query = $self->query;   my $reply_id = $query->param('reply_id');   return $self->_edit(reply_to => $reply_id); } 

The _edit() method isn't listed as a run mode, so it can't be called directly by users. This is an important feature—if users could access arbitrary methods, then CGI::Application would be a security breach waiting to happen!

The end result is a system that allows you to directly express the flow control of your CGI system in code. Instead of using an ad hoc flow-control mechanism to guess which action to perform, CGI::Application modules use a table of run modes that map the contents of the run-mode parameter to methods in the class. And as I'll explore later, by building your applications as modules rather than scripts, you'll be able to reuse them in multiple projects and even release them on CPAN.



Writing Perl Modules for CPAN
Writing Perl Modules for CPAN
ISBN: 159059018X
EAN: 2147483647
Year: 2002
Pages: 110
Authors: Sam Tregar

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net