X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FCatalyst.pm;h=8f91b31381a95b798aa231ef055b3cbb43a6904d;hb=7c7954572cfa0a81c6cea8c15c6110386b851418;hp=07a8c1605e496a37d207ea55f2ce9d08e3bc4ba5;hpb=69c6b6cb43d15b6a145523f4d83f3855b6c3677e;p=catagits%2FCatalyst-Runtime.git diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index 07a8c16..8f91b31 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -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 (); @@ -29,8 +28,15 @@ use Tree::Simple::Visitor::FindByUID; 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; } @@ -68,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/; + 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::CGI'); __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.80025'; +our $VERSION = '5.89003'; sub import { my ( $class, @arguments ) = @_; @@ -100,7 +105,12 @@ sub import { $meta->superclasses(grep { $_ ne 'Moose::Object' } $meta->superclasses); unless( $meta->has_method('meta') ){ - $meta->add_method(meta => sub { Moose::Meta::Class->initialize("${caller}") } ); + if ($Moose::VERSION >= 1.15) { + $meta->_add_meta_method('meta'); + } + else { + $meta->add_method(meta => sub { Moose::Meta::Class->initialize("${caller}") } ); + } } $caller->arguments( [@arguments] ); @@ -266,11 +276,11 @@ the name will be replaced with underscores, e.g. MyApp::Web should use MYAPP_WEB_HOME. If both variables are set, the MYAPP_HOME one will be used. If none of these are set, Catalyst will attempt to automatically detect the -home directory. If you are working in a development envirnoment, Catalyst +home directory. If you are working in a development environment, Catalyst will try and find the directory containing either Makefile.PL, Build.PL or dist.ini. If the application has been installed into the system (i.e. you have done C), then Catalyst will use the path to your -application module, without the .pm extension (ie, /foo/MyApp if your +application module, without the .pm extension (e.g., /foo/MyApp if your application was installed at /foo/MyApp.pm) =head2 -Log @@ -338,9 +348,10 @@ call to forward. Note that L<< forward|/"$c->forward( $action [, \@arguments ] )" >> implies an C<< eval { } >> around the call (actually -L<< execute|/"$c->execute( $class, $coderef )" >> does), thus de-fatalizing -all 'dies' within the called action. If you want C to propagate you -need to do something like: +L<< execute|/"$c->execute( $class, $coderef )" >> does), thus rendering all +exceptions thrown by the called action non-fatal and pushing them onto +$c->error instead. If you want C to propagate you need to do something +like: $c->forward('foo'); die join "\n", @{ $c->error } if @{ $c->error }; @@ -365,6 +376,8 @@ or stash it like so: and access it from the stash. +Keep in mind that the C method used is that of the caller action. So a C<$c-Edetach> inside a forwarded action would run the C method from the original action requested. + =cut sub forward { my $c = shift; no warnings 'recursion'; $c->dispatcher->forward( $c, @_ ) } @@ -400,7 +413,7 @@ L return information for the visited action when they are invoked within the visited action. This is different from the behavior of L<< forward|/"$c->forward( $action [, \@arguments ] )" >>, which continues to use the $c->action object from the caller action even when -invoked from the callee. +invoked from the called action. C<< $c->stash >> is kept unchanged. @@ -428,6 +441,10 @@ with localized C<< $c->action >> and C<< $c->namespace >>. Like C, C escapes the processing of the current request chain on completion, and does not return to its caller. +@arguments are arguments to the final destination of $action. @captures are +arguments to the intermediate steps, if any, on the way to the final sub of +$action. + =cut sub go { my $c = shift; $c->dispatcher->go( $c, @_ ) } @@ -741,7 +758,12 @@ sub view { unless ( ref($name) ) { # Direct component hash lookup to avoid costly regexps my $comps = $c->components; my $check = $appclass."::View::".$name; - return $c->_filter_component( $comps->{$check}, @args ) if exists $comps->{$check}; + if( exists $comps->{$check} ) { + return $c->_filter_component( $comps->{$check}, @args ); + } + else { + $c->log->warn( "Attempted to use view '$check', but does not exist" ); + } } my @result = $c->_comp_search_prefixes( $name, qw/View V/ ); return map { $c->_filter_component( $_, @args ) } @result if ref $name; @@ -845,6 +867,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; @@ -911,12 +936,18 @@ on the receiving component to access the config value. use Moose; # this attr will receive 'baz' at construction time - has 'bar' => ( + has 'bar' => ( is => 'rw', isa => 'Str', ); You can then get the value 'baz' by calling $c->model('Foo')->bar +(or $self->bar inside code in the model). + +B you MUST NOT call C<< $self->config >> or C<< __PACKAGE__->config >> +as a way of reading config within your code, as this B give you the +correctly merged config back. You B take the config values supplied to +the constructor and use those instead. =cut @@ -1092,7 +1123,10 @@ sub setup { $class->setup_log( delete $flags->{log} ); $class->setup_plugins( delete $flags->{plugins} ); $class->setup_dispatcher( delete $flags->{dispatcher} ); - $class->setup_engine( delete $flags->{engine} ); + if (my $engine = delete $flags->{engine}) { + $class->log->warn("Specifying the engine in ->setup is no longer supported, see Catalyst::Upgrading"); + } + $class->setup_engine(); $class->setup_stats( delete $flags->{stats} ); for my $flag ( sort keys %{$flags} ) { @@ -1219,7 +1253,7 @@ EOF A hook to attach modifiers to. This method does not do anything except set the C accessor. -Applying method modifiers to the C method doesn't work, because of quirky thingsdone for plugin setup. +Applying method modifiers to the C method doesn't work, because of quirky things done for plugin setup. Example: @@ -1638,7 +1672,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; @@ -1660,8 +1696,8 @@ sub execute { $error = qq/Caught exception in $class->$name "$error"/; } $c->error($error); - $c->state(0); } + $c->state(0); } return $c->state; } @@ -1701,7 +1737,7 @@ sub _stats_start_execute { my $parent = $c->stack->[-1]; # forward, locate the caller - if ( exists $c->counter->{"$parent"} ) { + if ( defined $parent && exists $c->counter->{"$parent"} ) { $c->stats->profile( begin => $action, parent => "$parent" . $c->counter->{"$parent"}, @@ -1835,14 +1871,14 @@ sub finalize_headers { } # Content-Length - if ( $response->body && !$response->content_length ) { + if ( defined $response->body && length $response->body && !$response->content_length ) { # get the length from a filehandle - if ( blessed( $response->body ) && $response->body->can('read') ) + 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'); @@ -1916,7 +1952,7 @@ sub handle_request { # Always expect worst case! my $status = -1; - eval { + try { if ($class->debug) { my $secs = time - $START || 1; my $av = sprintf '%.3f', $COUNT / $secs; @@ -1927,12 +1963,11 @@ sub handle_request { my $c = $class->prepare(@arguments); $c->dispatch; $status = $c->finalize; - }; - - if ( my $error = $@ ) { - chomp $error; - $class->log->error(qq/Caught exception in engine "$error"/); } + catch { + chomp(my $error = $_); + $class->log->error(qq/Caught exception in engine "$error"/); + }; $COUNT++; @@ -1969,28 +2004,38 @@ sub prepare { $c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION ); } - #XXX reuse coderef from can - # Allow engine to direct the prepare flow (for POE) - if ( $c->engine->can('prepare') ) { - $c->engine->prepare( $c, @arguments ); - } - else { - $c->prepare_request(@arguments); - $c->prepare_connection; - $c->prepare_query_parameters; - $c->prepare_headers; - $c->prepare_cookies; - $c->prepare_path; - - # Prepare the body for reading, either by prepare_body - # or the user, if they are using $c->read - $c->prepare_read; - - # Parse the body unless the user wants it on-demand - unless ( ref($c)->config->{parse_on_demand} ) { - $c->prepare_body; + try { + # Allow engine to direct the prepare flow (for POE) + if ( my $prepare = $c->engine->can('prepare') ) { + $c->engine->$prepare( $c, @arguments ); + } + else { + $c->prepare_request(@arguments); + $c->prepare_connection; + $c->prepare_query_parameters; + $c->prepare_headers; + $c->prepare_cookies; + $c->prepare_path; + + # Prepare the body for reading, either by prepare_body + # or the user, if they are using $c->read + $c->prepare_read; + + # Parse the body unless the user wants it on-demand + unless ( ref($c)->config->{parse_on_demand} ) { + $c->prepare_body; + } } } + # VERY ugly and probably shouldn't rely on ->finalize actually working + catch { + # failed prepare is always due to an invalid request, right? + $c->response->status(400); + $c->response->content_type('text/plain'); + $c->response->body('Bad Request'); + $c->finalize; + die $_; + }; my $method = $c->req->method || ''; my $path = $c->req->path; @@ -2150,7 +2195,7 @@ sub log_request { $c->log->debug("Query keywords are: $keywords"); } - $c->log_request_parameters( query => $request->query_parameters, body => $request->body_parameters ); + $c->log_request_parameters( query => $request->query_parameters, $request->_has_body ? (body => $request->body_parameters) : () ); $c->log_request_uploads($request); } @@ -2205,7 +2250,7 @@ sub log_response_status_line { =head2 $c->log_response_headers($headers); -Hook method which can be wrapped by plugins to log the responseheaders. +Hook method which can be wrapped by plugins to log the response headers. No-op in the default implementation. =cut @@ -2340,11 +2385,11 @@ sub prepare_write { my $c = shift; $c->engine->prepare_write( $c, @_ ) } =head2 $c->request_class -Returns or sets the request class. +Returns or sets the request class. Defaults to L. =head2 $c->response_class -Returns or sets the response class. +Returns or sets the response class. Defaults to L. =head2 $c->read( [$maxlength] ) @@ -2369,7 +2414,12 @@ Starts the engine. =cut -sub run { my $c = shift; return $c->engine->run( $c, @_ ) } +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 ) @@ -2553,114 +2603,177 @@ Sets up engine. =cut +sub engine_class { + my $class = shift; + $class->engine_loader->catalyst_engine_class(@_); +} + sub setup_engine { - my ( $class, $engine ) = @_; + my ($class, $requested_engine) = @_; + + $class->engine_loader( + Catalyst::EngineLoader->new({ + application_name => $class, + (defined $requested_engine + ? (requested_engine => $requested_engine) : ()), + }), + ); - if ($engine) { - $engine = 'Catalyst::Engine::' . $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}) { + 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 $psgi_app = $class->psgi_app; + $apache->call_app($r, $psgi_app); + }); + + $meta->make_immutable(%immutable_options) if $was_immutable; } - if ( my $env = Catalyst::Utils::env_value( $class, 'ENGINE' ) ) { - $engine = 'Catalyst::Engine::' . $env; + $class->engine( $engine->new ); + + return; +} + +sub _finalized_psgi_app { + my ($app) = @_; + + unless ($app->_psgi_app) { + my $psgi_app = $app->_setup_psgi_app; + $app->_psgi_app($psgi_app); } - if ( $ENV{MOD_PERL} ) { - my $meta = Class::MOP::get_metaclass_by_name($class); + return $app->_psgi_app; +} - # create the apache method - $meta->add_method('apache' => sub { shift->engine->apache }); +sub _setup_psgi_app { + my ($app) = @_; - my ( $software, $version ) = - $ENV{MOD_PERL} =~ /^(\S+)\/(\d+(?:[\.\_]\d+)+)/; + for my $home (Path::Class::Dir->new($app->config->{home})) { + my $psgi_file = $home->file( + Catalyst::Utils::appprefix($app) . '.psgi', + ); - $version =~ s/_//g; - $version =~ s/(\.[^.]+)\./$1/g; + next unless -e $psgi_file; - if ( $software eq 'mod_perl' ) { + # 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. - if ( !$engine ) { + $app->loading_psgi_file(1); + my $psgi_app = Plack::Util::load_psgi($psgi_file); + $app->loading_psgi_file(0); - if ( $version >= 1.99922 ) { - $engine = 'Catalyst::Engine::Apache2::MP20'; - } + return $psgi_app + unless $app->engine_loader->needs_psgi_engine_compat_hack; - elsif ( $version >= 1.9901 ) { - $engine = 'Catalyst::Engine::Apache2::MP19'; - } + warn <<"EOW"; +Found a legacy Catalyst::Engine::PSGI .psgi file at ${psgi_file}. - elsif ( $version >= 1.24 ) { - $engine = 'Catalyst::Engine::Apache::MP13'; - } +Its content has been ignored. Please consult the Catalyst::Upgrading +documentation on how to upgrade from Catalyst::Engine::PSGI. +EOW + } - else { - Catalyst::Exception->throw( message => - qq/Unsupported mod_perl version: $ENV{MOD_PERL}/ ); - } + return $app->apply_default_middlewares($app->psgi_app); +} - } +=head2 $c->apply_default_middlewares - # install the correct mod_perl handler - if ( $version >= 1.9901 ) { - *handler = sub : method { - shift->handle_request(@_); - }; - } - else { - *handler = sub ($$) { shift->handle_request(@_) }; - } +Adds the following L middlewares to your application, since they are +useful and commonly needed: - } +L, (conditionally added based on the status +of your $ENV{REMOTE_ADDR}, and can be forced on with C +or forced off with C), L +(if you are using Lighttpd), L (always +applied since this middleware is smart enough to conditionally apply itself). - elsif ( $software eq 'Zeus-Perl' ) { - $engine = 'Catalyst::Engine::Zeus'; - } +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} - else { - Catalyst::Exception->throw( - message => qq/Unsupported mod_perl: $ENV{MOD_PERL}/ ); - } - } +=cut - unless ($engine) { - $engine = $class->engine_class; - } - Class::MOP::load_class($engine); +sub apply_default_middlewares { + my ($app, $psgi_app) = @_; - # check for old engines that are no longer compatible - my $old_engine; - if ( $engine->isa('Catalyst::Engine::Apache') - && !Catalyst::Engine::Apache->VERSION ) - { - $old_engine = 1; - } + $psgi_app = Plack::Middleware::Conditional->wrap( + $psgi_app, + builder => sub { Plack::Middleware::ReverseProxy->wrap($_[0]) }, + condition => sub { + my ($env) = @_; + return if $app->config->{ignore_frontend_proxy}; + return $env->{REMOTE_ADDR} eq '127.0.0.1' + || $app->config->{using_frontend_proxy}; + }, + ); - elsif ( $engine->isa('Catalyst::Engine::Server::Base') - && Catalyst::Engine::Server->VERSION le '0.02' ) - { - $old_engine = 1; - } + my $server_matches = sub { + my ($re) = @_; + return sub { + my ($env) = @_; + my $server = $env->{SERVER_SOFTWARE}; + return unless $server; + return $server =~ $re ? 1 : 0; + }; + }; - elsif ($engine->isa('Catalyst::Engine::HTTP::POE') - && $engine->VERSION eq '0.01' ) - { - $old_engine = 1; - } + # 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); + + $psgi_app = Plack::Middleware::Conditional->wrap( + $psgi_app, + condition => $server_matches->(qr/^nginx/), + builder => sub { + my ($to_wrap) = @_; + return sub { + my ($env) = @_; + my $script_name = $env->{SCRIPT_NAME}; + $env->{PATH_INFO} =~ s/^$script_name//g; + return $to_wrap->($env); + }; + }, + ); - elsif ($engine->isa('Catalyst::Engine::Zeus') - && $engine->VERSION eq '0.01' ) - { - $old_engine = 1; - } + # 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); - if ($old_engine) { - Catalyst::Exception->throw( message => - qq/Engine "$engine" is not supported by this version of Catalyst/ - ); - } + return $psgi_app; +} - # engine instance - $class->engine( $engine->new ); +=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. + +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 psgi_app { + my ($app) = @_; + return $app->engine->build_psgi_app($app); } =head2 $c->setup_home @@ -2780,7 +2893,7 @@ the plugin name does not begin with C. 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) { @@ -2924,7 +3037,7 @@ welcome screens C - The request body (for example file uploads) will not be parsed until it is accessed. This allows you to (for example) check authentication (and reject -the upload) before actually recieving all the data. See L +the upload) before actually receiving all the data. See L =item * @@ -2945,7 +3058,7 @@ to be shown in hit debug tables in the test server. =item * -C - Controlls if the C or C environment +C - Controls if the C or C environment variable should be used for determining the request path. See L for more information. @@ -3062,6 +3175,8 @@ Wiki: =head2 L - The test suite. +=begin stopwords + =head1 PROJECT FOUNDER sri: Sebastian Riedel @@ -3202,6 +3317,14 @@ Yuval Kogman, C rainboxx: Matthias Dietrich, C +dd070: Dhaval Dhanani + +=end stopwords + +=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