The nginx bullshit can just die
[catagits/Catalyst-Runtime.git] / lib / Catalyst.pm
index 5a34cc2..6155e9c 100644 (file)
@@ -16,7 +16,6 @@ use Catalyst::Utils;
 use Catalyst::Controller;
 use Data::OptList;
 use Devel::InnerPackage ();
-use File::stat;
 use Module::Pluggable::Object ();
 use Text::SimpleTable ();
 use Path::Class::Dir ();
@@ -30,9 +29,14 @@ use Class::C3::Adopt::NEXT;
 use List::MoreUtils qw/uniq/;
 use attributes;
 use String::RewritePrefix;
+use Catalyst::EngineLoader;
 use utf8;
 use Carp qw/croak carp shortmess/;
 use Try::Tiny;
+use Plack::Middleware::Conditional;
+use Plack::Middleware::ReverseProxy;
+use Plack::Middleware::IIS6ScriptNameFix;
+use Plack::Middleware::LighttpdScriptNameFix;
 
 BEGIN { require 5.008004; }
 
@@ -70,18 +74,17 @@ our $GO        = Catalyst::Exception::Go->new;
 #maybe we should just make them attributes with a default?
 __PACKAGE__->mk_classdata($_)
   for qw/components arguments dispatcher engine log dispatcher_class
-  engine_class context_class request_class response_class stats_class
-  setup_finished _psgi_app/;
+  engine_loader context_class request_class response_class stats_class
+  setup_finished _psgi_app loading_psgi_file/;
 
 __PACKAGE__->dispatcher_class('Catalyst::Dispatcher');
-__PACKAGE__->engine_class('Catalyst::Engine');
 __PACKAGE__->request_class('Catalyst::Request');
 __PACKAGE__->response_class('Catalyst::Response');
 __PACKAGE__->stats_class('Catalyst::Stats');
 
 # Remember to update this in Catalyst::Runtime as well!
 
-our $VERSION = '5.80030';
+our $VERSION = '5.89003';
 
 sub import {
     my ( $class, @arguments ) = @_;
@@ -863,6 +866,9 @@ sub component {
             return $c->_filter_component( $comp, @args ) if $comp;
         }
 
+        return
+            if $c->config->{disable_component_resolution_regex_fallback};
+
         # This is here so $c->comp( '::M::' ) works
         my $query = ref $name ? $name : qr{$name}i;
 
@@ -1117,7 +1123,7 @@ sub setup {
     $class->setup_plugins( delete $flags->{plugins} );
     $class->setup_dispatcher( delete $flags->{dispatcher} );
     if (my $engine = delete $flags->{engine}) {
-        $class->log->warn("Specifying the engine in ->setup is no longer supported, XXX FIXME");
+        $class->log->warn("Specifying the engine in ->setup is no longer supported, see Catalyst::Upgrading");
     }
     $class->setup_engine();
     $class->setup_stats( delete $flags->{stats} );
@@ -1246,7 +1252,7 @@ EOF
 A hook to attach modifiers to. This method does not do anything except set the
 C<setup_finished> accessor.
 
-Applying method modifiers to the C<setup> method doesn't work, because of quirky thingsdone for plugin setup.
+Applying method modifiers to the C<setup> method doesn't work, because of quirky things done for plugin setup.
 
 Example:
 
@@ -1665,7 +1671,9 @@ sub execute {
     push( @{ $c->stack }, $code );
 
     no warnings 'recursion';
-    eval { $c->state( $code->execute( $class, $c, @{ $c->req->args } ) || 0 ) };
+    # N.B. This used to be combined, but I have seen $c get clobbered if so, and
+    #      I have no idea how, ergo $ret (which appears to fix the issue)
+    eval { my $ret = $code->execute( $class, $c, @{ $c->req->args } ) || 0; $c->state( $ret ) };
 
     $c->_stats_finish_execute( $stats_info ) if $c->use_stats and $stats_info;
 
@@ -1687,8 +1695,8 @@ sub execute {
                 $error = qq/Caught exception in $class->$name "$error"/;
             }
             $c->error($error);
-            $c->state(0);
         }
+        $c->state(0);
     }
     return $c->state;
 }
