March 2010 Archives

vhosts with modperl

| No Comments

This post deals with configuring mod_perl environments with vhosts. Our objects for the post are to configure an Apache2 prefork server with mod_perl 2.0 with the following specifications:

1) Each vhost should have its own interpretter pool with its own paths.
2) vhosts do not need to use custom directives during startup.
3) Custom liraries in each vhost should not be shared with the others.
4) Users should be able to execute mod_perl scripts only from docs/bin.

Here's a snip from my apache2ctl status: "Server Version: Apache/2.2.11 (Ubuntu) mod_apreq2-20051231/2.6.0 mod_perl/2.0.4 Perl/v5.10.0"

Let's get started!

I have three vhosts {a, b, c} each listening on ports 5050, 8080, and 9090 respectively.

Here's my initial /etc/apache2/sites-available/vhosts.conf file:


<VirtualHost *:5050>
DocumentRoot /var/www/a
#PerlPostConfigHandler MyApache2::Test->post_config
</VirtualHost>

<VirtualHost *:8080>
DocumentRoot /var/www/b
#PerlPostConfigHandler MyApache2::Test->post_config
</VirtualHost>

<VirtualHost *:9090>
DocumentRoot /var/www/c
#PerlPostConfigHandler MyApache2::Test->post_config
</VirtualHost>


Here's my initial /etc/apache2/httpd.conf file:

ServerName localhost


PerlSwitches -I/var/myperl/dep_lib
PerlPostConfigHandler MyApache2::Test->post_config

/var/myperl/dep_lib/MyApache2::Test.pm looks like this:


package MyApache2::Test;
use Apache2::ServerRec;
use Apache2::Const -compile => OK
use strict;
use warnings;

sub post_config {
my ($self, $conf_pool, $log_pool, $temp_pool, $s) = @_;
warn "HELLO " . $s->port . "\n";
}

1;

When we start the server, this is the output we get:

root@larx:~# tail -fn 0 /var/log/apache2/error.log
HELLO 0
HELLO 0
[Tue Mar 09 19:09:00 2010] [notice] Apache/2.2.11 (Ubuntu) mod_apreq2-20051231/2.6.0 mod_perl/2.0.4 Perl/v5.10.0 configured -- resuming normal operations
[Tue Mar 09 19:09:00 2010] [info] Server built: Nov 13 2009 21:59:28
[Tue Mar 09 19:09:00 2010] [debug] prefork.c(1032): AcceptMutex: sysvsem (default: sysvsem)

This shows that the global configuration (*:0) is saying hello when it calls the PostConfigHandler. After the first call to PerlPostConfigHandler, Apache restarts itself to test its graceful restart ability. [1]

Now what happens when we try to implement a PostConfigHandler in a VirtualHost? According to the documentation, since PerlPostConfigHandler is valid in the SRV scope, we should be able to do this:

/etc/apache2/httpd.conf:


ServerName localhost
PerlSwitches -I/var/myperl/dep_lib
#PerlPostConfigHandler MyApache2::Test->post_config

/etc/apache2/site-available/vhosts.conf:


<VirtualHost *:5050>
DocumentRoot /var/www/a
PerlSwitches -I/var/myperl/dep_lib
PerlPostConfigHandler MyApache2::Test->post_config
</VirtualHost>

<VirtualHost *:8080>
DocumentRoot /var/www/b
</VirtualHost>

<VirtualHost *:9090>
DocumentRoot /var/www/c
</VirtualHost>


and the startup:

root@larx:~# tail -fn 0 /var/log/apache2/error.log
[Tue Mar 09 19:19:23 2010] [notice] Apache/2.2.11 (Ubuntu) mod_apreq2-20051231/2.6.0 mod_perl/2.0.4 Perl/v5.10.0 configured -- resuming normal operations
[Tue Mar 09 19:19:23 2010] [info] Server built: Nov 13 2009 21:59:28
[Tue Mar 09 19:19:23 2010] [debug] prefork.c(1032): AcceptMutex: sysvsem (default: sysvsem)

Notice that the PerlPostConfigHandler hasn't said anything. This is actually by design, contrary to what the documentation might say. In reality, PerlPostConfigHandler is only valid in the server-wide configuration context, NOT in vhosts. There are two ways to get around this.

