X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FCatalyst.pm;h=9ac29efee55d98632df50e762e11dc53d2e103b1;hb=551068cfd29b252658de18655ac4bc193ccd5b56;hp=a6cf346cbefadd7b7dbeea376dbf8077bedca091;hpb=c287088e5ca1df568e066f98efcd2788207c7192;p=catagits%2FCatalyst-Runtime.git diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index a6cf346..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.90041'; +our $VERSION = '5.90042'; sub import { my ( $class, @arguments ) = @_; @@ -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) = @_; @@ -3033,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 @@ -3220,6 +3362,10 @@ use like: In the future this might become the default behavior. +=item * + +C - See L. + =back =head1 INTERNAL ACTIONS @@ -3311,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