home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


Writing Apache Modules with Perl and C
By:   Lincoln Stein and Doug MacEachern
Published:   O'Reilly & Associates, Inc.  - March 1999

Copyright © 1999 by O'Reilly & Associates, Inc.


 


   Show Contents   Previous Page   Next Page

Chapter 8 - Customizing the Apache Configuration Process / Configuring Apache with Perl
A Real-Life Example

For a complete example of an Apache configuration constructed with <Perl> sections, we'll look at Doug's setup. As a freelance contractor, Doug must often configure his development server in a brand-new environment. Rather than creating a customized server configuration file each time, Doug uses a generic configuration that can be brought up anywhere, simply by running:

% httpd -f $HOME/httpd.conf

This one step automatically creates the server and document roots if they don't exist, as well as the log and configuration directories. It also detects the user that it is being run as, and configures the User and Group directives to match.

Example 8-5 shows a slightly simplified version of Doug's httpd.conf. It contains only two hard-coded Apache directives:

# file: httpd.conf
PerlPassEnv HOME
Port 9008

There's a PerlPassEnv directive with the value of HOME, required in order to make the value of this environment variable visible to the code contained within the <Perl> section, and there's a Port directive set to Doug's favorite port number.

The rest of the configuration file is written entirely in Perl:

<Perl>
#!perl
$ServerRoot = "$ENV{HOME}/www";

The <Perl> section begins by choosing a path for the server root. Doug likes to have his test environment set up under his home directory in ~/www, so the variable $ServerRoot is set to $ENV{HOME}/www. The server root will now be correctly configured regardless of whether users' directories are stored under /home, /users, or /var/users.

unless (-d "$ServerRoot/logs") {
   for my $dir ("", qw(logs conf htdocs perl)) {
       mkdir "$ServerRoot/$dir", 0755;
   }
   require File::Copy;
   File::Copy::cp($0, "$ServerRoot/conf");
}

Next, the code detects whether the server root has been properly initialized and, if not, creates the requisite directories and subdirectories. It looks to see whether $ServerRoot/logs exists and is a directory. If not, the code proceeds to create the directories, calling mkdir() repeatedly to create first the server root and subsequently logs, conf, htdocs, and perl subdirectories beneath it. The code then copies the generic httpd.conf file that is currently running into the newly created conf subdirectory, using the File::Copy module's cp() routine. Somewhat magically, mod_perl arranges for the Perl global variable $0 to hold the path of the .conf file that is currently being processed.

if(-e "$ServerRoot/startup.pl") {
   $PerlRequire = "startup.pl";
}

Next, the code checks whether there is a startup.pl present in the configuration directory. If this is the first time the server is being run, the file won't be present, but there may well be one there later. If the file exists, the code sets the $PerlRequire global to load it.

$User  = getpwuid($>) || $>;
$Group = getgrgid($)) || $);
$ServerAdmin = $User;

The code sets the User, Group, and ServerAdmin directives next. The user and group are taken from the Perl magic variables $> and $), corresponding to the user and group IDs of the person who launched the server. Since this is the default when Apache is run from a nonroot shell, this has no effect now but will be of use if the server is run as root at a later date. Likewise, $ServerAdmin is set to the name of the current user.

$ServerName = `hostname`;
$DocumentRoot = "$ServerRoot/htdocs";
my $types = "$ServerRoot/conf/mime.types";
$TypesConfig = -e $types ? $types : "/dev/null";

The server name is set to the current host's name by setting the $ServerName global, and the document root is set to $ServerRoot/htdocs. We look to see whether the configuration file mime.types is present and, if so, use it to set $TypesConfig to this value. Otherwise, we use /dev/null.

push @Alias,
       ["/perl"   => "$ServerRoot/perl"],
       ["/icons"  => "$ServerRoot/icons"];

Next, the <Perl> section declares some directory aliases. The URI /perl is aliased to $ServerRoot/perl, and /icons is aliased to $ServerRoot/icons. Notice how the @Alias global is set to an array of arrays in order to express that it contains multiple Alias directives.

my $servers = 3;
for my $s (qw(MinSpareServers MaxSpareServers StartServers MaxClients)) {
   $$s = $servers;
}

