__PACKAGE__->mk_classdata($_)
for qw/components arguments dispatcher engine log dispatcher_class
engine_loader context_class request_class response_class stats_class
- setup_finished _psgi_app loading_psgi_file run_options/;
+ setup_finished _psgi_app loading_psgi_file run_options _psgi_middleware/;
__PACKAGE__->dispatcher_class('Catalyst::Dispatcher');
__PACKAGE__->request_class('Catalyst::Request');
$class->setup_log( delete $flags->{log} );
$class->setup_plugins( delete $flags->{plugins} );
+ $class->setup_middleware();
$class->setup_dispatcher( delete $flags->{dispatcher} );
if (my $engine = delete $flags->{engine}) {
$class->log->warn("Specifying the engine in ->setup is no longer supported, see Catalyst::Upgrading");
$class->log->debug( "Loaded plugins:\n" . $t->draw . "\n" );
}
+ my @middleware = map { ref $_ eq 'CODE' ? "Inline Coderef" : (ref($_) .' '. $_->VERSION || '') }
+ $class->registered_middlewares;
+
+ if (@middleware) {
+ my $column_width = Catalyst::Utils::term_width() - 6;
+ my $t = Text::SimpleTable->new($column_width);
+ $t->row($_) for @middleware;
+ $class->log->debug( "Loaded PSGI Middleware:\n" . $t->draw . "\n" );
+ }
+
my $dispatcher = $class->dispatcher;
my $engine = $class->engine;
my $home = $class->config->{home};
sub psgi_app {
my ($app) = @_;
- return $app->engine->build_psgi_app($app);
+ my $psgi = $app->engine->build_psgi_app($app);
+ return $app->apply_registered_middleware($psgi);
}
=head2 $c->setup_home
$class => @roles
) if @roles;
}
-}
-
-has '_registered_middlewares' => (
- traits => ['Array'],
- is => 'bare',
- isa => 'ArrayRef[Object|CodeRef]',
- default => sub { [] },
- handles => {
- registered_middlewares => 'elements',
- _register_middleware => 'push',
- });
-
+}
-=head2 setup_middleware
+=head2 setup_middleware (?@middleware)
Read configuration information stored in configuration key 'psgi_middleware'
and invoke L</register_middleware> for each middleware prototype found. See
experience with L<Plack::Builder> or if you previously used the plugin
L<Catalyst::Plugin::EnableMiddleware> (which is now considered deprecated)
+You can pass middleware definitions to this as well, from the application
+class if you like.
+
=head2 register_middleware (@args)
Given @args that represent the definition of some L<Plack::Middleware> or
middleware with a compatible interface, register it with your L<Catalyst>
application.
-This is called by L</setup_middleware>. the behaior of invoking it yourself
-at runtime is currently undefined, and anything that works or doesn't work
+This is called by L</setup_middleware>. 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)
+=head2 _build_middleware (@args)
Internal application that converts a single middleware definition (see
L</psgi_middleware>) into an actual instance of middleware.
=cut
+sub registered_middlewares { @{shift->_psgi_middleware || []} }
+
sub setup_middleware {
- my ($self) = @_;
- my @middleware_definitions = reverse
- @{$self->config->{'psgi_middleware'}||[]};
+ my ($class, @middleware_definitions) = @_;
+ push @middleware_definitions, reverse(
+ @{$class->config->{'psgi_middleware'}||[]});
while(my $next = shift(@middleware_definitions)) {
if(ref $next) {
if(Scalar::Util::blessed $next && $next->can('wrap')) {
- $self->register_middleware($next);
+ $class->register_middleware($next);
} elsif(ref $next eq 'CODE') {
- $self->register_middleware($next);
+ $class->register_middleware($next);
} elsif(ref $next eq 'HASH') {
my $namespace = shift @middleware_definitions;
- $self->register_middleware($namespace, %$next);
+ my $mw = $class->_build_middleware($namespace, %$next);
+ $class->register_middleware($mw);
} else {
- $self->register_middleware($next);
+ die "I can't handle middleware definition ${\ref $next}";
}
+ } else {
+ my $mw = $class->_build_middleware($next);
+ $class->register_middleware($mw);
}
}
}
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);
- }
+ my ($class, @middleware) = @_;
+ if($class->_psgi_middleware) {
+ push @{$class->_psgi_middleware}, @middleware;
+ } else {
+ $class->_psgi_middleware(\@middleware);
+ }
+}
+
+sub _build_middleware {
+ my ($class, $namespace, @init_args) = @_;
+
+ if(
+ $namespace =~s/^\+// ||
+ $namespace =~/^Plack::Middleware/ ||
+ $namespace =~/^$class/
+ ) { ## the string is a full namespace
+ return Class::Load::try_load_class($namespace) ?
+ $namespace->new(@init_args) :
+ die "Can't load class $namespace";
+ } else { ## the string is a partial namespace
+ if(Class::Load::try_load_class("Plack::Middleware::$namespace")) { ## Act like Plack::Builder
+ return "Plack::Middleware::$namespace"->new(@init_args);
+ } elsif(Class::Load::try_load_class("$class::$namespace")) { ## Load Middleware from Project namespace
+ return "$class::$namespace"->new(@init_args);
}
}
- die "I don't know how to build $proto into valid Plack Middleware";
+ return; ## be sure we can count on a proper return when valid
}
sub apply_registered_middleware {
- my ($self, $psgi) = @_;
+ my ($class, $psgi) = @_;
my $new_psgi = $psgi;
- foreach my $middleware ($self->registered_middlewares) {
+ foreach my $middleware ($class->registered_middlewares) {
$new_psgi = Scalar::Util::blessed $middleware ?
$middleware->wrap($new_psgi) :
$middleware->($new_psgi);
In the future this might become the default behavior.
+=item *
+
+C<psgi_middleware> - See L<PSGI MIDDLEWARE>.
+
=back
=head1 INTERNAL ACTIONS
modules you are using must also be thread-safe. Some modules, most notably
L<DBD::SQLite>, are not thread-safe.
+=head1 PSGI MIDDLEWARE
+
+You can define middleware, defined as L<Plack::Middleware> or a compatible
+interface in configuration. Your middleware definitions are in the form of an
+arrayref under the configuration key C<psgi_middleware>. 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<Plack::Builder>:
+
+=over 4
+
+=item Middleware Object
+
+An already initialized object that conforms to the L<Plack::Middleware>
+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<Plack::Middleware> 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<Plack::Middleware> 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<HashRef> is used as arguments
+to initialize the middleware object.
+
+ __PACKAGE__->config(
+ 'psgi_middleware', [
+ 'Session' => {store => 'File'},
+ ]);
+
+=back
+
+Please see L<PSGI> for more on middleware.
+
=head1 ENCODING
On request, decodes all params from encoding into a sequence of