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

In this section...

Introduction
Debugging <Perl> Sections
Simple Dynamic Configuration
A Real-Life Example

Introduction

   Show Contents   Go to Top   Previous Page   Next Page

We've just seen how you can configure Perl modules using the Apache configuration mechanism. Now we turn it around to show you how to configure Apache from within Perl. Instead of configuring Apache by hand (editing a set of configuration files), the Perl API allows you to write a set of Perl statements to dynamically configure Apache at runtime. This gives you limitless flexibility. For example, you can create complex configurations involving hundreds of virtual hosts without manually typing hundreds of <VirtualHost> sections into httpd.conf. Or you can write a master configuration file that will work without modification on any machine in a "server farm." You could even look up configuration information at runtime from a relational database.

The key to Perl-based server configuration is the <Perl> directive. Unlike the other directives defined by mod_perl, this directive is paired to a corresponding </Perl> directive, forming a Perl section.

When Apache hits a Perl section during startup time, it passes everything within the section to mod_perl. mod_perl, in turn, compiles the contents of the section by evaluating it inside the Apache::ReadConfig package. After compilation is finished, mod_perl walks the Apache::ReadConfig symbol table looking for global variables with the same names as Apache's configuration directives. The values of those globals are then fed into Apache's normal configuration mechanism as if they'd been typed directly into the configuration file. The upshot of all this is that instead of setting the account under which the server runs with the User directive:

User www

you can write this:

<Perl>
 $User = 'www';
</Perl>

This doesn't look like much of a win until you consider that you can set this global using any arbitrary Perl expression, for example:

<Perl>
 my $hostname = `hostname`;
 $User = 'www'     if $hostname =~ /^papa-bear/;
 $User = 'httpd'   if $hostname =~ /^momma-bear/;
 $User = 'nobody'  if $hostname =~ /^goldilocks/;
</Perl>

The Perl global that you set must match the spelling of the corresponding Apache directive. Globals that do not match known Apache directives are silently ignored. Capitalization is not currently significant.

In addition to single-valued directives such as User, Group, and ServerRoot, you can use <Perl> sections to set multivalued directives such as DirectoryIndex and AddType. You can also configure multipart sections such as <Directory> and <VirtualHost>. Depending on the directive, the Perl global you need to set may be a scalar, an array, or a hash. To figure out what type of Perl variable to use, follow these rules:

Directive takes no arguments

There are few examples of configuration directives that take no arguments. The only one that occurs in the standard Apache modules is CacheNegotiatedDocs, which is part of mod_ negotiation. To create a nonargument directive, set the corresponding scalar variable to the empty string '':

$CacheNegotiatedDocs = '';
Directive takes one argument

This is probably the most common case. Set the corresponding global to the value of your choice.

$Port = 8080;
Directive takes multiple arguments

These include directives such as DirectoryIndex and AddType. Create a global array with the name of the directive and set it to the list of desired arguments.

@DirectoryIndex = map { "index.$_" } qw(html htm shtml cgi);

An alternative to this is to create a scalar variable containing the usual value of the directive as a string, for example:

$DirectoryIndex = "index.html index.htm index.shtml index.cgi";
Directive is repeated multiple times

If a directive is repeated multiple times with different arguments each time, you can represent it as an array of arrays. This example using the AddIcon directive shows how:

@AddIcon = (
      [ '/icons/compressed.gif' => qw(.Z .z .gz .tgz .zip) ],
      [ '/icons/layout.gif'     => qw(.html .shtml .htm .pdf) ],
);
Directive is a block section with begin and end tags

Configuration sections like <VirtualHost> and <Directory> are mapped onto Perl hashes. Use the directive's argument (the hostname, directory, or URI) as the hash key, and make the value stored at this key an anonymous hash containing the desired directive/value pairs. This is easier to see than to describe. Consider the following virtual host section:

<VirtualHost 192.168.2.5:80>
  ServerName   www.fishfries.org
  DocumentRoot /home/httpd/fishfries/htdocs
  ErrorLog     /home/httpd/fishfries/logs/error.log
  TransferLog  /home/httpd/fishfries/logs/access.log
  ServerAdmin  webmaster@fishfries.org
</Virtual>

You can represent this in a <Perl> section by the following code:

