<Perl>sections can contain any and as much Perl code as you wish. <Perl>sections are compiled into a special package called Apache::ReadConfig. mod_perl looks through the symbol table for Apache::ReadConfig for Perl variables and structures to grind through the Apache core configuration gears. Most of the configuration directives can be represented as scalars ($scalar) or arrays (@array). A few directives become hashes.
How do you know which Perl global variables to use? Just take the Apache directive name and prepend either $, @, or % (as shown in the following examples), depending on what the directive accepts. If you misspell the directive, it is silently ignored, so it's a good idea to check your settings.
Since Apache directives are case-insensitive, their Perl equivalents are case-insensitive as well. The following statements are equivalent:
$User = 'stas'; $user = 'stas'; # the same
Let's look at all possible cases we might encounter while configuring Apache in Perl:
Directives that accept zero or one argument are represented as scalars. For example, CacheNegotiatedDocs is a directive with no arguments. In Perl, we just assign it an empty string:
<Perl>
$CacheNegotiatedDocs = '';
</Perl>
Directives that accept a single value are simple to handle. For example, to configure Apache so that child processes run as user httpd and group httpd, use:
User = httpd Group = httpd
What if we don't want user and group definitions to be hardcoded? Instead, what if we want to define them on the fly using the user and group with which the server is started? This is easily done with <Perl>sections:
<Perl>
$User = getpwuid($>) || $>;
$Group = getgrgid($)) || $);
</Perl>
We use the power of the Perl API to retrieve the data on the fly. $User is set to the name of the effective user ID with which the server was started or, if the name is not defined, the numeric user ID. Similarly, $Group is set to either the symbolic value of the effective group ID or the numeric group ID.
Notice that we've just taken the Apache directives and prepended a $, as they represent scalars.
Directives that accept more than one argument are represented as arrays or as a space-delimited string. For example, this directive:
PerlModule Mail::Send Devel::Peek
becomes:
<Perl>
@PerlModule = qw(Mail::Send Devel::Peek);
</Perl>
@PerlModule is an array variable, and we assign it a list of modules. Alternatively, we can use the scalar notation and pass all the arguments as a space-delimited string:
<Perl>
$PerlModule = "Mail::Send Devel::Peek";
</Perl>
Directives that can be repeated more than once with different values are represented as arrays of arrays. For example, this configuration:
AddEncoding x-compress Z AddEncoding x-gzip gz tgz
becomes:
<Perl>
@AddEncoding = (
['x-compress' => qw(Z)],
['x-gzip' => qw(gz tgz)],
);
</Perl>
Directives that implement a container block, with beginning and ending delimiters such as <Location> ... </Location>, are represented as Perl hashes. In these hashes, the keys are the arguments of the opening directive, and the values are the contents of the block. For example:
Alias /private /home/httpd/docs/private
<Location /private>
DirectoryIndex index.html index.htm
AuthType Basic
AuthName "Private Area"
AuthUserFile /home/httpd/docs/private/.htpasswd
Require valid-user
</Location>
These settings tell Apache that URIs starting with /private are mapped to the physical directory /home/httpd/docs/private/ and will be processed according to the following rules:
The users are to be authenticated using basic authentication.
PrivateArea will be used as the title of the pop-up box displaying the login and password entry form.
Only valid users listed in the password file /home/httpd/docs/private/.htpasswd and who provide a valid password may access the resources under /private/.
If the filename is not provided, Apache will attempt to respond with the index.html or index.htm directory index file, if found.
Now let's see the equivalent <Perl>section:
<Perl>
push @Alias, qw(/private /home/httpd/docs/private);
$Location{"/private"} = {
DirectoryIndex => [qw(index.html index.htm)],
AuthType => 'Basic',
AuthName => '"Private Area"',
AuthUserFile => '/home/httpd/docs/private/.htpasswd',
Require => 'valid-user',
};
</Perl>
First, we convert the Alias directive into an array @Alias. Instead of assigning, however, we push the values at the end. We do this because it's possible that we have assigned values earlier, and we don't want to overwrite them. Alternatively, you may want to push references to lists, like this:
push @Alias, [qw(/private /home/httpd/docs/private)];
Second, we convert the Location block, using /private as a key to the hash %Location and the rest of the block as its value. When the structures are nested, the normal Perl rules apply—that is, arrays and hashes turn into references. Therefore, DirectoryIndex points to an array reference. As shown earlier, we can always replace this array with a space-delimited string:
$Location{"/private"} = {
DirectoryIndex => 'index.html index.htm',
...
};
Also notice how we specify the value of the AuthName attribute:
AuthName => '"Private Area"',
The value is quoted twice because Apache expects a single value for this argument, and if we write:
AuthName => 'Private Area',
<Perl> will pass two values to Apache, "Private" and "Area", and Apache will refuse to start, with the following complaint:
[Thu May 16 17:01:20 2002] [error] <Perl>: AuthName takes one argument, The authentication realm (e.g. "Members Only")
If a block section accepts two or more identical keys (as the <VirtualHost> ... </VirtualHost>section does), the same rules as in the previous case apply, but a reference to an array of hashes is used instead.
In one company, we had to run an Intranet machine behind a NAT/firewall (using the 10.0.0.10 IP address). We decided up front to have two virtual hosts to make both the management and the programmers happy. We had the following simplistic setup:
NameVirtualHost 10.0.0.10
<VirtualHost 10.0.0.10>
ServerName tech.intranet
DocumentRoot /home/httpd/docs/tech
ServerAdmin webmaster@tech.intranet
</VirtualHost>
<VirtualHost 10.0.0.10>
ServerName suit.intranet
DocumentRoot /home/httpd/docs/suit
ServerAdmin webmaster@suit.intranet
</VirtualHost>
In Perl, we wrote it as follows:
<Perl>
$NameVirtualHost => '10.0.0.10';
my $doc_root = "/home/httpd/docs";
$VirtualHost{'10.0.0.10'} = [
{
ServerName => 'tech.intranet',
DocumentRoot => "$doc_root/tech",
ServerAdmin => 'webmaster@tech.intranet',
},
{
ServerName => 'suit.intranet',
DocumentRoot => "$doc_root/suit",
ServerAdmin => 'webmaster@suit.intranet',
},
];
</Perl>
Because normal Perl rules apply, more entries can be added as needed using push( ).[28] Let's say we want to create a special virtual host for the company's president to show off to his golf partners, but his fancy vision doesn't really fit the purpose of the Intranet site. We just let him handle his own site:
[28]For complex configurations with multiple entries, consider using the module Tie::DxHash, which implements a hash that preserves insertion order and allows duplicate keys.
push @{ $VirtualHost{'10.0.0.10'} },
{
ServerName => 'president.intranet',
DocumentRoot => "$doc_root/president",
ServerAdmin => 'webmaster@president.intranet',
};
Nested block directives naturally become Perl nested data structures. Let's extend an example from the previous section:
<Perl>
my $doc_root = "/home/httpd/docs";
push @{ $VirtualHost{'10.0.0.10'} },
{
ServerName => 'president.intranet',
DocumentRoot => "$doc_root/president",
ServerAdmin => 'webmaster@president.intranet',
Location => {
"/private" => {
Options => 'Indexes',
AllowOverride => 'None',
AuthType => 'Basic',
AuthName => '"Do Not Enter"',
AuthUserFile => 'private/.htpasswd',
Require => 'valid-user',
},
"/perlrun" => {
SetHandler => 'perl-script',
PerlHandler => 'Apache::PerlRun',
PerlSendHeader => 'On',
Options => '+ExecCGI',
},
},
};
</Perl>
We have added two Location blocks. The first, /private, is for the juicy stuff and accessible only to users listed in the president's password file. The second, /perlrun, is for running dirty Perl CGI scripts, to be handled by the Apache::PerlRun handler.
<Perl>sections don't provide equivalents for <IfModule> and <IfDefine> containers. Instead, you can use the module( ) and define( ) methods from the Apache package. For example:
<IfModule mod_ssl.c>
Include ssl.conf
</IfModule>
can be written as:
if (Apache->module("mod_ssl.c")) {
push @Include, "ssl.conf";
}
And this configuration example:
<IfDefine SSL>
Include ssl.conf
</IfDefine>
can be written as:
if (Apache->define("SSL")) {
push @Include, "ssl.conf";
}
Now that you know how to convert the usual configuration directives to Perl code, there's no limit to what you can do with it. For example, you can put environment variables in an array and then pass them all to the children with a single configuration directive, rather than listing each one via PassEnv or PerlPassEnv:
<Perl>
my @env = qw(MYSQL_HOME CVS_RSH);
push @PerlPassEnv, \@env;
</Perl>
Or suppose you have a cluster of machines with similar configurations and only small distinctions between them. Ideally, you would want to maintain a single configuration file, but because the configurations aren't exactly the same (for example, the ServerName directive will have to differ), it's not quite that simple.
<Perl>sections come to the rescue. Now you can have a single configuration file and use the full power of Perl to tweak the local configuration. For example, to solve the problem of the ServerName directive, you might have this <Perl>section:
<Perl>
use Sys::Hostname;
$ServerName = hostname( );
</Perl>
and the right machine name will be assigned automatically.
Or, if you want to allow personal directories on all machines except the ones whose names start with secure, you can use:
<Perl>
use Sys::Hostname;
$ServerName = hostname( );
if ($ServerName !~ /^secure/) {
$UserDir = "public.html";
}
</Perl>
 
Continue to: