X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst.pm;h=39962a67863e00383200c76658afc49f9229c42e;hp=4d53a0d14cdc44c439142b9368e2f884bfcf1a2b;hb=a6fb59b76c87d2339fc7b837e492f2e17fa5f841;hpb=2f166b9e39b5e6ba1dfdf8b933c3be86f66e6cee diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index 4d53a0d..39962a6 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -14,6 +14,7 @@ use Catalyst::Request::Upload; use Catalyst::Response; use Catalyst::Utils; use Catalyst::Controller; +use Data::OptList; use Devel::InnerPackage (); use File::stat; use Module::Pluggable::Object (); @@ -28,8 +29,13 @@ 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; BEGIN { require 5.008004; } @@ -67,21 +73,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/; __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.80020'; -our $PRETTY_VERSION = $VERSION; - -$VERSION = eval $VERSION; +our $VERSION = '5.89002'; sub import { my ( $class, @arguments ) = @_; @@ -102,7 +104,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] ); @@ -283,14 +290,15 @@ Specifies a comma-delimited list of log levels. =head2 -Stats -Enables statistics collection and reporting. You can also force this setting -from the system environment with CATALYST_STATS or _STATS. The -environment settings override the application, with _STATS having the -highest priority. +Enables statistics collection and reporting. -e.g. + use Catalyst qw/-Stats=1/; - use Catalyst qw/-Stats=1/ +You can also force this setting from the system environment with CATALYST_STATS +or _STATS. The environment settings override the application, with +_STATS having the highest priority. + +Stats are also enabled if L<< debugging |/"-Debug" >> is enabled. =head1 METHODS @@ -366,6 +374,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, @_ ) } @@ -429,6 +439,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, @_ ) } @@ -640,7 +654,13 @@ If you want to search for controllers, pass in a regexp as the argument. sub controller { my ( $c, $name, @args ) = @_; + my $appclass = ref($c) || $c; if( $name ) { + unless ( ref($name) ) { # Direct component hash lookup to avoid costly regexps + my $comps = $c->components; + my $check = $appclass."::Controller::".$name; + return $c->_filter_component( $comps->{$check}, @args ) if exists $comps->{$check}; + } my @result = $c->_comp_search_prefixes( $name, qw/Controller C/ ); return map { $c->_filter_component( $_, @args ) } @result if ref $name; return $c->_filter_component( $result[ 0 ], @args ); @@ -674,6 +694,11 @@ sub model { my ( $c, $name, @args ) = @_; my $appclass = ref($c) || $c; if( $name ) { + unless ( ref($name) ) { # Direct component hash lookup to avoid costly regexps + my $comps = $c->components; + my $check = $appclass."::Model::".$name; + return $c->_filter_component( $comps->{$check}, @args ) if exists $comps->{$check}; + } my @result = $c->_comp_search_prefixes( $name, qw/Model M/ ); return map { $c->_filter_component( $_, @args ) } @result if ref $name; return $c->_filter_component( $result[ 0 ], @args ); @@ -728,6 +753,16 @@ sub view { my $appclass = ref($c) || $c; if( $name ) { + unless ( ref($name) ) { # Direct component hash lookup to avoid costly regexps + my $comps = $c->components; + my $check = $appclass."::View::".$name; + 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; return $c->_filter_component( $result[ 0 ], @args ); @@ -877,7 +912,7 @@ component is constructed. For example: MyApp->config({ 'Model::Foo' => { bar => 'baz', overrides => 'me' } }); - MyApp::Model::Foo->config({ quux => 'frob', 'overrides => 'this' }); + MyApp::Model::Foo->config({ quux => 'frob', overrides => 'this' }); will mean that C receives the following data when constructed: @@ -888,6 +923,27 @@ constructed: overrides => 'me', }); +It's common practice to use a Moose attribute +on the receiving component to access the config value. + + package MyApp::Model::Foo; + + use Moose; + + # this attr will receive 'baz' at construction time + 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 around config => sub { @@ -1062,7 +1118,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, XXX FIXME"); + } + $class->setup_engine(); $class->setup_stats( delete $flags->{stats} ); for my $flag ( sort keys %{$flags} ) { @@ -1146,7 +1205,7 @@ EOF if ( $class->debug ) { my $name = $class->config->{name} || 'Application'; - $class->log->info("$name powered by Catalyst $Catalyst::PRETTY_VERSION"); + $class->log->info("$name powered by Catalyst $Catalyst::VERSION"); } # Make sure that the application class becomes immutable at this point, @@ -1212,7 +1271,9 @@ sub setup_finalize { Constructs an absolute L object based on the application root, the provided path, and the additional arguments and query parameters provided. -When used as a string, provides a textual URI. +When used as a string, provides a textual URI. If you need more flexibility +than this (i.e. the option to provide relative URIs etc.) see +L. If no arguments are provided, the URI for the current action is returned. To return the current action and also provide @args, use @@ -1266,13 +1327,11 @@ sub uri_for { carp "uri_for called with undef argument" if grep { ! defined $_ } @args; foreach my $arg (@args) { utf8::encode($arg) if utf8::is_utf8($arg); - } - s/([^$URI::uric])/$URI::Escape::escapes{$1}/go for @args; - if (blessed $path) { # Action object only. - s|/|%2F|g for @args; + $arg =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go; } if ( blessed($path) ) { # action object + s|/|%2F|g for @args; my $captures = [ map { s|/|%2F|g; $_; } ( scalar @args && ref $args[0] eq 'ARRAY' ? @{ shift(@args) } @@ -1293,8 +1352,6 @@ sub uri_for { $path = '/' if $path eq ''; } - undef($path) if (defined $path && $path eq ''); - unshift(@args, $path); unless (defined $path && $path =~ s!^/!!) { # in-place strip @@ -1500,7 +1557,7 @@ sub welcome_message { models, and views; they can save you a lot of work.

