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, ...);

Example 6-20 is the new code for Book::Cookie2.

Example 6-20. Book/Cookie2.pm

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:

print $q->header(
   -type   => 'text/html',
   -cookie => $c);

we do it in two stages. First, the following line adds a Set-Cookie header to the outgoing headers table:

$c->bake;

Then this line sets the Content-Type header to text/html and sends out the whole HTTP header:

$r->send_http_header('text/html');

The rest of the code is unchanged.

The last thing we need to do is add the following snippet to httpd.conf:

PerlModule Book::Cookie2
<Location /test/cookie2>
    SetHandler perl-script
    PerlHandler Book::Cookie2
</Location>

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.