[Catalyst] FastCGI ENV issue

Sam Vilain sam at vilain.net
Mon Nov 14 22:04:03 CET 2005


On Mon, 2005-11-14 at 14:27 -0500, Andy Grundman wrote:
> I've run into an issue with %ENV under lighttpd + FastCGI.  I believe 
> the same issue occurs using Apache.

It is an endemic problem that the CGI protocol and other libraries have
conflicting ideas about who gets to use this stash of globals called the
environment.  Remember when you were taught that "Globals are bad,
m'kay?"

With mod_perl, you "just" set it with PerlSetEnv in the httpd.conf.  You
didn't expect to be able to treat Apache like a normal app did you?
Silly.  Actually I'll shoot down my own Apache rant there by saying that
otherwise there's no way you could have multiple mod_perl environments
with distinct environment settings.  That's right, it's not a rabid
violation of the principle of least surprise, it's a feature.  In fact,
if you look at the environment with `ps ax', you'll see a quite
different beast to the one visible from Perl.  I'm not sure whether this
magic is LD_PRELOAD-style or via Perl %ENV magic, but I'm guessing the
former as I've always seen those Oracle variables set up in the
httpd.conf.

> The issue is that the initial %ENV that exists when you first start your 
> app, including any variables you set in BEGIN blocks, is clobbered on 
> every request.  Each request gets a new %ENV from the web server.

Correct.

> I had a problem with Oracle connections because ORACLE_HOME was not 
> being set on each request, only on startup.
> I came up with a quick fix, can anyone see any issues with this?
>      # save initial ENV state during startup
>      my %startup_ENV = %ENV;
> 
>      ....
> 
>      while ( $request->Accept >= 0 ) {
> 
>          # merge initial ENV with request ENV
>          %ENV = ( %startup_ENV, %{ $request->GetEnvironment() } );
> 
>          $class->handle_request;
>      }

This is a fine interim solution.

A more correct solution would be to change all instances of references
to the global (remember, globals are bad, m'kay?) %ENV in
Catalyst::Engine::CGI to allow request variables to be passed in via an
explicit argument to prepare_request();

One very nice thing about the FCGI library is that when you create the
request object, you pass into it references to the filehandles that you
want to use for request input, output, error, and for passing request
variables in CGI/1.1 form.

It might look something like this; I haven't tested this at all, please
feel free to hack into commission :)

=== lib/Catalyst/Engine/CGI.pm
==================================================================
--- lib/Catalyst/Engine/CGI.pm  (revision 1627)
+++ lib/Catalyst/Engine/CGI.pm  (local)
@@ -28,6 +28,11 @@
 
 This is the Catalyst engine specialized for the CGI environment.
 
+=cut
+
+# Globals are bad, m'kay?
+__PACKAGE__->mk_accessors(qw/env/);
+
 =head1 OVERLOADED METHODS
 
 This class overloads some methods from C<Catalyst::Engine>.
@@ -53,6 +58,7 @@
 
 sub prepare_connection {
     my ( $self, $c ) = @_;
+    local(*ENV) = $self->env || \%ENV;
 
     $c->request->address( $ENV{REMOTE_ADDR} );
 
@@ -105,6 +111,7 @@
 
 sub prepare_path {
     my ( $self, $c ) = @_;
+    local(*ENV) = $self->env || \%ENV;
 
     my $scheme = $c->request->secure ? 'https' : 'http';
     my $host      = $ENV{HTTP_HOST}   || $ENV{SERVER_NAME};
@@ -155,12 +162,25 @@
 
 sub prepare_query_parameters {
     my ( $self, $c ) = @_;
+    local(*ENV) = $self->env || \%ENV;
     
     if ( $ENV{QUERY_STRING} ) {
         $self->SUPER::prepare_query_parameters( $c,
$ENV{QUERY_STRING} );
     }
 }
 
+=item $self->prepare_request($c, (name => val...));
+
+=cut
+
+sub prepare_request {
+    my ( $self, $c, %args ) = @_;
+
+    if ( $args{env} ) {
+       $self->env($args{env});
+    }
+}
+
 =item $self->prepare_write($c)
 
 Enable autoflush on the output handle for CGI-based engines.
=== lib/Catalyst/Engine/FastCGI.pm
==================================================================
--- lib/Catalyst/Engine/FastCGI.pm      (revision 1627)
+++ lib/Catalyst/Engine/FastCGI.pm      (local)
@@ -61,10 +61,12 @@
 
     $options ||= {};
 
+    my %env;
+
     my $request = FCGI::Request(\*STDIN,
                                \*STDOUT,
                                \*STDERR,
-                               \%ENV,
+                               \%env,
                                $sock,
                                ($options->{nointr}
                                 ? 0 : &FCGI::FAIL_ACCEPT_ON_INTR),
@@ -81,7 +83,7 @@
     }
     while ( $request->Accept >= 0 ) {
        $proc_manager && $proc_manager->pm_pre_dispatch();
-        $class->handle_request;
+        $class->handle_request( env => \%env );
        $proc_manager && $proc_manager->pm_pre_dispatch();
     }
 }


Sam.




More information about the Catalyst mailing list