6.7. Transition from mod_cgi Scripts to Apache Handlers
If you don't need to preserve backward compatibility
with mod_cgi, you can port mod_cgi scripts to use mod_perl-specific
APIs. This allows you to benefit from features not available under
mod_cgi and gives you better performance for the features available
under both. We have already seen how easily
Apache::Registry turns scripts into handlers
before they get executed. The transition to handlers is
straightforward in most cases.
Let's see a transition example. We will start with a
mod_cgi-compatible script running under
Apache::Registry, transpose it into a Perl content
handler without using any mod_perl-specific modules, and then convert
it to use the Apache::Request and
Apache::Cookie modules that are available only in
the mod_perl environment.
use strict;
use CGI;
use CGI::Cookie;
use vars qw($q $switch $status $sessionID);
init( );
print_header( );
print_status( );
sub init {
$q = new CGI;
$switch = $q->param("switch") ? 1 : 0;
my %cookies = CGI::Cookie->fetch;
$sessionID = exists $cookies{'sessionID'}
? $cookies{'sessionID'}->value
: '';
# 0 = not running, 1 = running
$status = $sessionID ? 1 : 0;
# switch status if asked to
$status = !$status if $switch;
if ($status) {
# preserve sessionID if it exists or create a new one
$sessionID ||= generate_sessionID( ) if $status;
} else {
# delete the sessionID
$sessionID = '';
}
}
sub print_header {
my $c = CGI::Cookie->new(
-name => 'sessionID',
-value => $sessionID,
-expires => '+1h'
);
print $q->header(
-type => 'text/html',
-cookie => $c
);
}
# print the current Session status and a form to toggle the status
sub print_status {
print qq{<html><head><title>Cookie</title></head><body>};
print "<B>Status:</B> ",
$status
? "Session is running with ID: $sessionID"
: "No session is running";
# change status form
my $button_label = $status ? "Stop" : "Start";
print qq{<hr>
<form>
<input type=submit name=switch value=" $button_label ">
</form>
};
print qq{</body></html>};
}
# A dummy ID generator
# Replace with a real session ID generator
########################
sub generate_sessionID {
return scalar localtime;
}
The code is very simple. It creates a session when you press the
Start button and deletes it when you pressed the Stop button. The
session is stored and retrieved using cookies.
We have split the code into three subroutines. init(
) initializes global variables and parses incoming data.
print_header( ) prints the HTTP headers, including
the cookie header. Finally, print_status( )
generates the output. Later, we will see that this logical separation
will allow an easy conversion to Perl content-handler code.
We have used a few global variables, since we didn't
want to pass them from function to function. In a big project, you
should be very restrictive about what variables are allowed to be
global, if any. In any case, the init( )
subroutine makes sure all these variables are reinitialized for each
code reinvocation.
We have used a very simple generate_sessionID( )
function that returns a current date-time string (e.g., Wed Apr 12
15:02:23 2000) as a session ID. You'll want to
replace this with code that generates a unique and unpredictable
session ID each time it is called.
6.7.2. Converting into a Perl Content Handler
Let's now convert
this script into a content
handler. There are two parts to this task: first configure Apache to
run the new code as a Perl handler, then modify the code itself.
When a request whose URI starts with
/test/cookie is received, Apache will execute
the Book::Cookie::handler( )subroutine (which we
will look at presently) as a content handler. We made sure we
preloaded the Book::Cookie module at server
startup with the PerlModule directive.
Now we modify the script itself. We copy its contents to the file
Cookie.pm and place it into one of the
directories listed in @INC. In this example,
we'll use /home/httpd/perl,
which we added to @INC. Since we want to call this
package Book::Cookie, we'll put
Cookie.pm into the
/home/httpd/perl/Book/ directory.
The changed code is in Example 6-19. As the
subroutines were left unmodified from the original script, they
aren't reproduced here (so you'll
see the differences more clearly.)
Example 6-19. Book/Cookie.pm
package Book::Cookie;
use Apache::Constants qw(:common);
use strict;
use CGI;
use CGI::Cookie;
use vars qw($q $switch $status $sessionID);
sub handler {
my $r = shift;
init( );
print_header( );
print_status( );
return OK;
}
# all subroutines unchanged
1;
Two lines have been added to the beginning of the code:
package Book::Cookie;
use Apache::Constants qw(:common);
The first line declares the package name, and the second line imports
constants commonly used in mod_perl handlers to return status codes.
In our case, we use the OK constant only when
returning from the handler( )subroutine.
The following code is left unchanged:
use strict;
use CGI;
use CGI::Cookie;
use vars qw($q $switch $status $sessionID);
We add some new code around the subroutine calls:
sub handler {
my $r = shift;
init( );
print_header( );
print_status( );
return OK;
}
Each content handler (and any other handler) should begin with a
subroutine called handler( ). This subroutine is
called when a request's URI starts with
/test/cookie, as per our configuration. You can
choose a different subroutine name—for example,
execute( )—but then you must explicitly
specify that name in the configuration directives in the following
way:
The handler( )subroutine is just like any other
subroutine, but generally it has the following structure:
sub handler {
my $r = shift;
# the code
# status (OK, DECLINED or else)
return OK;
}
First, we retrieve a reference to the request object by shifting it
from @_ and assigning it to the
$r variable. We'll need this a
bit later.
Second, we write the code that processes the request.
Third, we return the status of the execution. There are many possible
statuses; the most commonly used are OK and
DECLINED. OK tells the server
that the handler has completed the request phase to which it was
assigned. DECLINED means the opposite, in which
case another handler will process this request.
Apache::Constants exports these and other commonly
used status codes.
In our example, all we had to do was to wrap the three calls:
init( );
print_header( );
print_status( );
inside the handler( )skeleton:
sub handler {
my $r = shift;
return OK;
}
Last, we need to add 1; at the end of the module,
as we do with any Perl module. This ensures that
PerlModule doesn't fail when it
tries to load Book::Cookie.
To summarize, we took the original script's code and
added the following seven lines:
package Book::Cookie;
use Apache::Constants qw(:common);
sub handler {
my $r = shift;
return OK;
}
1;
and we now have a fully-fledged Perl content handler.
6.7.3. Converting to use the mod_perl API and mod_perl-Specific Modules
Now that we have a complete
PerlHandler, let's convert it to
use the mod_perl API and mod_perl-specific modules. First, this may
give us better performance where the internals of the API are
implemented in C. Second, this unleashes the full power of Apache
provided by the mod_perl API, which is only partially available in
the mod_cgi-compatible modules.
We are going to replace
CGI.pm
and CGI::Cookie with their mod_perl-specific
equivalents: Apache::Request and
Apache::Cookie, respectively. These two modules
are written in C with the XS interface to Perl, so
code that uses these modules heavily runs much faster.
Apache::Request has an API similar to
CGI's, and
Apache::Cookie has an API similar to
CGI::Cookie's. This makes porting
straightforward. Essentially, we just replace:
use CGI;
$q = new CGI;
with:
use Apache::Request ( );
$q = Apache::Request->new($r);
And we replace:
use CGI::Cookie ( );
my $cookie = CGI::Cookie->new(...)
with:
use Apache::Cookie ( );
my $cookie = Apache::Cookie->new($r, ...);
package Book::Cookie2;
use Apache::Constants qw(:common);
use strict;
use Apache::Request ( );
use Apache::Cookie ( );
use vars qw($r $q $switch $status $sessionID);
sub handler {
$r = shift;
init( );
print_header( );
print_status( );
return OK;
}
sub init {
$q = Apache::Request->new($r);
$switch = $q->param("switch") ? 1 : 0;
my %cookies = Apache::Cookie->fetch;
$sessionID = exists $cookies{'sessionID'}
? $cookies{'sessionID'}->value : '';
# 0 = not running, 1 = running
$status = $sessionID ? 1 : 0;
# switch status if asked to
$status = !$status if $switch;
if ($status) {
# preserve sessionID if it exists or create a new one
$sessionID ||= generate_sessionID( ) if $status;
} else {
# delete the sessionID
$sessionID = '';
}
}
sub print_header {
my $c = Apache::Cookie->new(
$r,
-name => 'sessionID',
-value => $sessionID,
-expires => '+1h');
# Add a Set-Cookie header to the outgoing headers table
$c->bake;
$r->send_http_header('text/html');
}
# print the current Session status and a form to toggle the status
sub print_status {
print qq{<html><head><title>Cookie</title></head><body>};
print "<B>Status:</B> ",
$status
? "Session is running with ID: $sessionID"
: "No session is running";
# change status form
my $button_label = $status ? "Stop" : "Start";
print qq{<hr>
<form>
<input type=submit name=switch value=" $button_label ">
</form>
};
print qq{</body></html>};
}
# replace with a real session ID generator
sub generate_sessionID {
return scalar localtime;
}
1;
The only other changes are in the print_header( )
function. Instead of passing the cookie code to
CGI's header(
) function to return a proper HTTP header, like this:
Now the magic URI that will trigger the above code execution will be
one starting with /test/cookie2. We save the
code in the file
/home/httpd/perl/Book/Cookie2.pm, since we have
called this package Book::Cookie2.
As you've seen, converting well-written CGI code
into mod_perl handler code is straightforward. Taking advantage of
mod_perl-specific features and modules is also generally simple. Very
little code needs to be changed to convert a script.
Note that to make the demonstration simple to follow, we
haven't changed the style of the original package.
But by all means consider doing that when porting real code: use
lexicals instead of globals, apply mod_perl API functions where
applicable, etc.