1) Patch your mod_perl installation using a patch (possibly outdated) available on the mod-perl mailing lists here [2].
2) Use startup.pl files in a per-host configuration scheme.

Since I have less than no desire to go hacking around inside the monster, I've opted for choice 2.

To achieve this, we need to provide each vhost with a way to load some file at server startup. In /var/www/(a|b|c) we place a file called startup.pl that looks remarkably like this: (just make sure to s/a/(b|c) when and where appropriate)

/var/www/a/startup.pl:


#!/usr/bin/perl

use lib qw( /var/www/a/lib /var/www/a/lib/dep_lib );

use Data::Dumper;

use warnings;
use strict;

warn "Dumping the A startup.pl:\n";
print STDERR Dumper(\@INC);
warn "Finished processing A startup!\n";

1;

Notice that "1;". That's pretty important since the script is effectively being require()'d by mod_perl and therefore must return true.

Next we need to configure the vhost:

/etc/apache2/sites-available/vhosts.conf:


<VirtualHost *:5050>
DocumentRoot /var/www/a
PerlPostConfigRequire /var/www/a/startup.pl
</VirtualHost>

<VirtualHost *:8080>
DocumentRoot /var/www/b
</VirtualHost>

<VirtualHost *:9090>
DocumentRoot /var/www/c
</VirtualHost>


This is the outcome from a startup run:

root@larx:~# tail -fn 0 /var/log/apache2/error.log
[Thu Mar 11 16:54:12 2010] [info] removed PID file /var/run/apache2.pid (pid=4057)
[Thu Mar 11 16:54:12 2010] [notice] caught SIGTERM, shutting down
Dumping the A startup.pl:
$VAR1 = [
'/var/www/a/lib',
'/var/www/a/lib/dep_lib',
'/var/myperl/dep_lib',
'/etc/perl',
'/usr/local/lib/perl/5.10.0',
'/usr/local/share/perl/5.10.0',
'/usr/lib/perl5',
'/usr/share/perl5',
'/usr/lib/perl/5.10',
'/usr/share/perl/5.10',
'/usr/local/lib/site_perl',
'.',
'/etc/apache2'
];
Finished processing A startup!
Dumping the A startup.pl:
$VAR1 = [
'/var/www/a/lib',
'/var/www/a/lib/dep_lib',
'/var/myperl/dep_lib',
'/etc/perl',
'/usr/local/lib/perl/5.10.0',
'/usr/local/share/perl/5.10.0',
'/usr/lib/perl5',
'/usr/share/perl5',
'/usr/lib/perl/5.10',
'/usr/share/perl/5.10',
'/usr/local/lib/site_perl',
'.',
'/etc/apache2'
];
Finished processing A startup!
[Thu Mar 11 16:54:15 2010] [notice] Apache/2.2.11 (Ubuntu) mod_apreq2-20051231/2.6.0 mod_perl/2.0.4 Perl/v5.10.0 configured -- resuming normal operations
[Thu Mar 11 16:54:15 2010] [info] Server built: Mar 9 2010 21:04:15
[Thu Mar 11 16:54:15 2010] [debug] prefork.c(1032): AcceptMutex: sysvsem (default: sysvsem)


Notice that we see the Dumper being called twice. This is, again, because immediately after starting up for the first time Apache starts once, and then restarts to test its graceful restart ability. [1]

