I want to enumerate a variety of ways to configure apache (v2.2.9) with/without modperl (v2.0.4) for passing requests to perl code. I want to cover both the situations where we have full access to configure apache (in-house server) and those situations where we don’t (third-party server).

To set a base line, we’ll assume there are other apache-driven perl applications on the same server, and the application we’re configuring has multiple modules. I’d like to avoid URL rewriting, but have URLs as clean as is practical. When running from modperl, I’d like to maximise the setup done at server start-up and minimise the work done per hit. I’ll give consideration to efficiency, scalability, and maintainability.

The application is a specialisation of CGI::Application, resides in /srv/trial and has the dir structure suggested at Layout for a CGI::Application. This means we need to prepend ‘/srv/trial/mod:/srv/trial/lib:‘ to the @INC path.


Pieces of the puzzle

First we’ll go through the pieces of the jigsaw before looking at how they are typically put together.

Interpreter start up and state isolation

PerlOptions Clone

PerlOptions +Clone directive will share the parent (apache) perl interpreter but specific to the VirtualHost. This is most useful if the hosts load distinct (large) modules or load the same modules with distinct parameters. For example, one uses catalyst and the other uses CGI::Application. Or one loads use Cari::Mysql (cnfdir => '/var/local/auth/abc'); while the other loads use Cari::Mysql (cnfdir => '/var/local/auth/xyz');.

PerlOptions Parent

PerlOptions +Parent directive will create a new parent perl interpreter (for this scope). This is very similar to Clone above, but it does not inherit from above and the scope can be more specific than the VirtualHost.
Example from mod_perl2 docs

<Location /trial>
  PerlOptions +Parent
  PerlSwitches -I/srv/trial/mod -I/srv/trial/lib
  PerlInterpStart 1
  PerlInterpMax 4
</Location>
<Location /trial2>
  PerlOptions +Parent
  PerlSwitches -I/srv/trial2/mod -I/srv/trial2/lib
  PerlInterpStart 1
  PerlInterpMax 2
</Location>

[NB: Although that example is lifted from the modperl docn, it doesn’t work for me]

Set modules search path

PerlOptions +Parent
PerlSwitches -I/srv/trial/mod
PerlSwitches -I/srv/trial/lib

Which is equivalent to the following (note the path declarations are swapped).

PerlOptions +Parent
PerlSwitches -Mlib=/srv/trial/lib
PerlSwitches -Mlib=/srv/trial/mod

Alternatively, you can use a startup script

PerlPostConfigRequire /srv/trial/cfg/startup.pl

Passing to the handler

The usual handler type is perl-script. It takes care of setting up and isolating %ENV and ties STDIN and STDOUT to make request object IO easy. To make life simple, always use perl-script when returning a response body.
In those rare situations where you don’t need that support, you can gain a little performance by using instead modperl. Using this route, the only %ENV vars are MOD_PERL, MOD_PERL_API_VERSION, PATH, TZ.
If your handler is written in OO style (ie expects class/object as first param) then you have a choice between

<Location /gateway>
  PerlResponseHandler Gateway
</Location>
package Gateway;
sub handler : method {
    my ($proto, $r) = @_;

or writing the call within the apache configuration

<Location /gateway>
  PerlResponseHandler Gateway->handler
</Location>
package Gateway;
sub handler {
    my ($proto, $r) = @_;

Other pieces

We haven’t discussed decisions/consequences of MPM choice. (I stick to prefork when I can.)


Example scenarios

Now we’ve seen the key pieces of the puzzle, here are some sample ways of putting them together for various scenarios.

Dirty CGI

The following will spawn a new child perl per request; that is expensive but ensures state changes in the code can’t bleed out to other code, nor even subsequent hits on the same code.
Example from mod_perl2 docs

<Location /cgi-bin>
  PerlOptions +Parent
  PerlInterpMaxRequests 1
  PerlInterpStart 1
  PerlInterpMax 1
  PerlResponseHandler ModPerl::Registry
</Location>

[NB: Although that example is lifted from the modperl docn, it doesn’t work for me]

CGI directory

ScriptAlias /trial/ /srv/trial/cgi/
<Directory /srv/trial/cgi>
  AllowOverride None
  Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
</Directory>

Vanilla CGI setup

<Location /perl>
  SetHandler perl-script
  PerlHandler ModPerl::Registry
  Options ExecCGI
  PerlOptions +ParseHeaders
</Location>
<Location /cgi-bin>
  SetHandler perl-script
  PerlHandler ModPerl::PerlRun
  Options ExecCGI
  PerlOptions +ParseHeaders
</Location>

CGI::Application

DocumentRoot /srv/ebdb/www
<Directory /srv/ebdb/www>
  Options -Indexes -Multiviews +FollowSymLinks
  AllowOverride None
<Directory>
PerlOptions +Parent
PerlSwitches -I/srv/ebdb/mod -I/srv/ebdb/lib
<Location /ebdb>
  PerlInterpStart 1
  PerlInterpMinSpare 1
  PerlInterpMaxSpare 4
  SetHandler perl-script
  PerlHandler C::Dispatch
  PerlSetVar DISPATCH_DEBUG 1
</Location>

Directives scope

Server scoped directives

  • PerlSwitches
  • PerlPostConfigRequire
  • PerlModule
  • PerlInterpStart
  • PerlInterpMax

Directory scoped directives

  • PerlOptions
  • PerlSetVar
  • PerlAddVar
  • PerlSetEnv
  • PerlResponseHandler