5.1 mod_perlFirst created by Doug MacEachern in 1996, mod_perl is the main flower of the Apache Perl integration project. It brings the full power of the Perl language into the heart of the Apache HTTP server by linking the Perl runtime library into Apache's modular C language API. This is like being able to turn your Jeep into a jet-powered helicopter for the weekend, and then back again on Monday, at the flick of a switch.[1]
Once Apache is up and running with mod_perl, the Perl interpreter engine is up and running too, conveniently preloaded into constant memory. This means there's no restart overhead each time you run a Perl CGI script. The speed improvement brought to any Perl-based web site by the addition of mod_perl, and the scalability implications, are enormous. Important Apache mod_perl links include:
5.1.1 Installing mod_perl on UnixBefore you can test your mod_perl installation, you must make sure that the Perl LWP.pm module is available. Developed by Gisle Aas, this module provides a "Library for WWW access in Perl"; it consists of a wide range of related Perl modules designed to help simplify Perl Internet client connections. Not only is this module useful for our later mod_perl test, it's invaluable for many Perl Internet requirements. We'll come back to LWP.pm again and again as we discuss Perl and the Web in the next few chapters. 5.1.1.1 LWP-Library for WWW access in PerlThe main focus of LWP is to provide classes and functions allowing the creation of Internet Perl clients. The library also contains modules for more general use, even making it possible to create simple HTTP servers. Fortunately for us, Gisle Aas has collated all of the related LWP modules into a single download, libwww-perl-5.64.tar.gz (or its latest derivative). However, LWP itself relies upon several other related modules, as detailed in the appropriate installation order, in Table 5-1. If you want to install these by hand, download the latest tarballs and process them in the usual perl Makefile.PL manner. Alternatively, we'll accept a little sneaky automation here. There is a very handy command you can run, which should load everything required for LWP, directly over the Internet, in just one line: $ perl -MCPAN -e 'install Bundle::LWP' An even sneakier routine loads the whole of mod_perl and many of its related modules: $ perl -MCPAN -e 'install Bundle::Apache' This will load every module you require, including LWP. However, we'll still go through the manual route; this way, we can describe all the bumps in the road and configure everything properly. The CPAN module is a great tool, but it can sometimes be unreliable, as we discussed in Chapter 2, particularly when CPAN modules are not preconfigured by their authors in exactly the way that CPAN is expecting (many Internet modules fall into this category).
Two other CPAN packages that people often use alongside LWP include: http://www.cpan.org/authors/id/A/AM/AMS http://www.cpan.org/authors/id/KWILLIAMS 5.1.1.2 SSL — Secure Sockets LayerIf you need enhanced security at your site, you may also want to use the popular Secure Sockets Layer (SSL) program. Before you install LWP (or re-install it), be sure to check out the programs and Perl extensions listed in Table 5-2.
5.1.1.3 Installing mod_perlBefore we begin our installation of mod_perl, we recommend that for security reasons you shut down all your Apache processes, and then save the entire Apache root structure, perhaps in a tarball, before continuing. We can always revert back to this saved structure later on, should mod_perl prove problematic. To do the actual mod_perl installation on Unix, first download the latest and greatest goods by visiting Doug MacEachern's CPAN page: Then follow these steps:
5.1.1.4 Specifying the mod_perl Apache libraryAfter a refreshing rest, we can begin again by writing our first mod_perl server script module, HelloApache.pm. (We'll deal with the conversion of ordinary CGI scripts later.) First of all, we need to establish where our main mod_perl Apache library will be. We suggest that you create a ../lib/perl/Apache directory: $ cd /usr/local/apache $ mkdir -p lib/perl/Apache We now have two options for telling mod_perl where this library will be when Apache starts running. The first is to add the following line somewhere near the top of our httpd.conf configuration file: PerlSetEnv PERL5LIB /usr/local/apache/lib/perl However, because this approach adds a little overhead to each HTTP request, we recommend the second option instead. Go to your conf directory and edit a new Perl file: $ cd /usr/local/apache/conf $ vi startup.pl $ chmod 755 startup.pl Create the Perl script shown in Example 5-1 as your superuser (to ensure later security). Change directives, where appropriate, such as the location of the perl program in the shebang line (notice that we've commented out Apache::DBI, which we'll be covering later): Example 5-1. startup.pl — Apache mod_perl initialization script#!/usr/bin/perl # Set up the include path to get our new lib/perl directory BEGIN { use Apache( ); use lib Apache->server_root_relative('lib/perl'); } # Insert the most required modules use Apache::Registry( ); use Apache::Constants( ); #use Apache::DBI( ); # We'll get to this later! :-) use LWP( ); use CGI qw(-compile :all); use CGI::Carp( ); 1; # Must finish with a true value On httpd startup, the preceding Perl script will be run and will therefore load everything important directly into memory (use startup.pl to add other modules later on). It is run by adding the following lines to httpd.conf: PerlRequire conf/startup.pl PerlFreshRestart On Now we can restart Apache, this time with the added zest of mod_perl: $ /usr/local/apache/bin/apachectl restart /usr/local/apache/bin/apachectl start: httpd started This means we can also write our first new Apache module, to directly access the internal workings of the httpd server. $ cd /usr/local/apache/lib/perl/Apache $ vi HelloApache.pm Now create the test package, as in Example 5-2: Example 5-2. The HelloApache.pm modulepackage Apache::HelloApache; use strict; use Apache::Constants qw(:common); sub handler { my $r = shift; $r->content_type('text/html'); $r->send_http_header; my $host = $r->get_remote_host; $r->print(<<END); <HTML><HEAD><TITLE>HelloApache</TITLE></HEAD> <BODY><CENTER> <H1>Hello $host</H1> <H2>Okay, perhaps we should have said "Hello World!" but nobody expects Perl Sith Lords to do the expected! :-) <H2> </CENTER></BODY></HTML> END return OK; } 1; # Must finish with a true value Because this is a real live server upgrade, we need to tell httpd when to access this handler process. Again, edit httpd.conf: <Location /hello/apache> SetHandler perl-script PerlHandler Apache::HelloApache </Location> Now restart Apache and point your browser to http://localhost/hello/apache: $ /usr/local/apache/bin/apachectl restart /usr/local/apache/bin/apachectl restart: httpd restarted Ladies and Gentlemen, check out Figure 5-1. The mod_perl has landed. Figure 5-1. Our first Apache Perl module in sparkling formNow, you may acknowledge that this is all very nice and agree that mod_perl works much more efficiently than the plain Perl CGI alternative. But you may also have 200 debugged Perl CGI scripts, all of which work brilliantly from a functional point of view, and you may have very little free time available to spend converting these scripts to Apache server modules. So even though your scripts are eating up too much CPU (and management is thinking of Java servlets), you probably have little inclination to plunge into a major conversion effort. What can you do? Read on. 5.1.2 Apache Perl ModulesBecause you've embedded the Perl interpreter into the heart of the Apache server, the entire Apache server-side API is available to Perl programmers. The two modules listed here will provide you with the most bang for the buck in terms of managing your current collections of DBA CGI scripts:
We'll look at these two key modules in the following sections. All of the popular Apache mod_perl packages are summarized in Table 5-3.
5.1.2.1 Apache::RegistryTo take advantage of the performance advantages of mod_perl, you normally must rewrite your Perl CGI scripts in the form of server subroutines. Apache::Registry, which comes automatically with mod_perl, helps avoid this overhead. As we mentioned, you may already have a large number of working scripts that you use in performing Oracle database administration. There is nothing really wrong with them; the only problem is the overhead of their full execution cycle every time they're requested. This makes them processor-intensive (i.e., slow). You'd rather avoid rewriting them all as mod_perl scripts, but you would like to make them run faster — this is where Apache::Registry comes in. It takes CGI script calls, in the form of http://www.myhost.com/cgi-bin/cgi-script.pl, and evaluates them into server subroutines, thereby turning plain old scripts into much quicker mod_perl objects. These server subroutines remain resident in the Apache server's memory. You will generally find that using Apache::Registry gives you a massive power enhancement.
Note that each child process must compile at least once, so early requests may seem slow, but each subsequent request will be dealt with in Apache server memory and will seem very fast indeed. You will particularly notice this effect with large scripts or those with lots of module calls. Follow these steps to use Apache::Registry:
Example 5-3. The HelloInquisition.pl program#!/usr/bin/perl use strict; print "Content-Type: text/html\n\n"; print <<END; <HTML><HEAD><TITLE>HelloInquistion</TITLE></HEAD> <BODY><CENTER> <H1>Hello $ENV{REMOTE_ADDR}, are you comfortable? 8-)</H1> <H2>Nobody ever expects the HelloInquisition.pl Script!</H2> </CENTER></BODY></HTML> END Make the script executable and restart Apache to pick up the httpd.conf change: $ cd /usr/local/apache/perl $ chmod 755 *.pl $ /usr/local/apache/bin/apachectl restart /usr/local/apache/bin/apachectl restart: httpd restarted You can see the Perl script output in Figure 5-2. Figure 5-2. Apache::Registry linking CGI to mod_perlUse of the Apache::Registry module helps overcome the performance problem that occurs when the Perl interpreter has to be re-executed every time a Perl script is called. But there is also another source of performance problems with CGI scripts more closely linked to database usage. We'll discuss that in the next section. 5.1.2.2 Apache::DBIEach time you run a Perl CGI script that accesses a database, that script opens a new connection to the Oracle database at the beginning of the script, and then has to close it again at the end. This happens every single time you run the script, no matter how many thousands of people an hour are browsing the target page. This login process has a substantial overhead associated with it. It creates another performance issue for CGI scripts, one that even Apache::Registry can't overcome: even if the script is always in memory, it still has to open and close database connections. Edmund Mergl has provided an excellent solution. His Apache::DBI module is an extension to Apache that's written in Perl (and thus requires the presence of mod_perl). Once you load Apache::DBI, it pools, or caches, all of the required database connections into memory, lending them out the same way that ConnectionPool classes do for Java. Whenever this module detects that a CGI script is opening or closing a database connection, it simply steps in and takes over from DBI, handing out and collecting its pooled Oracle connections as necessary, closing and opening them independently of the CGI scripts in current operation. These cached connections are known as persistent connections because the connection to the database is kept persistent between sessions. Apache::DBI does its work entirely in the background, so you'll be aware only that your web site has become much faster and far more scalable — even more important, your database is doing less work! Unlike Apache::Registry, which comes preloaded with mod_perl, Apache::DBI is a supplementary module available from CPAN.[10] You can obtain it from:
Follow these steps:
Simply move your DBI web scripts to the target ../perl area to gain its benefit. 5.1.2.3 Apache and ORACLE_HOMEApache generally needs to know where your ORACLE_HOME is in order to get DBD::Oracle to work correctly. The easiest way of specifying any environment variable is to have a line such as the following in httpd.conf: PerlSetEnv ORACLE_HOME /opt/oracle/product/9.0.1 (We use PerlSetEnv, rather than Apache's usual SetEnv, because it is guaranteed to take effect before all of the mod_perl and Apache handlers run; their Perl additions may later require values such as ORACLE_HOME.) With persistent database connections now on board, let's give it a whirl. Try out the CGI script in Example 5-4. Notice that there's no explicit use of Apache::DBI within the script. It has been called off the bench in startup.pl, and mod_perl is holding it in memory for us under the floodlights, keeping it there until we send out the blade runners later on to shut down the Apache server daemons. Example 5-4. WaitsMonitor.pl#!/usr/bin/perl use strict; use DBI; use CGI qw(:standard :netscape); use CGI::Pretty qw(:html3); # Link to Oracle, this time via Apache::DBI in the background, # and set up our SQL to get our results. my $url = 'dbi:Oracle:orcl.world'; my $user = 'system'; my $passwd = 'manager'; my $dbh = DBI->connect($url, $user, $passwd, {RaiseError=>1, AutoCommit=>0}); my $sth = $dbh->prepare('select event Wait_Event, ' . 'total_waits Tot_Waits, ' . 'time_waited Times_Waited ' . 'from v$system_event ' . 'where event like \'%file%\' ' . 'order by total_waits desc ' ) ; $sth->execute or die "Cannot execute"; # Get the fieldnames, and make them into table headers. my $rs = $sth->{NAME}; my @col_head; for (@$rs) { push(@col_head, $_); } # Now get the data dough, and roll out the pastry my @row; my @rows; while (@row = $sth->fetchrow_array) { push(@rows, td(\@row)); } $dbh->disconnect; # Finished with DBI. Now we sort out the CGI side of life. my $title = "Welcome back to WaitsMonitor!"; # Create the HTML page. my $current_time = localtime( ); print header, start_html(-title=>$title, -bgcolor=>'white', -text=>'black'), center(h1($title), hr( ), table({border=>'2'}, caption($current_time), TR([th(\@col_head), @rows]) )), end_html; Make the script executable: $ cd /usr/local/apache/perl $ chmod 755 WaitsMonitor.pl You can see the results of this script's being called in Figure 5-3. Figure 5-3. Apache::DBI saving us connection time5.1.3 Installing mod_perl on Win32Fortunately for those of us who have just waded through the Unix installation of mod_perl, there is a binary version of mod_perl that was built for Win32 and Perl 5.6 by the heroic Randy Kobes. Installing it is very straightforward:
5.1.3.1 Configuring Apache on Win32We're nearly there. Before starting Apache on Win32, however, we need to add the following line to the httpd.conf file after all the other LoadModule statements: LoadModule perl_module modules/mod_perl.so Do this from the Win32 Start menu as follows: Start Programs Apache HTTP Server Configure Apache Server Edit the Apache httpd.conf Configuration File Also make sure you add the following line after the AddModule section: AddModule mod_perl.c Keep httpd.conf open at this point, and move onto the next stage. 5.1.3.2 Testing on Win32As with Unix, we need to load up the Count of Monte Cristo's chest of Apache jewels every time we fire up the server.
We can now write our first Win32 Perl Apache module. 5.1.3.3 HelloWin32.pmMove to our new perl\Apache directory: C:\ cd C:\Program Files\Apache Group\Apache\lib\perl\Apache We can create our new module, shown in Example 5-5, which does some checking on tablespace fragmentation via a subroutine called from the Apache handler process. (It's easy to forget, but what we're doing here really is quite amazing. We're right in the heart of the Apache server, changing it directly to make it do exactly what we want it to do. Could any other web server give you this kind of flexibility with something as relatively easy to use as Perl?) Example 5-5. HelloWin32.pmpackage Apache::HelloWin32; use strict; use DBI; use Apache::Constants qw(:common); use CGI qw(-compile :all); sub handler { my $r = shift; $r->content_type('text/html'); $r->send_http_header; my $host = $r->get_remote_host; my $table = tabspace_frag( ); $r->print(<<END); <HTML><HEAD><TITLE>Hello Win32</TITLE></HEAD><BODY> <H1>Hello $host - Let's do something half useful</H1><HR> $table </BODY></HTML> END return OK; } sub tabspace_frag { my $url = 'dbi:Oracle:orcl'; my $user = 'system'; my $passwd = 'manager'; my $dbh = DBI->connect($url, $user, $passwd, {RaiseError=>1}); my $sth = $dbh->prepare( 'SELECT ts.name tspace, ' . 'tf.blocks blocks, ' . 'sum(f.length) free, ' . 'count(*) pieces, ' . 'max(f.length) biggest, ' . 'min(f.length) smallest, ' . 'round(avg(f.length)) average, ' . 'sum(decode(sign(f.length-5), ' . '-1,f.length,0)) dead ' . 'FROM sys.fet$ f, sys.file$ tf, ' . 'sys.ts$ ts ' . 'WHERE ts.ts# = f.ts# ' . 'AND ts.ts# = tf.ts# ' . 'GROUP BY ts.name,tf.blocks'); $sth->execute; # Get the fieldnames, and make them into table headers. my $rs = $sth->{NAME}; my @col_head; for (@$rs) { push(@col_head, $_); } # Now get the data, to fill the table with shortly. my @row; my @rows; while (@row = $sth->fetchrow_array) { push(@rows, td(\@row)); } $dbh->disconnect; # Now we sort out CGI and return to handler. # Create the HTML page. return center(table({border=>'2'}, caption("Tablespace Fragmentation"), TR([th(\@col_head), @rows]))); } 1; # This is a package, therefore truth required Before we complete our test run, we need to make one final addition to the httpd.conf configuration file before running the server: <Location /hello/win32> SetHandler perl-script PerlHandler Apache::HelloWin32 </Location> We can now set Apache running: Start-> Programs-> Apache HTTP Server-> Start Apache in Console You can see the spectacular results in Figure 5-4. Figure 5-4. HelloWin32.pm attempting to be half useful |