| home / programming / perl / mod_perl / chap6 / 3 | [previous] [next] |
|
|
Example 6-22: Book/Config1.pm
package Book::Config1;use strict;BEGIN {use Exporter ( );@Book::Config1::ISA = qw(Exporter);@Book::Config1::EXPORT = qw( );@Book::Config1::EXPORT_OK = qw(%c);}use vars qw(%c);%c = (dir => {cgi => '/home/httpd/perl',docs => '/home/httpd/docs',img => '/home/httpd/docs/images',},url => {cgi => '/perl',docs => '/',img => '/images',},color => {hint => '#777777',warn => '#990066',normal => '#000000',},);
Good Perl style suggests keeping a comma at the end of each list. This makes it easy to add new items at the end of a list.
Our script now looks like this:
use strict;use Book::Config1 qw(%c);use vars qw(%c);print "Content-type: text/plain\n\n";print "My url docs root: $c{url}{docs}\n";
The whole mess is gone. Now there is only one variable to worry about.
The one small downside to this approach is auto-vivification.
For example, if we write $c{url}{doc} by mistake,
Perl will silently create this element for us with the value undef. When we
use strict;, Perl will tell us about any misspelling
of this kind for a simple scalar, but this check is not performed for hash elements.
This puts the onus of responsibility back on us, since we must take greater
care.
The benefits of the hash approach are significant. Let's make
it even better by getting rid of the Exporter stuff
completely, removing all the exporting code from the configuration file. See
Example 6-23.
Example 6-23: Book/Config2.pm
package Book::Config2;use strict;use vars qw(%c);%c = (dir => {cgi => '/home/httpd/perl',docs => '/home/httpd/docs',img => '/home/httpd/docs/images',},url => {cgi => '/perl',docs => '/',img => '/images',},color => {hint => '#777777',warn => '#990066',normal => '#000000',},);
Our script is modified to use fully qualified names for the configuration variables it uses:
use strict;use Book::Config2 ( );print "Content-type: text/plain\n\n";print "My url docs root: $Book::Config2::c{url}{docs}\n";
To save typing and spare the need to use fully qualified variable names, we'll use a magical Perl feature to alias the configuration variable to a script's variable:
use strict;use Book::Config2 ( );use vars qw(%c);*c = \%Book::Config2::c;print "Content-type: text/plain\n\n";print "My url docs root: $c{url}{docs}\n";
We've aliased the *c glob with a
reference to the configuration hash. From now on, %Book::Config2::c
and %c refer to the same hash for all practical
purposes.
One last point: often, redundancy is introduced in configuration variables. Consider:
$cgi_dir = '/home/httpd/perl';$docs_dir = '/home/httpd/docs';$img_dir = '/home/httpd/docs/images';
It's obvious that the base path /home/httpd should be moved to a separate variable, so only that variable needs to be changed if the application is moved to another location on the filesystem.
$base = '/home/httpd';$cgi_dir = "$base/perl";$docs_dir = "$base/docs";$img_dir = "$docs_dir/images";
This cannot be done with a hash, since we cannot refer to its values before the definition is completed. That is, this will not work:
%c = (base => '/home/httpd',dir => {cgi => "$c{base}/perl",docs => "$c{base}/docs",img => "$c{base}{docs}/images",},);
But nothing stops us from adding additional variables that are
lexically scoped with my( ). The following code
is correct:
my $base = '/home/httpd';%c = (dir => {cgi => "$base/perl",docs => "$base/docs",img => "$base/docs/images",},);
We've learned how to write configuration files that are easy to maintain, and how to save memory by avoiding importing variables in each script's namespace. Now let's look at reloading those files.
Reloading configuration files
First, lets look at a simple case, in which we just have to look after a simple configuration file like the one below. Imagine a script that tells you who is the patch pumpkin of the current Perl release.[2] (Pumpkin is a whimsical term for the person with exclusive access to a virtual "token" representing a certain authority, such as applying patches to a master copy of some source.)
use CGI ( );use strict;my $firstname = "Jarkko";my $surname = "Hietaniemi";my $q = CGI->new;print $q->header(-type=>'text/html');print $q->p("$firstname $surname holds the patch pumpkin" ."for this Perl release.");
The script is very simple: it initializes the CGI object, prints the proper HTTP header, and tells the world who the current patch pumpkin is. The name of the patch pumpkin is a hardcoded value.
We don't want to modify the script every time the patch pumpkin
changes, so we put the $firstname and $surname
variables into a configuration file:
$firstname = "Jarkko";$surname = "Hietaniemi";1;
Note that there is no package declaration in the above file, so
the code will be evaluated in the caller's package or in the main::
package if none was declared. This means that the variables $firstname
and $surname will override (or initialize) the
variables with the same names in the caller's namespace. This works for global
variables only--you cannot update variables defined lexically (with my(
)) using this technique.
Let's say we have started the server and everything is working
properly. After a while, we decide to modify the configuration. How do we let
our running server know that the configuration was modified without restarting
it? Remember, we are in production, and a server restart can be quite expensive.
One of the simplest solutions is to poll the file's modification time by calling
stat( ) before the script starts to do real work.
If we see that the file was updated, we can force a reconfiguration of the variables
located in this file. We will call the function that reloads the configuration
reread_conf( ) and have it accept the relative
path to the configuration file as its single argument.
Apache::Registry executes a chdir(
) to the script's directory before it starts the script's execution.
So if your CGI script is invoked under the Apache::Registry
handler, you can put the configuration file in the same directory as the script.
Alternatively, you can put the file in a directory below that and use a path
relative to the script directory. However, you have to make sure that the file
will be found, somehow. Be aware that do( ) searches
the libraries in the directories in @INC.
use vars qw(%MODIFIED);sub reread_conf {my $file = shift;return unless defined $file;return unless -e $file and -r _;my $mod = -M _;unless (exists $MODIFIED{$file} and $MODIFIED{$file} = = $mod) {unless (my $result = do $file) {warn "couldn't parse $file: $@" if $@;warn "couldn't read $file: $!" unless defined $result;warn "couldn't run $file" unless $result;}$MODIFIED{$file} = $mod; # Update the MODIFICATION times}}
Notice that we use the = = comparison
operator when checking the file's modification timestamp, because all we want
to know is whether the file was changed or not.
When the require( ), use(
), and do( ) operators successfully return,
the file that was passed as an argument is inserted into %INC.
The hash element key is the name of the file, and the element's value is the
file's path. When Perl sees require( ) or use(
) in the code, it first tests %INC to see
whether the file is already there and thus loaded. If the test returns true,
Perl saves the overhead of code rereading and recompiling; however, calling
do( ) will load or reload the file regardless of
whether it has been previously loaded.
We use do( ), not require(
), to reload the code in this file because although do(
) behaves almost identically to require( ),
it reloads the file unconditionally. If do( ) cannot
read the file, it returns undef and sets $!
to report the error. If do( ) can read the file
but cannot compile it, it returns undef and sets
an error message in $@. If the file is successfully
compiled, do( ) returns the value of the last expression
evaluated.
The configuration file can be broken if someone has incorrectly
modified it. Since we don't want the whole service using that file to be broken
that easily, we trap the possible failure to do( )
the file and ignore the changes by resetting the modification time. If do(
) fails to load the file, it might be a good idea to send an email about
the problem to the system administrator.
However, since do( ) updates %INC
like require( ) does, if you are using Apache::StatINC
it will attempt to reload this file before the reread_conf(
) call. If the file doesn't compile, the request will be aborted. Apache::StatINC
shouldn't be used in production anyway (because it slows things down by stat(
)ing all the files listed in %INC), so this
shouldn't be a problem.
Note that we assume that the entire purpose of this function is
to reload the configuration if it was changed. This is fail-safe, because if
something goes wrong we just return without modifying the server configuration.
The script should not be used to initialize the variables on its first invocation.
To do that, you would need to replace each occurrence of return(
) and warn( ) with die(
).
We've used the above approach with a huge configuration file that was loaded only at server startup and another little configuration file that included only a few variables that could be updated by hand or through the web interface. Those variables were initialized in the main configuration file. If the webmaster breaks the syntax of this dynamic file while updating it by hand, it won't affect the main (write-protected) configuration file and won't stop the proper execution of the programs. In the next section, we will see a simple web interface that allows us to modify the configuration file without the risk of breaking it.
Example 6-24 shows a sample script using our reread_conf(
) subroutine.
| home / programming / perl / mod_perl / chap6 / 3 | [previous] [next] |
Created: March 27 2003
Revised: July 23, 2003
URL: http://webreference.com/programming/perl/mod_perl/chap6/3