$VirtualHost{'192.168.2.5:80'} = {
  ServerName   => 'www.fishfries.org',
  DocumentRoot => '/home/httpd/fishfries/htdocs',
  ErrorLog     => '/home/httpd/fishfries/logs/error.log',
  TransferLog  => '/home/httpd/fishfries/logs/access.log',
  ServerAdmin  => 'webmaster@fishfries.org',
};

There is no special Perl variable which maps to the <IfModule> directive container; however, the Apache module method will provide you with this functionality.

if(Apache->module("mod_ssl.c")) {
   push @Include, "ssl.conf";
}

The Apache define() method can be used to implement an <IfDefine> container, as follows:

if(Apache->define("MOD_SSL")) {
   push @Include, "ssl.conf";
}

Certain configuration blocks may require directives to be in a particular order. As you probably know, Perl does not maintain hash values in any predictable order. Should you need to preserve order with hashes inside <Perl> sections, simply install Gurusamy Sarathy's Tie::IxHash module from CPAN. Once installed, mod_perl will tie %VirtualHost, %Directory, %Location, and %Files hashes to this class, preserving their order when the Apache configuration is generated.

Directive is a block section with multiple same-value keys

The Apache named virtual host mechanism provides a way to configure virtual hosts using the same IP address.

NameVirtualHost 192.168.2.5
<VirtualHost 192.168.2.5>
 ServerName one.fish.net
 ServerAdmin webmaster@one.fish.net
</VirtualHost>
<VirtualHost 192.168.2.5>
 ServerName red.fish.net
 ServerAdmin webmaster@red.fish.net
</VirtualHost>

In this case, the %VirtualHost syntax from the previous section would not work, since assigning a hash reference for the given IP address will overwrite the original entry. The solution is to use an array reference whose values are hash references, one for each virtual host entry. Example:

$VirtualHost{'192.168.2.5'} = [
  {
     ServerName   => 'one.fish.net',
     ...
     ServerAdmin  => 'webmaster@one.fish.net',
  },
  {
     ServerName   => 'red.fish.net',
     ...
     ServerAdmin  => 'webmaster@red.fish.net',
  },
];
Directive is a nested block

Nested block sections are mapped onto anonymous hashes, much like main sections. For example, to put two <Directory> sections inside the virtual host of the previous example, you can use this code:

<Perl>
my $root = '/home/httpd/fishfries';
$VirtualHost{'192.168.2.5:80'} = {
  ServerName   => 'www.fishfries.org',
  DocumentRoot => "$root/htdocs",
  ErrorLog     => "$root/logs/error.log",
  TransferLog  => "$root/logs/access.log",
  ServerAdmin  => 'webmaster@fishfries.org',
  Directory     => {
      "$root/htdocs"   => {
            Options => 'Indexes FollowSymlinks',
            AllowOverride => 'Options Indexes Limit FileInfo',
            Order   => 'deny,allow',
            Deny    => 'from all',
            Allow   => 'from fishfries.org',
      },
      "$root/cgi-bin" => {
            AllowOverride => 'None',
            Options       => 'ExecCGI',
            SetHandler    => 'cgi-script',
      },
   },
};
</Perl>

Notice that all the usual Perlisms, such as interpolation of the $root variable into the double-quoted strings, still work here. Another thing to see in this example is that in this case we've chosen to write the multivalued Options directive as a single string:

Options => 'Indexes FollowSymlinks',

The alternative would be to use an anonymous array for the directive's arguments, as in:

Options => ['Indexes','FollowSymlinks'],

Both methods work. The only gotcha is that you must always be sure of what is an argument list and what isn't. In the Options directive, "Indexes" and "FollowSymlinks" are distinct arguments and can be represented as an anonymous array. In the Order directive, the string deny,allow is a single argument, and representing it as the array ['deny','allow'] will not work, even though it looks like it should (use the string deny,allow instead).

<Perl> sections are available if you built and installed mod_perl with the PERL_SECTIONS configuration variable set (Appendix B, Building and Installing mod_perl). They are evaluated in the order in which they appear in httpd.conf, srm.conf, and access.conf. This allows you to use later <Perl> sections to override values declared in earlier parts of the configuration files.

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