Really interesting side note: when you reload (aka "graceful restart," "-k graceful," SIGUSR1) the apache server will start new children with the new configuration settings. On the other hand, restarting (aka "restart now, "-k restart", SIGHUP) stops all children, reloads the parent, and then loads all children with the new configuration. This means that when you send "reload" you get one startup pass from modperl instead of two (since the server is already running it doesn't test it's ability to restart) whereas with restart, the server (for all intents and purposes) stops and starts again meaning that you see the output from modperl twice. [3]

Now that we know what's going on with the modperl startup, let's finish our setup. The remaining adjustments are fairly straightforward:

/etc/apache2/httpd.conf:


ServerName localhost
PerlSwitches -I/var/myperl/dep_lib
#This option allows us to say things like print "Content-Type: text/plain\n\n"
PerlOptions +ParseHeaders
#We still might want to do something server-wide
PerlPostConfigHandler MyApache2::Test->post_config

/etc/apache2/sites-available/vhosts.conf:


#I've actually ignored one of the criteria I set earlier (because I can, and partially to show a point):
#vhost a allows scripts to run under modperl from docs/bin even though the documentroot is under ExecCGI.
#The PerlOptions +Parent gives vhost a it's own perl interpreter pool complete with its own @INC.
<VirtualHost *:5050>
DocumentRoot /var/www/a/docs
Options ExecCGI +Includes FollowSymLinks +MultiViews Indexes
DirectoryIndex index.html

PerlPostConfigRequire /var/www/a/startup.pl
PerlOptions +Parent

<Directory /var/www/a>
AllowOverride All
</Directory>

<Directory /var/www/a/docs/bin>
SetHandler perl-script
PerlResponseHandler ModPerl::Registry
</Directory>
</VirtualHost>

#vhost b is configred identically to vhost a.
<VirtualHost *:8080>
DocumentRoot /var/www/b/docs
Options ExecCGI +Includes FollowSymLinks +MultiViews Indexes
DirectoryIndex index.html

PerlPostConfigRequire /var/www/b/startup.pl
PerlOptions +Parent

<Directory /var/www/b>
AllowOverride All
</Directory>

<Directory /var/www/b/docs/bin>
SetHandler perl-script
PerlResponseHandler ModPerl::Registry
</Directory>
</VirtualHost>

#vhost c is a little different. This vhost allows scripts to be run under modperl from anywhere it its document root.
<VirtualHost *:9090>
DocumentRoot /var/www/c/docs
Options ExecCGI +Includes FollowSymLinks +MultiViews Indexes
DirectoryIndex index.html

PerlPostConfigRequire /var/www/c/startup.pl
PerlOptions +Parent
SetHandler perl-script
PerlResponseHandler ModPerl::Registry

<Directory /var/www/c>
AllowOverride All
</Directory>

</VirtualHost>

There are two things that should be explained:

PerlOptions +Parent
...creates a separate interpreter pool for each of the vhosts. These pools do not inherit any settings from other pools.

SetHandler perl-script
...tells Apache to parse all files in that Directory (also valid for Locations) as perl-scripts. ("SetHandler modperl" also works just as well here). More on this in a week or two.

PerlResponseHandler ModPerl::Registry
...tells modperl that everytime it needs to process something, use ModPerl::Registry to generate the response by compiling/parsing/executing your code when it needs to.

Finally, to prove that this actually works (in addition to having the error log), here's a script that you can actually run under modperl in each of the vhosts. Notice the lack of a #! line and a 1;. They're not necessary [in this particular instance]!

test.pl:


use Data::Dumper;

print "Content-type: text/plain\n\n";
for(sort keys %ENV) {
print "$_ => $ENV{$_}\n";
}

print Dumper(\@INC);

Finally, my directory structure looks like this:

root@larx:/var/www# tree
.
|-- a
| |-- docs
| | |-- bin
| | | `-- test.pl
| | `-- index.html
| |-- lib
| | `-- dep_lib
| `-- startup.pl
|-- b
| |-- docs
| | |-- bin
| | | `-- test.pl
| | `-- lib
| | `-- dep_lib
| |-- lib
| | `-- dep_lib
| `-- startup.pl
`-- c
|-- docs
| |-- bin
| | `-- test.pl
| |-- lib
| | `-- dep_lib
| `-- test.pl
|-- lib
| `-- dep_lib
`-- startup.pl

19 directories, 8 files

Try visiting http://localhost:8080/bin/test.pl and http://localhost:9090/test.pl . Now you have mod_perl working with isolated vhosts!

Join me next week as I delve further into mod_perl. Possible topics include authentication and authorization, ModPerl::Registry and/or ModPerl::PerlRun, custom environment variables, and further per-vhost configuration options.


References:

[1] http://perl.apache.org/docs/2.0/user/handlers/server.html#Server_Life_Cycle

[2] http://www.gossamer-threads.com/lists/modperl/modperl/60796#60796

[3] http://httpd.apache.org/docs/2.2/en/stopping.html#graceful

About this Archive

This page is an archive of entries from March 2010 listed from newest to oldest.

February 2010 is the previous archive.

April 2010 is the next archive.

Find recent content on the main index or look in the archives to find all content.