Mod_perl_cgi - running CGI scripts under mod_perl ($Date: 2000/03/05 11:57:55 $)
This part of the mod_perl FAQ deals with questions surrounding CGI scripts.
What are the symptoms? Here are some possibilities.
Have you made the correct entries in Apache's configuration files? You
need to add the Alias /perl/ ...
and <Location /perl>...
directives to access.conf as described in mod_perl.pod. And of course the
script must be in the directory specified by the Alias directive and it
must be readable and executable by the user that the web server runs as.
You don't have permission to access /perl/foo on this server.
chmod 755 /path/to/my/mod_perl/scripts chmod 755 /path/to/my/mod_perl/scripts/foo
The script died with an execution error. There should be an error message in the server's error.log saying why. Provided you are using CGI.pm, you can also see what happens by running the script at a shell prompt.
If the error.log claims there are syntax errors in your script, but
perl -c /path/to/my/mod_perl/scripts/foo
says it is OK, you have probably used __END__ or __DATA__. Sorry. Mod_perl's Apache::Registry can't deal with that.
You have a script that works fine under mod_cgi but the browser displays ``Content-Type: text/html'' or similar headers at the top of the page when it is run under mod_perl. There are two possible causes.
Something, either your script or mod_perl or CGI.pm (if you are using it) has to trigger Apache to send the response header. This happens when you call the CGI.pm $q->header method or mod_perl's $r->send_http_header. But if your script just prints out one or more header lines followed by a blank line and the page content, you need to set ``PerlSendHeader On'' in the configuration for the location of the script. This tells mod_perl to parse the stuff that the script prints and call $r->send_http_header for you when it sees the blank line.
This parsing only happens if PerlSendHeader is on and the header has not been sent yet. Even so, it is costly and mod_perl makes the assumption that individual headers are not split across print statements, to simplify the parser and avoid having to retain fragments of headers between calls to print(). So the following does not work:
print "Content-type: text/html\n"; print "Set-Cookie: iscookietext\; "; print "expires=Wednesday, 09-Nov-1999 00:00:00 GMT\; "; print "path=\/\; domain=\.mmyserver.com\; \n\n"; print "hello";
because the Set-Cookie header is split across multiple print's.
You need to print each header (or group of headers) in one go, possibly after building it up in a temporary variable.
print "Content-type: text/html\n"; my $cookie = "Set-Cookie: iscookietext; "; $cookie .= "expires=Wednesday, 09-Nov-1999 00:00:00 GMT; "; $cookie .= "path=/; domain=.mmyserver.com; \n\n"; print $cookie; print "hello";
Remember that a conventional CGI script always starts up a fresh perl interpreter, whereas a mod_perl script is reused in the same process context many times. This means that certain categories of variables can survive from one invocation of the script to the next. You can make that work to your advantage, but you can also be caught out by it.
When diagnosing a problem that might be caused by variable lifetimes, always start the web server in single process mode. Apache normally spawns a number of child processes to handle queries, and they get used in round-robin fashion, which makes test results unpredictable.
The command
# ./httpd -X
will start a single-process server with its default configuration.
You can specify a different configuration with the -f
flag (and
thus use a different port number for testing, for instance).
Now try executing your script from a browser. A non-graphical browser is often much better for diagnosing low-level problems. Install lynx (http://lynx.browser.org/) if you haven't already got it and use
lynx -mime_header http://localhost/perl/myscript
to see the response that the web server produces when it GETs your script, and
lynx -head -dump http://localhost/perl/myscript
to see the response to a HEAD request. The GET and HEAD commands that come with libwww-perl are similar but slower.
Here are some of the effects that you might see.
Your script is calling the CORE perl exit()
function. That is not
a problem in a conventional CGI script, provided that query processing
is complete. But you almost certainly don't want to exit in a
mod_perl script. It kills the server process that handled the
request, meaning that the advantage of using mod_perl to avoid startup
overhead is lost.
The best way to avoid calling exit()
is to restructure the script so
that all execution paths return to a common point at the end of the
script. If this seems impractical you can force the same effect by
placing a label after the last executable statement and replacing calls to
exit()
with goto label;
See also what mod_perl_traps says about Apache::exit()
and the way
that Apache::Registry causes it to terminate the script but not the
httpd child.
There may be exceptional circumstances in which you explicitly want to
terminate the httpd child at the end of the current request. In this
case Apache->exit(-2)
should be used.
The so-called sticky query effect happens when the CGI query object, or another request-specific variable, has a lifetime longer than a single execution of your script and does not get reinitialised each time the script is invoked.
This does not matter in a conventional CGI script, because the script starts with a clean slate for each new request. But a mod_perl script gets compiled into a subroutine by the Apache::Registry handler and then processes an arbitrary number of requests. To make sure that both you and the perl interpreter have the same idea about the meaning of your script, make sure it starts like this:
#!/usr/bin/perl -w use strict;
It is good for you! It will make perl point out all variables that you have not explicitly declared. You can then think about whether they need to be global or if they can be lexical. Try to declare things lexically, with my(). These variables disappear when the block they are declared in ends, so they don't occupy memory when they are not in use and they also do not need a run-time symbol table entry.
Beware, though, of referring to a lexical variable indirectly from within a subroutine. To quote perlsub/``Private Variables via my()'', the variable ``... now becomes unreachable by the outside world, but retains its value between calls to ...'' the subroutine. You will see classic ``sticky query'' symptoms if your code looks like this:
#!/usr/bin/perl -w use strict; use CGI; my $q = CGI->new(); doit();
sub doit { print($q->header(), $q->start_html()); print('Value is ', $q->param('val')) if $q->param; $q->print('<p>', $q->startform, 'Value? ', $q->textfield(-name=>'val', -size=>20), ' ', $q->submit('enter'), $q->endform); print($q->end_html()); }
Because you remembered to put the -w
switch on the first line, the
error log will tell you that ``Variable $q will not stay shared''
(provided you are using perl5.004 or higher).
You must either pass the variable to the subroutine as a parameter,
doit($q)
sub doit { my($q) = @_; ....
or declare this variable to be global,
use vars qw($q); $q = CGI->new();
The reason why Perl works this way is explained in a news posting by Mike Guy that is included with this FAQ (mjtg-news.txt).
mjtg-news.txt
CGI.pm must pull some extra tricks when it is being used via Apache::Registry. Versions of CGI.pm before 2.35 did not know this, and Apache::Registry will complain if you try to use an earlier version.
CGI.pm detects that it is running under Apache::Registry by looking
for an environment variable. This test can fail if use CGI
is
evaluated too early, before the environment has been set up. That can
happen if you have use CGI
in a script and pull the script in with
a PerlRequire
directive in httpd.conf. Replacing use CGI
with
require CGI
will fix it.
If you have CGI code that seems to be fundamentally at odds with
mod_perl's ``compile once, run many'' environment, you may find that
it will work if run under the module Apache::PerlRun
. See the
documentation of that module, which is included with recent versions
of mod_perl.
If the client submits a form that will take some time to process, you may want to say ``Thanks for submitting the form'' and close the connection, before processing it.
You can achieve this by registering the subroutine that processes the form as a cleanup handler:
if($ENV{GATEWAY_INTERFACE} =~ /^CGI-Perl/) { Apache->request->register_cleanup(sub { doProcess($query) }); }