-
script/${prefix}_create.pl -help
+
script/${prefix}_create.pl --help

Also, be sure to check out the vast and growing collection of plugins for Catalyst on CPAN; you are likely to find what you need there. @@ -1610,7 +1667,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; @@ -1673,7 +1732,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"}, @@ -1743,6 +1802,8 @@ sub finalize { $c->finalize_body; } + $c->log_response; + if ($c->use_stats) { my $elapsed = sprintf '%f', $c->stats->elapsed; my $av = $elapsed == 0 ? '??' : sprintf '%.3f', 1 / $elapsed; @@ -1805,10 +1866,10 @@ 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 ) { @@ -1875,7 +1936,7 @@ namespaces. sub get_actions { my $c = shift; $c->dispatcher->get_actions( $c, @_ ) } -=head2 $c->handle_request( $class, @arguments ) +=head2 $app->handle_request( @arguments ) Called to handle each HTTP request. @@ -1886,7 +1947,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; @@ -1897,12 +1958,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++; @@ -1935,40 +1995,49 @@ sub prepare { #surely this is not the most efficient way to do things... $c->stats($class->stats_class->new)->enable($c->use_stats); - if ( $c->debug ) { + if ( $c->debug || $c->config->{enable_catalyst_header} ) { $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; $path = '/' unless length $path; my $address = $c->req->address || ''; - $c->log->debug(qq/"$method" request for "$path" from "$address"/) - if $c->debug; + $c->log_request; $c->prepare_action; @@ -1998,17 +2067,6 @@ sub prepare_body { $c->engine->prepare_body( $c, @_ ); $c->prepare_parameters; $c->prepare_uploads; - - if ( $c->debug && keys %{ $c->req->body_parameters } ) { - my $t = Text::SimpleTable->new( [ 35, 'Parameter' ], [ 36, 'Value' ] ); - for my $key ( sort keys %{ $c->req->body_parameters } ) { - my $param = $c->req->body_parameters->{$key}; - my $value = defined($param) ? $param : ''; - $t->row( $key, - ref $value eq 'ARRAY' ? ( join ', ', @$value ) : $value ); - } - $c->log->debug( "Body Parameters are:\n" . $t->draw ); - } } =head2 $c->prepare_body_chunk( $chunk ) @@ -2092,55 +2150,156 @@ sub prepare_query_parameters { my $c = shift; $c->engine->prepare_query_parameters( $c, @_ ); +} - if ( $c->debug && keys %{ $c->request->query_parameters } ) { - my $t = Text::SimpleTable->new( [ 35, 'Parameter' ], [ 36, 'Value' ] ); - for my $key ( sort keys %{ $c->req->query_parameters } ) { - my $param = $c->req->query_parameters->{$key}; - my $value = defined($param) ? $param : ''; - $t->row( $key, - ref $value eq 'ARRAY' ? ( join ', ', @$value ) : $value ); - } - $c->log->debug( "Query Parameters are:\n" . $t->draw ); +=head2 $c->log_request + +Writes information about the request to the debug logs. This includes: + +=over 4 + +=item * Request method, path, and remote IP address + +=item * Query keywords (see L) + +=item * Request parameters + +=item * File uploads + +=back + +=cut + +sub log_request { + my $c = shift; + + return unless $c->debug; + + my($dump) = grep {$_->[0] eq 'Request' } $c->dump_these; + my $request = $dump->[1]; + + my ( $method, $path, $address ) = ( $request->method, $request->path, $request->address ); + $method ||= ''; + $path = '/' unless length $path; + $address ||= ''; + $c->log->debug(qq/"$method" request for "$path" from "$address"/); + + $c->log_request_headers($request->headers); + + if ( my $keywords = $request->query_keywords ) { + $c->log->debug("Query keywords are: $keywords"); } + + $c->log_request_parameters( query => $request->query_parameters, $request->_has_body ? (body => $request->body_parameters) : () ); + + $c->log_request_uploads($request); } -=head2 $c->prepare_read +=head2 $c->log_response -Prepares the input for reading. +Writes information about the response to the debug logs by calling +C<< $c->log_response_status_line >> and C<< $c->log_response_headers >>. =cut -sub prepare_read { my $c = shift; $c->engine->prepare_read( $c, @_ ) } +sub log_response { + my $c = shift; -=head2 $c->prepare_request + return unless $c->debug; -Prepares the engine request. + my($dump) = grep {$_->[0] eq 'Response' } $c->dump_these; + my $response = $dump->[1]; + + $c->log_response_status_line($response); + $c->log_response_headers($response->headers); +} + +=head2 $c->log_response_status_line($response) + +Writes one line of information about the response to the debug logs. This includes: + +=over 4 + +=item * Response status code + +=item * Content-Type header (if present) + +=item * Content-Length header (if present) + +=back =cut -sub prepare_request { my $c = shift; $c->engine->prepare_request( $c, @_ ) } +sub log_response_status_line { + my ($c, $response) = @_; -=head2 $c->prepare_uploads + $c->log->debug( + sprintf( + 'Response Code: %s; Content-Type: %s; Content-Length: %s', + $response->status || 'unknown', + $response->headers->header('Content-Type') || 'unknown', + $response->headers->header('Content-Length') || 'unknown' + ) + ); +} -Prepares uploads. +=head2 $c->log_response_headers($headers); + +Hook method which can be wrapped by plugins to log the responseheaders. +No-op in the default implementation. =cut -sub prepare_uploads { - my $c = shift; +sub log_response_headers {} - $c->engine->prepare_uploads( $c, @_ ); +=head2 $c->log_request_parameters( query => {}, body => {} ) + +Logs request parameters to debug logs + +=cut - if ( $c->debug && keys %{ $c->request->uploads } ) { +sub log_request_parameters { + my $c = shift; + my %all_params = @_; + + return unless $c->debug; + + my $column_width = Catalyst::Utils::term_width() - 44; + foreach my $type (qw(query body)) { + my $params = $all_params{$type}; + next if ! keys %$params; + my $t = Text::SimpleTable->new( [ 35, 'Parameter' ], [ $column_width, 'Value' ] ); + for my $key ( sort keys %$params ) { + my $param = $params->{$key}; + my $value = defined($param) ? $param : ''; + $t->row( $key, ref $value eq 'ARRAY' ? ( join ', ', @$value ) : $value ); + } + $c->log->debug( ucfirst($type) . " Parameters are:\n" . $t->draw ); + } +} + +=head2 $c->log_request_uploads + +Logs file uploads included in the request to the debug logs. +The parameter name, filename, file type, and file size are all included in +the debug logs. + +=cut + +sub log_request_uploads { + my $c = shift; + my $request = shift; + return unless $c->debug; + my $uploads = $request->uploads; + if ( keys %$uploads ) { my $t = Text::SimpleTable->new( [ 12, 'Parameter' ], [ 26, 'Filename' ], [ 18, 'Type' ], [ 9, 'Size' ] ); - for my $key ( sort keys %{ $c->request->uploads } ) { - my $upload = $c->request->uploads->{$key}; + for my $key ( sort keys %$uploads ) { + my $upload = $uploads->{$key}; for my $u ( ref $upload eq 'ARRAY' ? @{$upload} : ($upload) ) { $t->row( $key, $u->filename, $u->type, $u->size ); } @@ -2149,6 +2308,68 @@ sub prepare_uploads { } } +=head2 $c->log_request_headers($headers); + +Hook method which can be wrapped by plugins to log the request headers. +No-op in the default implementation. + +=cut + +sub log_request_headers {} + +=head2 $c->log_headers($type => $headers) + +Logs L (either request or response) to the debug logs. + +=cut + +sub log_headers { + my $c = shift; + my $type = shift; + my $headers = shift; # an HTTP::Headers instance + + return unless $c->debug; + + my $column_width = Catalyst::Utils::term_width() - 28; + my $t = Text::SimpleTable->new( [ 15, 'Header Name' ], [ $column_width, 'Value' ] ); + $headers->scan( + sub { + my ( $name, $value ) = @_; + $t->row( $name, $value ); + } + ); + $c->log->debug( ucfirst($type) . " Headers:\n" . $t->draw ); +} + + +=head2 $c->prepare_read + +Prepares the input for reading. + +=cut + +sub prepare_read { my $c = shift; $c->engine->prepare_read( $c, @_ ) } + +=head2 $c->prepare_request + +Prepares the engine request. + +=cut + +sub prepare_request { my $c = shift; $c->engine->prepare_request( $c, @_ ) } + +=head2 $c->prepare_uploads + +Prepares uploads. + +=cut + +sub prepare_uploads { + my $c = shift; + + $c->engine->prepare_uploads( $c, @_ ); +} + =head2 $c->prepare_write Prepares the output for writing. @@ -2159,11 +2380,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] ) @@ -2188,7 +2409,7 @@ Starts the engine. =cut -sub run { my $c = shift; return $c->engine->run( $c, @_ ) } +sub run { my $c = shift; return $c->engine->run( $c, $c->_finalized_psgi_app, @_ ) } =head2 $c->set_action( $action, $code, $namespace, $attrs ) @@ -2226,8 +2447,7 @@ sub setup_components { my $config = $class->config->{ setup_components }; - my @comps = sort { length $a <=> length $b } - $class->locate_components($config); + my @comps = $class->locate_components($config); my %comps = map { $_ => 1 } @comps; my $deprecatedcatalyst_component_names = grep { /::[CMV]::/ } @comps; @@ -2242,10 +2462,6 @@ sub setup_components { # we know M::P::O found a file on disk so this is safe Catalyst::Utils::ensure_class_loaded( $component, { ignore_loaded => 1 } ); - - # Needs to be done as soon as the component is loaded, as loading a sub-component - # (next time round the loop) can cause us to get the wrong metaclass.. - $class->_controller_init_base_classes($component); } for my $component (@comps) { @@ -2255,7 +2471,6 @@ sub setup_components { : $class->expand_component_module( $component, $config ); for my $component (@expanded_components) { next if $comps{$component}; - $class->_controller_init_base_classes($component); # Also cover inner packages $class->components->{ $component } = $class->setup_component($component); } } @@ -2287,7 +2502,8 @@ sub locate_components { %$config ); - my @comps = $locator->plugins; + # XXX think about ditching this sort entirely + my @comps = sort { length $a <=> length $b } $locator->plugins; return @comps; } @@ -2308,19 +2524,6 @@ sub expand_component_module { =cut -# FIXME - Ugly, ugly hack to ensure the we force initialize non-moose base classes -# nearest to Catalyst::Controller first, no matter what order stuff happens -# to be loaded. There are TODO tests in Moose for this, see -# f2391d17574eff81d911b97be15ea51080500003 -sub _controller_init_base_classes { - my ($app_class, $component) = @_; - return unless $component->isa('Catalyst::Controller'); - foreach my $class ( reverse @{ mro::get_linear_isa($component) } ) { - Moose::Meta::Class->initialize( $class ) - unless find_meta($class); - } -} - sub setup_component { my( $class, $component ) = @_; @@ -2390,114 +2593,152 @@ Sets up engine. =cut +sub engine_class { + my $class = shift; + $class->engine_loader->catalyst_engine_class(@_); +} + sub setup_engine { - my ( $class, $engine ) = @_; + my ($class) = @_; - if ($engine) { - $engine = 'Catalyst::Engine::' . $engine; - } + $class->engine_loader(Catalyst::EngineLoader->new(application_name => $class)); + + my $engine = $class->engine_class; + Class::MOP::load_class($engine); - if ( my $env = Catalyst::Utils::env_value( $class, 'ENGINE' ) ) { - $engine = 'Catalyst::Engine::' . $env; + if ($ENV{MOD_PERL}) { + my $apache = $class->engine_loader->auto; + # FIXME - Immutable + $class->meta->add_method(handler => sub { + my $r = shift; + my $psgi_app = $class->psgi_app; + $apache->call_app($r, $psgi_app); + }); } - if ( $ENV{MOD_PERL} ) { - my $meta = Class::MOP::get_metaclass_by_name($class); + $class->engine( $engine->new ); - # create the apache method - $meta->add_method('apache' => sub { shift->engine->apache }); + return; +} - my ( $software, $version ) = - $ENV{MOD_PERL} =~ /^(\S+)\/(\d+(?:[\.\_]\d+)+)/; +sub _finalized_psgi_app { + my ($app) = @_; - $version =~ s/_//g; - $version =~ s/(\.[^.]+)\./$1/g; + unless ($app->_psgi_app) { + my $psgi_app = $app->_setup_psgi_app; + $app->_psgi_app($psgi_app); + } - if ( $software eq 'mod_perl' ) { + return $app->_psgi_app; +} - if ( !$engine ) { +sub _setup_psgi_app { + my ($app) = @_; - if ( $version >= 1.99922 ) { - $engine = 'Catalyst::Engine::Apache2::MP20'; - } + if (my $home = Path::Class::Dir->new($app->config->{home})) { + my $psgi_file = $home->file( + Catalyst::Utils::appprefix($app) . '.psgi', + ); - elsif ( $version >= 1.9901 ) { - $engine = 'Catalyst::Engine::Apache2::MP19'; - } + return Plack::Util::load_psgi($psgi_file) + if -e $psgi_file; + } - elsif ( $version >= 1.24 ) { - $engine = 'Catalyst::Engine::Apache::MP13'; - } + return $app->_wrapped_legacy_psgi_app($app->psgi_app); +} - else { - Catalyst::Exception->throw( message => - qq/Unsupported mod_perl version: $ENV{MOD_PERL}/ ); - } +# Note - this is for back compatibility. Catalyst should not know or care about +# how it's deployed. The recommended way of configuring this is now to +# use the ReverseProxy middleware yourself if you want it in a .psgi +# file. +sub _wrapped_legacy_psgi_app { + my ($app, $psgi_app) = @_; + + $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}; + }, + ); - } + # If we're running under Lighttpd, swap PATH_INFO and SCRIPT_NAME + # http://lists.scsys.co.uk/pipermail/catalyst/2006-June/008361.html + # Thanks to Mark Blythe for this fix + # + # Note that this has probably the same effect as + # Plack::Middleware::LighttpdScriptNameFix and we should switch to that if + # we can. + $psgi_app = Plack::Middleware::Conditional->wrap( + $psgi_app, + builder => sub { + my ($to_wrap) = @_; + return sub { + my ($env) = @_; + $env->{PATH_INFO} ||= delete $env->{SCRIPT_NAME}; + return $to_wrap->($env); + }; + }, + condition => sub { + my ($env) = @_; + my $server = $env->{SERVER_SOFTWARE}; + return unless $server; + return $server =~ /lighttpd/ ? 1 : 0; + }, + ); - # install the correct mod_perl handler - if ( $version >= 1.9901 ) { - *handler = sub : method { - shift->handle_request(@_); - }; - } - else { - *handler = sub ($$) { shift->handle_request(@_) }; - } + $psgi_app = Plack::Middleware::Conditional->wrap( + $psgi_app, + builder => sub { + my ($to_wrap) = @_; + return sub { + my ($env) = @_; - } + my @script_name = split(m!/!, $env->{PATH_INFO}); + my @path_translated = split(m!/|\\\\?!, $env->{PATH_TRANSLATED}); + my @path_info; - elsif ( $software eq 'Zeus-Perl' ) { - $engine = 'Catalyst::Engine::Zeus'; - } + while ($script_name[$#script_name] eq $path_translated[$#path_translated]) { + pop(@path_translated); + unshift(@path_info, pop(@script_name)); + } - else { - Catalyst::Exception->throw( - message => qq/Unsupported mod_perl: $ENV{MOD_PERL}/ ); - } - } + unshift(@path_info, '', ''); - unless ($engine) { - $engine = $class->engine_class; - } + $env->{PATH_INFO} = join('/', @path_info); + $env->{SCRIPT_NAME} = join('/', @script_name); - Class::MOP::load_class($engine); + return $to_wrap->($env); + }; + }, + condition => sub { + my ($env) = @_; + my $server = $env->{SERVER_SOFTWARE}; + return unless $server; + return $server =~ /IIS\/[6-9]\.[0-9]/ ? 1 : 0; + }, + ); - # 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; - } + return $psgi_app; +} - elsif ( $engine->isa('Catalyst::Engine::Server::Base') - && Catalyst::Engine::Server->VERSION le '0.02' ) - { - $old_engine = 1; - } +=head2 $c->psgi_app - elsif ($engine->isa('Catalyst::Engine::HTTP::POE') - && $engine->VERSION eq '0.01' ) - { - $old_engine = 1; - } +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. - elsif ($engine->isa('Catalyst::Engine::Zeus') - && $engine->VERSION eq '0.01' ) - { - $old_engine = 1; - } +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. - if ($old_engine) { - Catalyst::Exception->throw( message => - qq/Engine "$engine" is not supported by this version of Catalyst/ - ); - } +=cut - # engine instance - $class->engine( $engine->new ); +sub psgi_app { + my ($app) = @_; + return $app->engine->build_psgi_app($app); } =head2 $c->setup_home @@ -2621,13 +2862,8 @@ the plugin name does not begin with C. if $plugin->isa( 'Catalyst::Component' ); $proto->_plugins->{$plugin} = 1; unless ($instant) { - no strict 'refs'; - if ( my $meta = Class::MOP::get_metaclass_by_name($class) ) { - my @superclasses = ($plugin, $meta->superclasses ); - $meta->superclasses(@superclasses); - } else { - unshift @{"$class\::ISA"}, $plugin; - } + my $meta = Class::MOP::get_metaclass_by_name($class); + $meta->superclasses($plugin, $meta->superclasses); } return $class; } @@ -2636,22 +2872,29 @@ the plugin name does not begin with C. my ( $class, $plugins ) = @_; $class->_plugins( {} ) unless $class->_plugins; - $plugins ||= []; + $plugins = Data::OptList::mkopt($plugins || []); - my @plugins = Catalyst::Utils::resolve_namespace($class . '::Plugin', 'Catalyst::Plugin', @$plugins); + my @plugins = map { + [ Catalyst::Utils::resolve_namespace( + $class . '::Plugin', + 'Catalyst::Plugin', $_->[0] + ), + $_->[1], + ] + } @{ $plugins }; for my $plugin ( reverse @plugins ) { - Class::MOP::load_class($plugin); - my $meta = find_meta($plugin); + Class::MOP::load_class($plugin->[0], $plugin->[1]); + my $meta = find_meta($plugin->[0]); next if $meta && $meta->isa('Moose::Meta::Role'); - $class->_register_plugin($plugin); + $class->_register_plugin($plugin->[0]); } my @roles = - map { $_->name } - grep { $_ && blessed($_) && $_->isa('Moose::Meta::Role') } - map { find_meta($_) } + map { $_->[0]->name, $_->[1] } + grep { blessed($_->[0]) && $_->[0]->isa('Moose::Meta::Role') } + map { [find_meta($_->[0]), $_->[1]] } @plugins; Moose::Util::apply_all_roles( @@ -2665,15 +2908,24 @@ the plugin name does not begin with C. Returns an arrayref of the internal execution stack (actions that are currently executing). +=head2 $c->stats + +Returns the current timing statistics object. By default Catalyst uses +L, but can be set otherwise with +L<< stats_class|/"$c->stats_class" >>. + +Even if L<< -Stats|/"-Stats" >> is not enabled, the stats object is still +available. By enabling it with C< $c->stats->enabled(1) >, it can be used to +profile explicitly, although MyApp.pm still won't profile nor output anything +by itself. + =head2 $c->stats_class -Returns or sets the stats (timing statistics) class. +Returns or sets the stats (timing statistics) class. L is used by default. =head2 $c->use_stats -Returns 1 when stats collection is enabled. Stats collection is enabled -when the -Stats options is set, debug is on or when the _STATS -environment variable is set. +Returns 1 when L<< stats collection|/"-Stats" >> is enabled. Note that this is a static method, not an accessor and should be overridden by declaring C in your MyApp.pm, not by calling C<< $c->use_stats(1) >>. @@ -2771,6 +3023,12 @@ to be shown in hit debug tables in the test server. =item * +C - Controlls if the C or C environment +variable should be used for determining the request path. See L +for more information. + +=item * + C - See L. =back @@ -3000,8 +3258,12 @@ random: Roland Lammel Robert Sedlacek C<< >> +SpiceMan: Marcel Montes + sky: Arthur Bergman +szbalint: Balint Szilakszi + t0m: Tomas Doran Ulf Edvinsson @@ -3012,8 +3274,14 @@ Will Hawes C willert: Sebastian Willert +wreis: Wallace Reis + Yuval Kogman, C +rainboxx: Matthias Dietrich, C + +dd070: Dhaval Dhanani + =head1 LICENSE This library is free software. You can redistribute it and/or modify it under