X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FCatalyst.pm;h=9ac29efee55d98632df50e762e11dc53d2e103b1;hb=551068cfd29b252658de18655ac4bc193ccd5b56;hp=0dd64a05c4aa3b998215b6a739aba17b9cd41d02;hpb=1d4df70b12006c9803f80e46d435a1f2610b75f1;p=catagits%2FCatalyst-Runtime.git diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index 0dd64a0..9ac29ef 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -40,6 +40,8 @@ use Plack::Middleware::ReverseProxy; use Plack::Middleware::IIS6ScriptNameFix; use Plack::Middleware::IIS7KeepAliveFix; use Plack::Middleware::LighttpdScriptNameFix; +use Plack::Util; +use Class::Load; BEGIN { require 5.008003; } @@ -113,7 +115,7 @@ __PACKAGE__->stats_class('Catalyst::Stats'); # Remember to update this in Catalyst::Runtime as well! -our $VERSION = '5.90030'; +our $VERSION = '5.90042'; sub import { my ( $class, @arguments ) = @_; @@ -1793,6 +1795,14 @@ sub finalize { $c->log->error($error); } + # Support skipping finalize for psgix.io style 'jailbreak'. Used to support + # stuff like cometd and websockets + + if($c->request->has_io_fh) { + $c->log_response; + return; + } + # Allow engine to handle finalize flow (for POE) my $engine = $c->engine; if ( my $code = $engine->can('finalize') ) { @@ -1820,7 +1830,7 @@ sub finalize { $c->log_response; if ($c->use_stats) { - my $elapsed = sprintf '%f', $c->stats->elapsed; + my $elapsed = $c->stats->elapsed; my $av = $elapsed == 0 ? '??' : sprintf '%.3f', 1 / $elapsed; $c->log->info( "Request took ${elapsed}s ($av/s)\n" . $c->stats->report . "\n" ); @@ -2718,6 +2728,11 @@ sub setup_engine { return; } +## This exists just to supply a prebuild psgi app for mod_perl and for the +## build in server support (back compat support for pre psgi port behavior). +## This is so that we don't build a new psgi app for each request when using +## the mod_perl handler or the built in servers (http and fcgi, etc). + sub _finalized_psgi_app { my ($app) = @_; @@ -2729,6 +2744,12 @@ sub _finalized_psgi_app { return $app->_psgi_app; } +## Look for a psgi file like 'myapp_web.psgi' (if the app is MyApp::Web) in the +## home directory and load that and return it (just assume it is doing the +## right thing :) ). If that does not exist, call $app->psgi_app, wrap that +## in default_middleware and return it ( this is for backward compatibility +## with pre psgi port behavior ). + sub _setup_psgi_app { my ($app) = @_; @@ -2983,13 +3004,17 @@ the plugin name does not begin with C. $plugins = [ grep { m/Unicode::Encoding/ ? do { $class->log->warn( - 'Unicode::Encoding plugin is now part of core,' + 'Unicode::Encoding plugin is auto-applied,' . ' please remove this from your appclass' + . ' and make sure to define "encoding" config' ); + unless (exists $class->config->{'encoding'}) { + $class->config->{'encoding'} = 'UTF-8'; + } () } : $_ } @$plugins ]; - unshift @$plugins, $class->_default_plugins; + push @$plugins, $class->_default_plugins; $plugins = Data::OptList::mkopt($plugins || []); my @plugins = map { @@ -3021,6 +3046,135 @@ the plugin name does not begin with C. } } +has '_registered_middlewares' => ( + traits => ['Array'], + is => 'bare', + isa => 'ArrayRef[Object|CodeRef]', + default => sub { [] }, + handles => { + registered_middlewares => 'elements', + _register_middleware => 'push', + }); + + +=head2 setup_middleware + +Read configuration information stored in configuration key 'psgi_middleware' +and invoke L for each middleware prototype found. See +under L information regarding L and how to +use it to enable L + +This method is automatically called during 'setup' of your application, so +you really don't need to invoke it. + +When we read middleware definitions from configuration, we reverse the list +which sounds odd but is likely how you expect it to work if you have prior +experience with L or if you previously used the plugin +L (which is now considered deprecated) + +=head2 register_middleware (@args) + +Given @args that represent the definition of some L or +middleware with a compatible interface, register it with your L +application. + +This is called by L. the behaior of invoking it yourself +at runtime is currently undefined, and anything that works or doesn't work +as a result of doing so is considered a side effect subject to change. + +=head2 _register_middleware (@args) + +Internal details of how registered middleware is stored by the application. + +=head2 _build_middleware_from_args (@args) + +Internal application that converts a single middleware definition (see +L) into an actual instance of middleware. + +=head2 registered_middlewares + +Read only accessor that returns an array of all the middleware in the order +that they were added (which is the REVERSE of the order they will be applied). + +The values returned will be either instances of L or of a +compatible interface, or a coderef, which is assumed to be inlined middleware + +=head2 apply_registered_middleware ($psgi) + +Given a $psgi reference, wrap all the L around it and +return the wrapped version. + +=cut + +sub setup_middleware { + my ($self) = @_; + my @middleware_definitions = reverse + @{$self->config->{'psgi_middleware'}||[]}; + + while(my $next = shift(@middleware_definitions)) { + if(ref $next) { + if(Scalar::Util::blessed $next && $next->can('wrap')) { + $self->register_middleware($next); + } elsif(ref $next eq 'CODE') { + $self->register_middleware($next); + } elsif(ref $next eq 'HASH') { + my $namespace = shift @middleware_definitions; + $self->register_middleware($namespace, %$next); + } else { + $self->register_middleware($next); + } + } + } +} + +sub register_middleware { + my($self, @args) = @_; + my $middleware = $self->_build_middleware_from_args(@args); + $self->_register_middleware($middleware); + return $middleware; +} + +sub _build_middleware_from_args { + my ($self, $proto, @args) = @_; + + if(ref $proto) { ## Either an object or coderef + if(Scalar::Util::blessed $proto && $proto->can('wrap')) { ## Its already an instance of middleware + return $proto; + } elsif(ref $proto eq 'CODE') { ## Its inlined coderef + return $proto; + } + } else { ## We assume its a string aiming to load Plack Middleware + my $class = ref($self) || $self; + if( + $proto =~s/^\+// || + $proto =~/^Plack::Middleware/ || + $proto =~/^$class/ + ) { ## the string is a full namespace + require "$proto"; + return $proto->new(@args); + } else { ## the string is a partial namespace + if(Class::Load::try_load_class("Plack::Middleware::$proto")) { ## Act like Plack::Builder + return "Plack::Middleware::$proto"->new(@args); + } elsif(Class::Load::try_load_class("$class::$proto")) { ## Load Middleware from Project namespace + return "$class::$proto"->new(@args); + } + } + } + + die "I don't know how to build $proto into valid Plack Middleware"; +} + +sub apply_registered_middleware { + my ($self, $psgi) = @_; + my $new_psgi = $psgi; + foreach my $middleware ($self->registered_middlewares) { + $new_psgi = Scalar::Util::blessed $middleware ? + $middleware->wrap($new_psgi) : + $middleware->($new_psgi); + } + return $new_psgi; +} + =head2 $c->stack Returns an arrayref of the internal execution stack (actions that are @@ -3181,7 +3335,7 @@ is having paths rewritten into it (e.g. as a .cgi/fcgi in a public_html director at other URIs than that which the app is 'normally' based at with C), the resolution of C<< $c->request->base >> will be incorrect. -=back +=back =item * @@ -3191,6 +3345,27 @@ C - See L. C - See L +=item * + +C + +When there is an error in an action chain, the default behavior is to continue +processing the remaining actions and then catch the error upon chain end. This +can lead to running actions when the application is in an unexpected state. If +you have this issue, setting this config value to true will promptly exit a +chain when there is an error raised in any action (thus terminating the chain +early.) + +use like: + + __PACKAGE__->config(abort_chain_on_error_fix => 1); + +In the future this might become the default behavior. + +=item * + +C - See L. + =back =head1 INTERNAL ACTIONS @@ -3282,6 +3457,131 @@ If you plan to operate in a threaded environment, remember that all other modules you are using must also be thread-safe. Some modules, most notably L, are not thread-safe. +=head1 PSGI MIDDLEWARE + +You can define middleware, defined as L or a compatible +interface in configuration. Your middleware definitions are in the form of an +arrayref under the configuration key C. Here's an example +with details to follow: + + package MyApp::Web; + + use Catalyst; + use Plack::Middleware::StackTrace; + + my $stacktrace_middleware = Plack::Middleware::StackTrace->new; + + __PACKAGE__->config( + 'psgi_middleware', [ + 'Debug', + '+MyApp::Custom', + $stacktrace_middleware, + 'Session' => {store => 'File'}, + sub { + my $app = shift; + return sub { + my $env = shift; + $env->{myapp.customkey} = 'helloworld'; + $app->($env); + }, + }, + ], + ); + + __PACKAGE__->setup; + +So the general form is: + + __PACKAGE__->config(psgi_middleware => \@middleware_definitions); + +Where C<@middleware> is one or more of the following, applied in the REVERSE of +the order listed (to make it function similarly to L: + +=over4 + +=item Middleware Object + +An already initialized object that conforms to the L +specification: + + my $stacktrace_middleware = Plack::Middleware::StackTrace->new; + + __PACKAGE__->config( + 'psgi_middleware', [ + $stacktrace_middleware, + ]); + + +=item coderef + +A coderef that is an inlined middleware: + + __PACKAGE__->config( + 'psgi_middleware', [ + sub { + my $app = shift; + return sub { + my $env = shift; + if($env->{PATH_INFO} =~m/forced/) { + Plack::App::File + ->new(file=>TestApp->path_to(qw/share static forced.txt/)) + ->call($env); + } else { + return $app->($env); + } + }, + }, + ]); + + + +=item a scalar + +We assume the scalar refers to a namespace after normalizing it using the +following rules: + +(1) If the scalar is prefixed with a "+" (as in C<+MyApp::Foo>) then the full string +is assumed to be 'as is', and we just install and use the middleware. + +(2) If the scalar begins with "Plack::Middleware" or your application namespace +(the package name of your Catalyst application subclass), we also assume then +that it is a full namespace, and use it. + +(3) Lastly, we then assume that the scalar is a partial namespace, and attempt to +resolve it first by looking for it under your application namespace (for example +if you application is "MyApp::Web" and the scalar is "MyMiddleware", we'd look +under "MyApp::Web::Middleware::MyMiddleware") and if we don't find it there, we +will then look under the regular L namespace (i.e. for the +previous we'd try "Plack::Middleware::MyMiddleware"). We look under your application +namespace first to let you 'override' common L locally, should +you find that a good idea. + +Examples: + + package MyApp::Web; + + __PACKAGE__->config( + 'psgi_middleware', [ + 'Debug', ## MyAppWeb::Middleware::Debug->wrap or Plack::Middleware::Debug->wrap + 'Plack::Middleware::Stacktrace', ## Plack::Middleware::Stacktrace->wrap + '+MyApp::Custom', ## MyApp::Custom->wrap + ], + ); + +=item a scalar followed by a hashref + +Just like the previous, except the following C is used as arguments +to initialize the middleware object. + + __PACKAGE__->config( + 'psgi_middleware', [ + 'Session' => {store => 'File'}, + ]); + +=back + +Please see L for more on middleware. + =head1 ENCODING On request, decodes all params from encoding into a sequence of