When just one developer is working on a specific server, there are fewer problems, because she can have complete control over the server. However, often a group of developers need to develop mod_perl scripts and modules concurrently on the same machine. Each developer wants to have control over the server: to be able to stop it, run it in single-server mode, restart it, etc. They also want control over the location of log files, configuration settings such as MaxClients, and so on.

Each developer might have her own desktop machine, but all development and staging might be done on a single central development machine (e.g., if the developers' personal desktop machines run a different operating system from the one running on the development and production machines).

One workaround for this problem involves having a few versions of the httpd.conf file (each having different Port, ErrorLog, etc. directives) and forcing each developer's server to be started with:

panic% httpd_perl -f /path/to/httpd.conf

However, this means that these files must be kept synchronized when there are global changes affecting all developers. This can be quite difficult to manage. The solution we use is to have a single httpd.conf file and use the -Dparameterserver startup option to enable a specific section of httpd.conf for each developer. Each developer starts the server with his or her username as an argument. As a result, a server uses both the global settings and the developer's private settings.

For example, user stas would start his server with:

panic% httpd_perl -Dstas

In httpd.conf, we write:

# Personal development server for stas
# stas uses the server running on port 8000
<IfDefine stas>
    Port 8000
    PidFile /home/httpd/httpd_perl/logs/httpd.pid.stas
    ErrorLog /home/httpd/httpd_perl/logs/error_log.stas
    Timeout 300
    KeepAlive On
    MinSpareServers 2
    MaxSpareServers 2
    StartServers 1
    MaxClients 3
    MaxRequestsPerChild 15
    # let developers to add their own configuration
    # so they can override the defaults
    Include /home/httpd/httpd_perl/conf/stas.conf

# Personal development server for eric
# eric uses the server running on port 8001
<IfDefine eric>
    Port 8001
    PidFile /home/httpd/httpd_perl/logs/httpd.pid.eric
    ErrorLog /home/httpd/httpd_perl/logs/error_log.eric
    Timeout 300
    KeepAlive Off
    MinSpareServers 1
    MaxSpareServers 2
    StartServers 1
    MaxClients 5
    MaxRequestsPerChild 0
    Include /home/httpd/httpd_perl/conf/eric.conf

With this technique, we have separate error_log files and full control over server starting and stopping, the number of child processes, and port selection for each server. This saves Eric from having to call Stas several times a day just to warn, "Stas, I'm restarting the server" (a ritual in development shops where all developers are using the same mod_perl server).

With this technique, developers will need to learn the PIDs of their parent httpd_perl processes. For user stas, this can be found in /home/httpd/httpd_perl/logs/httpd.pid.stas. To make things even easier, we change the apachectl script to do the work for us. We make a copy for each developer, called apachectl.username, and change two lines in each script:

HTTPD='/home/httpd/httpd_perl/bin/httpd_perl -Dusername'

For user stas, we prepare a startup script called apachectl.stas and change these two lines in the standard apachectlscript:

HTTPD='/home/httpd/httpd_perl/bin/httpd_perl -Dstas'

Now when user stas wants to stop the server, he executes:

panic% apachectl.stas stop

And to start the server, he executes:

panic% apachectl.stas start

And so on, for all other apachectl arguments.

It might seem that we could have used just one apachectl and have it determine for itself who executed it by checking the UID. But the setuid bit must be enabled on this script, because starting the server requires root privileges. With the setuid bit set, a single apachectlscript can be used for all developers, but it will have to be modified to include code to read the UID of the user executing it and to use this value when setting developer-specific paths and variables.

The last thing you need to do is to provide developers with an option to run in single-process mode. For example:

panic% /home/httpd/httpd_perl/bin/httpd_perl -Dstas -X

In addition to making the development process easier, we decided to use relative links in all static documents, including calls to dynamically generated documents. Since each developer's server is running on a different port, we have to make it possible for these relative links to reach the correct port number.

When typing the URI by hand, it's easy. For example, when user stas, whose server is running on port 8000, wants to access the relative URI /test/example, he types http://www.example.com:8000/test/example to get the generated HTML page. Now if this document includes a link to the relative URI /test/example2 and stas clicks on it, the browser will automatically generate a full request (http://www.example.com:8000/test/example2) by reusing the server:port combination from the previous request.

Note that all static objects will be served from the same server as well. This may be an acceptable situation for the development environment, but if it is not, a slightly more complicated solution involving the mod_rewrite Apache module will have to be devised.

To use mod_rewrite, we have to configure our httpd_docs (light) server with —enable-module=rewrite and recompile, or use DSOs and load and enable the module in httpd.conf. In the httpd.conf file of our httpd_docsserver, we have the following code:

RewriteEngine on

# stas's server
# port = 8000
RewriteCond  %{REQUEST_URI} ^/perl
RewriteCond  %{REMOTE_ADDR}
RewriteRule ^(.*)           http://example.com:8000/$1 [P,L]

# eric's server
# port = 8001
RewriteCond  %{REQUEST_URI} ^/perl
RewriteCond  %{REMOTE_ADDR}
RewriteRule ^(.*)           http://example.com:8001/$1 [P,L]

# all the rest
RewriteCond  %{REQUEST_URI} ^/perl
RewriteRule ^(.*)           http://example.com:81/$1 [P]

The IP addresses are those of the developer desktop machines (i.e., where they run their web browsers). If an HTML file includes a relative URI such as /perl/test.pl or even http://www.example.com/perl/test.pl, requests for those URIs from user stas's machine will be internally proxied to http://www.example.com:8000/perl/test.pl, and requests generated from user eric's machine will be proxied to http://www.example.com:8001/perl/test.pl.

Another possibility is to use the REMOTE_USER variable. This requires that all developers be authenticated when they access the server. To do so, change the RewriteRules to match REMOTE_USER in the above example.

Remember, the above setup will work only with relative URIs in the HTML code. If the HTML output by the code uses full URIs including a port other than 80, the requests originating from this HTML code will bypass the light server listening on the default port 80 and go directly to the server and port of the full URI.