My() Troubles
May 15, 2000
First, some ground rules. When developing and testing your Perl scripts under
mod_perl, there are some conditions you should set forth which will greatly
help you to create properly functional code. To wit:
- Always "use strict" in your Perl code. This rule will
force you to obey certain coding practices which are necessary under
the mod_perl environment, such as how you scope variables. We'll
see more on this momentarily.
- Enable the warning switch in your Perl script header, such as
#!/usr/bin/perl -w. These warnings will be output to your
Apache server's errorlog and can help you debug and track down
mysterious problems. Do remember however to remove the warning
switch once your code goes live, especially if your script does
produce harmless warnings, or else your errorlog may grow faster
than Louie Anderson at a state fair.
- Run your Apache server in "single process mode". Do this
with the commandline httpd -X, or wherever the appropriate
path to your Apache httpd is. In this mode, Apache will not
spawn any children. One of the most common problems of mod_perl
development is that sometimes Apache children will
"remember" values from a previous invocation of a script,
caused by the optimized nature of mod_perl and improperly coded
scripts. Often this problem is masked when you test as a single
user because new Apache children are spawned, thus failing to
guarantee that your repeated tests are handled by a single child
process. Confirming your scripts in a single Apache process will
provide peace of mind that there are no hidden problems being
obscured by the presence of multiple Apache children.
All that said, let's look at a sample mod_perl script which
suffers from a mysterious ailment.
#!/usr/bin/perl -w
use strict;
use CGI;
my $cgiobj=new CGI;
print $cgiobj->header;
my $name=$cgiobj->param("firstname").' '.
$cgiobj->param("lastname");
print &formatName($name);
sub formatName {
return uc($name);
}
|
We run this script through the browser, by passing a URL to it
with some parameters; e.g.
http://my.host/cgi-bin/welcome.cgi?firstname=martin&lastname=mungbean
And so the web page displays:
MARTIN MUNGBEAN
Notice that the one simple function of this script is to output the
supplied name in all uppercase, via the uc() function.
Keeping in mind that Apache is running in single process mode (-X),
we run the script again through the web browser, this time with the
parameters firstname=jane&lastname=frowny. And so the
web page displays:
MARTIN MUNGBEAN
No, that's not a typo on our part. The script seemed to ignore Jane
Frowny. Yet, if we went and tried to execute this script from the
command line rather than through the web browser (i.e. mod_perl)
and supplied the appropriate parameters, it would output the
correct name each time. So what's wrong with mod_perl? Nothing
-- the question is, what is wrong with this script?
Ideally, we were hoping to create $name as a global variable
that any subroutine could access. Often times this is not
recommended, but there are cases where it is realistic. But we
can't just create a true global variable because this is disallowed
by the strict rule, which we must use with mod_perl. So, we
scope our variables using my(). You can see that we
declared $name using my() in the outer code of this
example, and then we attempt to reference $name from inside
a subroutine. This type of reference will cause problems in Perl
when we use nested subroutines -- that is, a subroutine within a
subroutine. In fact, because we have the warning switch enabled for
Perl, you can see in the Apache errorlog a warning about just this
problem:
Variable "$name" will not stay shared at
/home/username/cgi-bin/test.cgi line 10.
|
From the looks of it, our example code does not use a nested
subroutine -- so where's the problem? As we alluded to earlier,
Perl scripts under mod_perl do not run inside the main package;
consequently, code that appears to be outside of any subroutines
in our script is actually nested, because our whole script is
nested, in a manner of speaking, inside a mod_perl subroutine
named handler. This causes us to suffer from the nested
subroutine problem with my() variables -- and, as we've
seen, the Apache child process "remembers" the parameter
values supplied on first invocation, and uses those precompiled
values for each subsequent invocation. This is not good.
Solutions are good, and there are several. One solution, of course,
is not to treat $name as a global variable, but rather to
pass it in and out of any subroutines:
print &formatName($name);
sub formatName {
my ($name)=@_;
return uc($name);
}
This solution is advised where possible, but sometimes it is just
not feasible to pass a variable around to every subroutine that
uses it, especially when one subroutine does not need it but it
needs to call another subroutine which does (it happens!). There's
a better way.
The Perl You Need to Know Special: Introduction to mod_perl Part 2
The Perl You Need to Know
Repackage Your Way to Success
|