@@ -1867,9 +1875,9 @@ sub finalize_headers {
         # get the length from a filehandle
         if ( blessed( $response->body ) && $response->body->can('read') || ref( $response->body ) eq 'GLOB' )
         {
-            my $stat = stat $response->body;
-            if ( $stat && $stat->size > 0 ) {
-                $response->content_length( $stat->size );
+            my $size = -s $response->body;
+            if ( $size ) {
+                $response->content_length( $size );
             }
             else {
                 $c->log->warn('Serving filehandle without a content-length');
@@ -2405,7 +2413,12 @@ Starts the engine.
 
 =cut
 
-sub run { my $c = shift; return $c->engine->run( $c, $c->psgi_app, @_ ) }
+sub run {
+  my $app = shift;
+  $app->engine_loader->needs_psgi_engine_compat_hack ?
+    $app->engine->run($app, @_) :
+      $app->engine->run( $app, $app->_finalized_psgi_app, @_ );
+}
 
 =head2 $c->set_action( $action, $code, $namespace, $attrs )
 
@@ -2589,21 +2602,46 @@ Sets up engine.
 
 =cut
 
+sub engine_class {
+    my ($class, $requested_engine) = @_;
+
+    if (!$class->engine_loader || $requested_engine) {
+        $class->engine_loader(
+            Catalyst::EngineLoader->new({
+                application_name => $class,
+                (defined $requested_engine
+                     ? (requested_engine => $requested_engine) : ()),
+            }),
+        );
+    }
+    $class->engine_loader->catalyst_engine_class;
+}
+
 sub setup_engine {
-    my ($class) = @_;
+    my ($class, $requested_engine) = @_;
+
+    my $engine = $class->engine_class($requested_engine);
+
+    # Don't really setup_engine -- see _setup_psgi_app for explanation.
+    return if $class->loading_psgi_file;
 
-    my $engine = $class->engine_class;
     Class::MOP::load_class($engine);
 
     if ($ENV{MOD_PERL}) {
-        require 'Catalyst/Engine/Loader.pm';
-        my $apache = Catalyst::Engine::Loader->auto;
-        # FIXME - Immutable
-        $class->meta->add_method(handler => sub {
+        my $apache = $class->engine_loader->auto;
+
+        my $meta = find_meta($class);
+        my $was_immutable = $meta->is_immutable;
+        my %immutable_options = $meta->immutable_options;
+        $meta->make_mutable if $was_immutable;
+
+        $meta->add_method(handler => sub {
             my $r = shift;
-            my $app = $class->psgi_app;
-            $apache->call_app($r, $app);
+            my $psgi_app = $class->psgi_app;
+            $apache->call_app($r, $psgi_app);
         });
+
+        $meta->make_immutable(%immutable_options) if $was_immutable;
     }
 
     $class->engine( $engine->new );
@@ -2611,57 +2649,74 @@ sub setup_engine {
     return;
 }
 
-=head2 $c->psgi_app
-
-Builds a PSGI application coderef for the catalyst application C<$c> using
-L</"$c->setup_psgi_app">, stores it internally, and returns it. On the next call
-to this method, C<setup_psgi_app> won't be invoked again, but its persisted
-return value of it will be returned.
-
-This is the top-level entrypoint for things that need a full blown Catalyst PSGI
-app. If you only need the raw PSGI application, without any middlewares, use
-L</"$c->raw_psgi_app"> instead.
-
-=cut
-
-sub psgi_app {
+sub _finalized_psgi_app {
     my ($app) = @_;
 
     unless ($app->_psgi_app) {
-        my $psgi_app = $app->setup_psgi_app;
+        my $psgi_app = $app->_setup_psgi_app;
         $app->_psgi_app($psgi_app);
     }
 
     return $app->_psgi_app;
 }
 
-=head2 $c->setup_psgi_app
+sub _setup_psgi_app {
+    my ($app) = @_;
 
-Builds a PSGI application coderef for the catalyst application C<$c>.
+    for my $home (Path::Class::Dir->new($app->config->{home})) {
+        my $psgi_file = $home->file(
+            Catalyst::Utils::appprefix($app) . '.psgi',
+        );
 
-If we're able to locate a C<${myapp}.psgi> file in the applications home
-directory, we'll use that to obtain our code reference.
+        next unless -e $psgi_file;
 
-Otherwise the raw psgi app, without any middlewares is created using
-C<raw_psgi_app> and wrapped into L<Plack::Middleware::ReverseProxy>
-conditionally. See L</"PROXY SUPPORT">.
+        # If $psgi_file calls ->setup_engine, it's doing so to load
+        # Catalyst::Engine::PSGI. But if it does that, we're only going to
+        # throw away the loaded PSGI-app and load the 5.9 Catalyst::Engine
+        # anyway. So set a flag (ick) that tells setup_engine not to populate
+        # $c->engine or do any other things we might regret.
 
-=cut
+        $app->loading_psgi_file(1);
+        my $psgi_app = Plack::Util::load_psgi($psgi_file);
+        $app->loading_psgi_file(0);
 
-sub setup_psgi_app {
-    my ($app) = @_;
+        return $psgi_app
+            unless $app->engine_loader->needs_psgi_engine_compat_hack;
 
-    if (my $home = Path::Class::Dir->new($app->config->{home})) {
-        my $psgi_file = $home->file(
-            Catalyst::Utils::appprefix($app) . '.psgi',
-        );
+        warn <<"EOW";
+Found a legacy Catalyst::Engine::PSGI .psgi file at ${psgi_file}.
 
-        return Plack::Util::load_psgi($psgi_file)
-            if -e $psgi_file;
+Its content has been ignored. Please consult the Catalyst::Upgrading
+documentation on how to upgrade from Catalyst::Engine::PSGI.
+EOW
     }
 
-    return Plack::Middleware::Conditional->wrap(
-        $app->raw_psgi_app,
+    return $app->apply_default_middlewares($app->psgi_app);
+}
+
+=head2 $c->apply_default_middlewares
+
+Adds the following L<Plack> middlewares to your application, since they are
+useful and commonly needed:
+
+L<Plack::Middleware::ReverseProxy>, (conditionally added based on the status
+of your $ENV{REMOTE_ADDR}, and can be forced on with C<using_frontend_proxy>
+or forced off with C<ignore_frontend_proxy>), L<Plack::Middleware::LighttpdScriptNameFix>
+(if you are using Lighttpd), L<Plack::Middleware::IIS6ScriptNameFix> (always
+applied since this middleware is smart enough to conditionally apply itself).
+
+Additionally if we detect we are using Nginx, we add a bit of custom middleware
+to solve some problems with the way that server handles $ENV{PATH_INFO} and
+$ENV{SCRIPT_NAME}
+
+=cut
+
+
+sub apply_default_middlewares {
+    my ($app, $psgi_app) = @_;
+
+    $psgi_app = Plack::Middleware::Conditional->wrap(
+        $psgi_app,
         builder   => sub { Plack::Middleware::ReverseProxy->wrap($_[0]) },
         condition => sub {
             my ($env) = @_;
@@ -2670,18 +2725,41 @@ sub setup_psgi_app {
                 || $app->config->{using_frontend_proxy};
         },
     );
+
+    my $server_matches = sub {
+        my ($re) = @_;
+        return sub {
+            my ($env) = @_;
+            my $server = $env->{SERVER_SOFTWARE};
+            return unless $server;
+            return $server =~ $re ? 1 : 0;
+        };
+    };
+
+    # If we're running under Lighttpd, swap PATH_INFO and SCRIPT_NAME
+    # http://lists.scsys.co.uk/pipermail/catalyst/2006-June/008361.html
+    $psgi_app = Plack::Middleware::LighttpdScriptNameFix->wrap($psgi_app);
+
+    # we're applying this unconditionally as the middleware itself already makes
+    # sure it doesn't fuck things up if it's not running under one of the right
+    # IIS versions
+    $psgi_app = Plack::Middleware::IIS6ScriptNameFix->wrap($psgi_app);
+
+    return $psgi_app;
 }
 
-=head2 $c->raw_psgi_app
+=head2 $c->psgi_app
 
 Returns a PSGI application code reference for the catalyst application
 C<$c>. This is the bare application without any middlewares
-applied. C<${myapp}.psgi> is not taken into account. See
-L</"$c->setup_psgi_app">.
+applied. C<${myapp}.psgi> is not taken into account.
+
+This is what you want to be using to retrieve the PSGI application code
+reference of your Catalyst application for use in F<.psgi> files.
 
 =cut
 
-sub raw_psgi_app {
+sub psgi_app {
     my ($app) = @_;
     return $app->engine->build_psgi_app($app);
 }
@@ -2803,7 +2881,7 @@ the plugin name does not begin with C<Catalyst::Plugin::>.
         my $class = ref $proto || $proto;
 
         Class::MOP::load_class( $plugin );
-        $class->log->warn( "$plugin inherits from 'Catalyst::Component' - this is decated and will not work in 5.81" )
+        $class->log->warn( "$plugin inherits from 'Catalyst::Component' - this is deprecated and will not work in 5.81" )
             if $plugin->isa( 'Catalyst::Component' );
         $proto->_plugins->{$plugin} = 1;
         unless ($instant) {
@@ -2969,8 +3047,46 @@ to be shown in hit debug tables in the test server.
 =item *
 
 C<use_request_uri_for_path> - Controlls if the C<REQUEST_URI> or C<PATH_INFO> environment
-variable should be used for determining the request path. See L<Catalyst::Engine::CGI/PATH DECODING>
-for more information.
+variable should be used for determining the request path. 
+
+Most web server environments pass the requested path to the application using environment variables,
+from which Catalyst has to reconstruct the request base (i.e. the top level path to / in the application,
+exposed as C<< $c->request->base >>) and the request path below that base.
+
+There are two methods of doing this, both of which have advantages and disadvantages. Which method is used
+is determined by the C<< $c->config(use_request_uri_for_path) >> setting (which can either be true or false).
+
+=over
+
+=item use_request_uri_for_path => 0
+
+This is the default (and the) traditional method that Catalyst has used for determining the path information.
+The path is synthesised from a combination of the C<PATH_INFO> and C<SCRIPT_NAME> environment variables.
+The allows the application to behave correctly when C<mod_rewrite> is being used to redirect requests
+into the application, as these variables are adjusted by mod_rewrite to take account for the redirect.
+
+However this method has the major disadvantage that it is impossible to correctly decode some elements
+of the path, as RFC 3875 says: "C<< Unlike a URI path, the PATH_INFO is not URL-encoded, and cannot
+contain path-segment parameters. >>" This means PATH_INFO is B<always> decoded, and therefore Catalyst
+can't distinguish / vs %2F in paths (in addition to other encoded values).
+
+=item use_request_uri_for_path => 1
+
+This method uses the C<REQUEST_URI> and C<SCRIPT_NAME> environment variables. As C<REQUEST_URI> is never
+decoded, this means that applications using this mode can correctly handle URIs including the %2F character
+(i.e. with C<AllowEncodedSlashes> set to C<On> in Apache).
+
+Given that this method of path resolution is provably more correct, it is recommended that you use
+this unless you have a specific need to deploy your application in a non-standard environment, and you are
+aware of the implications of not being able to handle encoded URI paths correctly.
+
+However it also means that in a number of cases when the app isn't installed directly at a path, but instead
+is having paths rewritten into it (e.g. as a .cgi/fcgi in a public_html directory, with mod_rewrite in a
+.htaccess file, or when SSI is used to rewrite pages into the app, or when sub-paths of the app are exposed
+at other URIs than that which the app is 'normally' based at with C<mod_rewrite>), the resolution of
+C<< $c->request->base >> will be incorrect.
+
+=back
 
 =item *
 
@@ -3225,6 +3341,12 @@ Yuval Kogman, C<nothingmuch@woobling.org>
 
 rainboxx: Matthias Dietrich, C<perl@rainboxx.de>
 
+dd070: Dhaval Dhanani <dhaval070@gmail.com>
+
+=head1 COPYRIGHT
+
+Copyright (c) 2005, the above named PROJECT FOUNDER and CONTRIBUTORS.
+
 =head1 LICENSE
 
 This library is free software. You can redistribute it and/or modify it under