X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst.pm;h=7b6017e125eac011b7a99d5007749aa13662b66c;hp=55845cd2bd9206d9f29f58d63cbf4f625e86f893;hb=c17c004aa78ef1280963f76abff75224343d71a8;hpb=74bebe95e90dced3f66a19a9535a2cf19c3df0ff diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index 55845cd..7b6017e 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 ) = @_; @@ -1828,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" ); @@ -2726,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) = @_; @@ -2737,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) = @_; @@ -2982,10 +2995,26 @@ the plugin name does not begin with C. return $class; } + sub _default_plugins { return qw(Unicode::Encoding) } + sub setup_plugins { my ( $class, $plugins ) = @_; $class->_plugins( {} ) unless $class->_plugins; + $plugins = [ grep { + m/Unicode::Encoding/ ? do { + $class->log->warn( + '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 ]; + push @$plugins, $class->_default_plugins; $plugins = Data::OptList::mkopt($plugins || []); my @plugins = map { @@ -3017,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 behavior of invoking it yourself +at run time 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 @@ -3177,12 +3335,37 @@ 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 * C - See L. +=item * + +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 @@ -3274,6 +3457,178 @@ 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: + +=over 4 + +=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 +logical characters. On response, encodes body into encoding. + +=head2 Methods + +=over 4 + +=item encoding + +Returns an instance of an C encoding + + print $c->encoding->name + +=item handle_unicode_encoding_exception ($exception_context) + +Method called when decoding process for a request fails. + +An C<$exception_context> hashref is provided to allow you to override the +behaviour of your application when given data with incorrect encodings. + +The default method throws exceptions in the case of invalid request parameters +(resulting in a 500 error), but ignores errors in upload filenames. + +The keys passed in the C<$exception_context> hash are: + +=over + +=item param_value + +The value which was not able to be decoded. + +=item error_msg + +The exception received from L. + +=item encoding_step + +What type of data was being decoded. Valid values are (currently) +C - for request parameters / arguments / captures +and C - for request upload filenames. + +=back + +=back + =head1 SUPPORT IRC: @@ -3447,7 +3802,7 @@ Will Hawes C willert: Sebastian Willert -wreis: Wallace Reis +wreis: Wallace Reis Yuval Kogman, C