6.7. Transition from mod_cgi Scripts to Apache HandlersIf 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. 6.7.1. Starting with a mod_cgi-Compatible ScriptExample 6-18 shows the original script's code. Example 6-18. cookie_script.pl
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 HandlerLet'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. First we add the following snippet to httpd.conf:
and restart the server. 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
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:
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:
We will use the default name, handler( ). The handler( )subroutine is just like any other subroutine, but generally it has the following structure:
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:
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:
6.7.3. Converting to use the mod_perl API and mod_perl-Specific ModulesNow 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
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:
The rest of the code is unchanged. The last thing we need to do is add the following snippet to httpd.conf:
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.
Copyright © 2003 O'Reilly & Associates. All rights reserved. |
|