Practical mod_perlPractical mod_perlSearch this book

4.3. The Startup File

At server startup, before child processes are spawned, you can do much more than just preload modules. You might want to register code that will initialize a database connection for each child when it is forked, tie read-only DBM files, fill in shared caches, etc.

The startup.pl file is an ideal place to put code that should be executed when the server starts. Once you have prepared the code, load it in httpd.conf before other mod_perl configuration directives with the PerlRequire directive:

PerlRequire  /home/httpd/perl/lib/startup.pl

Be careful with the startup file. Everything run at server initialization is run with root privileges if you start the server as root (which you have to do unless you choose to run the server on an unprivileged port, numbered 1024 or higher). This means that anyone who has write access to a script or module that is loaded by PerlModule, PerlRequire, or <Perl>sections effectively has root access to the system.

4.3.1. A Sample Startup File

Let's look at a real-world startup file. The elements of the file are shown here, followed by their descriptions.

use strict;

This pragma is worth using in every script longer than half a dozen lines. It will save a lot of time and debugging later.

use lib qw(/home/httpd/lib /home/httpd/extra-lib);

This permanently adds extra directories to @INC, something that's possible only during server startup. At the end of each request's processing, mod_perl resets @INC to the value it had after the server startup. Alternatively, you can use the PERL5LIB environment variable to add extra directories to @INC.

$ENV{MOD_PERL} or die "not running under mod_perl!";

This is a sanity check. If mod_perl wasn't properly built, the server startup is aborted.

use Apache::Registry ( );
use LWP::UserAgent ( );
use Apache::DBI ( );
use DBI ( );

Preload the modules that get used by Perl code serving requests. Unless you need the symbols (variables and subroutines) exported by preloaded modules to accomplish something within the startup file, don't import them—it's just a waste of startup time and memory. Instead, use the empty import list ( ) to tell the import( ) function not to import anything.

use Carp ( );
$SIG{_ _WARN_ _} = \&Carp::cluck;

This is a useful snippet to enable extended warnings logged in the error_log file. In addition to basic warnings, a trace of calls is added. This makes tracking potential problems a much easier task, since you know who called what.

The only drawback of this method is that it globally overrides the default warning handler behavior—thus, in some places it might be desirable to change the settings locally (for example, with local $^W=0, or no warnings under Perl 5.6.0 and higher). Usually warnings are turned off on production machines to prevent unnecessary clogging of the error_log file if your code is not very clean. Hence, this method is mostly useful in a development environment.

use CGI ( );
CGI->compile(':all');

Some modules, such as CGI.pm, create their subroutines at runtime via AUTOLOAD to improve their loading time. This helps when the module includes many subroutines but only a few are actually used. (Also refer to the AutoSplit manpage.) Since the module is loaded only once with mod_perl, it might be a good idea to precompile all or some of its methods at server startup. This avoids the overhead of compilation at runtime. It also helps share more compiled code between child processes.

CGI.pm's compile( ) method performs this task. Note that compile( ) is specific to CGI.pm; other modules that implement this feature may use another name for the compilation method.

As with all modules we preload in the startup file, we don't import symbols from them because they will be lost when they go out of the file's scope.

The following code snippet makes sure that when the child process is spawned, a connection to the database is opened automatically, avoiding this performance hit on the first request:

Apache::DBI->connect_on_init
  ("DBI:mysql:database=test;host=localhost",
   "user", "password", {
                        PrintError => 1, # warn( ) on errors
                        RaiseError => 0, # don't die on error
                        AutoCommit => 1, # commit executes immediately
                       }
  );

We discuss this method in detail in Chapter 20.

The file ends with 1;so it can be successfully loaded by Perl.

The entire startup.pl file is shown in Example 4-3.

Example 4-3. startup.pl

use strict;

use lib qw(/home/httpd/lib /home/httpd/extra-lib);
$ENV{MOD_PERL} or die "not running under mod_perl!";

use Apache::Registry ( );
use LWP::UserAgent ( );
use Apache::DBI ( );
use DBI ( );

use Carp ( );
$SIG{_ _WARN_ _} = \&Carp::cluck;

use CGI ( );
CGI->compile(':all');

Apache::DBI->connect_on_init
  ("DBI:mysql:database=test;host=localhost",
   "user", "password", {
                        PrintError => 1, # warn( ) on errors
                        RaiseError => 0, # don't die on error
                        AutoCommit => 1, # commit executes immediately
                       }
  );
1;

4.3.2. Syntax Validation

If the startup file doesn't include any modules that require the mod_perl runtime environment during their loading, you can validate its syntax with:

panic% perl -cw /home/httpd/perl/lib/startup.pl

The -c switch tells Perl to validate only the file's syntax, and the -w switch enables warnings.

Apache::DBI is an example of a module that cannot be loaded outside of the mod_perl environment. If you try to load it, you will get the following error message:

panic% perl -MApache::DBI -c -e 1
Can't locate object method "module" via package "Apache" 
(perhaps you forgot to load "Apache"?) at 
/usr/lib/perl5/site_perl/5.6.1/Apache/DBI.pm line 202.
Compilation failed in require.
BEGIN failed--compilation aborted.

However, Apache::DBI will work perfectly once loaded from within mod_perl.

4.3.3. What Modules Should Be Added to the Startup File

Every module loaded at server startup will be shared among the server children, saving a lot of RAM on your machine. Usually, we put most of the code we develop into modules and preload them.

You can even preload CGI scripts with Apache::RegistryLoader, as explained in Chapter 10.

4.3.4. The Confusion with use( ) in the Server Startup File

Some people wonder why they need to duplicate use Modulename in the startup file and in the script itself. The confusion arises due to misunderstanding use( ). Let's take the POSIX module as an example. When you write:

use POSIX qw(setsid);

use( ) internally performs two operations:

BEGIN {
    require POSIX;
    POSIX->import(qw(setsid));
}

The first operation loads and compiles the module. The second calls the module's import( ) method and specifies to import the symbol setsid into the caller's namespace. The BEGIN block makes sure that the code is executed as soon as possible, before the rest of the code is even parsed. POSIX, like many other modules, specifies a default export list. This is an especially extensive list, so when you call:

use POSIX;

about 500 KB worth of symbols gets imported.

Usually, we don't need POSIX or its symbols in the startup file; all we want is to preload it. Therefore, we use an empty list as an argument for use( ):

use POSIX ( );

so the POSIX::import( ) method won't be even called.

When we want to use the POSIX module in the code, we use( ) it again, but this time no loading overhead occurs because the module has been loaded already. If we want to import something from the module, we supply the list of symbols to load:

use POSIX qw(:flock_h);

This example loads constants used with the flock( ) function.

Technically, you aren't required to supply the use( )statement in your handler code if the module has already been loaded during server startup or elsewhere. When writing your code, however, don't assume that the module code has been preloaded. Someday in the future, you or someone else will revisit this code and will not understand how it is possible to use a module's methods without first loading the module itself.

Please refer to the Exporter and perlmod manpages, and to the section on use( ) in the perlfunc manpage for more information about import( ).

Remember that you can always use require( ) to preload the files at server startup if you don't add ( ), because:

require Data::Dumper;

is the same as:

use Data::Dumper ( );

except that it's not executed at compile-time.



Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.