Following this, the code sets the various parameters controlling Apache's preforking. The server doesn't need to handle much load, since it's just Doug's development server, so MaxSpareServers and friends are all set to a low value of three. We use "symbolic" or "soft" references here to set the globals indirectly. We loop through a set of strings containing the names of the globals we wish to set, and assign values to them as if they were scalar references rather than plain strings. Perl automatically updates the symbol table for us, avoiding the much more convoluted code that would be required to create the global using globs or by accessing the symbol table directly. Note that this technique will be blocked if strict reference checking is turned on with use strict 'refs'.

for my $l (qw(LockFile ErrorLog TransferLog PidFile ScoreBoardFile)) {
   $$l = "logs/$l";
    #clean out the logs
   local *FH;
   open FH, ">$ServerRoot/$$l";
   close FH;
}

We use a similar trick to configure the LockFile, ErrorLog, TransferLog, and other log file-related directives. A few additional lines of code truncate the various log files to zero length if they already exist. Doug likes to start with a clean slate every time he reconfigures and restarts a server.

my @mod_perl_cfg = qw{
 SetHandler perl-script
 Options    +ExecCGI
};
$Location{"/perl-status"} = {
   @mod_perl_cfg,
   PerlHandler => "Apache::Status",
};
$Location{"/perl"} = {
   @mod_perl_cfg,
   PerlHandler => "Apache::Registry",
};

The remainder of the configuration file sets up some directories for running and debugging Perl API modules. We create a lexical variable named @mod_perl_cfg that contains some common options, and then use it to configure the /perl-status and /perl <Location> sections. The /perl-status URI is set up so that it runs Apache::Status when retrieved, and /perl is put under the control of Apache::Registry for use with registry scripts.

use Apache::PerlSections ();
Apache::PerlSections->store("$ServerRoot/ServerConfig.pm");

The very last thing that the <Perl> section does is to write out the current configuration into the file $ServerRoot/ServerConfig.pm. This snapshots the current configuration in a form that Doug can review and edit, if necessary. Just the configuration variables set within the <Perl> section are snapshot. The PerlPass-Env and Port directives, which are outside the section, are not captured and will have to be added manually.

This technique makes possible the following interesting trick:

% httpd -C "PerlModule ServerConfig"

The -C switch tells httpd to process the directive PerlModule, which in turn loads the module file ServerConfig.pm. Provided that Perl's PERL5LIB environment variable is set up in such a way that Perl will be able to find the module, this has the effect of reloading the previously saved configuration and setting Apache to exactly the same state it had before.

Example 8-5. Doug's Generic httpd.conf

# file: httpd.conf
PerlPassEnv HOME
Port 9008
<Perl>
#!perl
$ServerRoot = "$ENV{HOME}/www";
unless (-d "$ServerRoot/logs") {
   for my $dir ("", qw(logs conf htdocs perl)) {
       mkdir "$ServerRoot/$dir", 0755;
   }
   require File::Copy;
   File::Copy::cp($0, "$ServerRoot/conf");
}
if(-e "$ServerRoot/startup.pl") {
   $PerlRequire = "startup.pl";
}
$User  = getpwuid($>) || $>;
$Group = getgrgid($)) || $);
$ServerAdmin = $User;
$ServerName = `hostname`;
$DocumentRoot = "$ServerRoot/htdocs";
my $types = "$ServerRoot/conf/mime.types";
$TypesConfig = -e $types ? $types : "/dev/null";
push @Alias,
       ["/perl"   => "$ServerRoot/perl"],
       ["/icons"  => "$ServerRoot/icons"];
my $servers = 3;
for my $s (qw(MinSpareServers MaxSpareServers StartServers MaxClients)) {
   $$s = $servers;
}
for my $l (qw(LockFile ErrorLog TransferLog PidFile ScoreBoardFile)) {
   $$l = "logs/$l";
    #clean out the logs
   local *FH;
   open FH, ">$ServerRoot/$$l";
   close FH;
}
my @mod_perl_cfg = qw{
 SetHandler perl-script
 Options    +ExecCGI
};
$Location{"/perl-status"} = {
   @mod_perl_cfg,
   PerlHandler => "Apache::Status",
};
$Location{"/perl"} = {
   @mod_perl_cfg,
   PerlHandler => "Apache::Registry",
};
use Apache::PerlSections ();
Apache::PerlSections->store("$ServerRoot/ServerConfig.pm");
__END__ </Perl>

   Show Contents   Previous Page   Next Page
Copyright © 1999 by O'Reilly & Associates, Inc.