use Plack::Middleware::IIS6ScriptNameFix;
use Plack::Middleware::IIS7KeepAliveFix;
use Plack::Middleware::LighttpdScriptNameFix;
+use Plack::Util;
BEGIN { require 5.008003; }
__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');
# Remember to update this in Catalyst::Runtime as well!
-our $VERSION = '5.90020';
+our $VERSION = '5.90049_001';
sub import {
my ( $class, @arguments ) = @_;
If none of these are set, Catalyst will attempt to automatically detect the
home directory. If you are working in a development environment, Catalyst
-will try and find the directory containing either Makefile.PL, Build.PL or
-dist.ini. If the application has been installed into the system (i.e.
-you have done C<make install>), then Catalyst will use the path to your
+will try and find the directory containing either Makefile.PL, Build.PL,
+dist.ini, or cpanfile. If the application has been installed into the system
+(i.e. you have done C<make install>), then Catalyst will use the path to your
application module, without the .pm extension (e.g., /foo/MyApp if your
application was installed at /foo/MyApp.pm)
$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};
# join args with '/', or a blank string
my $args = join('/', grep { defined($_) } @args);
$args =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE
- $args =~ s!^/+!!i;
+ $args =~ s!^/+!!;
my ($base, $class) = ('/', 'URI::_generic');
if(blessed($c)) {
$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') ) {
$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" );
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) = @_;
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) = @_;
sub psgi_app {
my ($app) = @_;
- return $app->engine->build_psgi_app($app);
+ my $psgi = $app->engine->build_psgi_app($app);
+ return $app->Catalyst::Utils::apply_registered_middleware($psgi);
}
=head2 $c->setup_home
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 {
$class => @roles
) if @roles;
}
+}
+
+=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<Plack::Middleware> or of a
+compatible interface, or a coderef, which is assumed to be inlined 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
+under L</CONFIGURATION> information regarding L</psgi_middleware> and how to
+use it to enable L<Plack::Middleware>
+
+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<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.
+
+=cut
+
+sub registered_middlewares {
+ my $class = shift;
+ if(my $middleware = $class->_psgi_middleware) {
+ return @$middleware;
+ } else {
+ die "You cannot call ->registered_middlewares until middleware has been setup";
+ }
+}
+
+sub setup_middleware {
+ my ($class, @middleware_definitions) = @_;
+ push @middleware_definitions, reverse(
+ @{$class->config->{'psgi_middleware'}||[]});
+
+ my @middleware = ();
+ while(my $next = shift(@middleware_definitions)) {
+ if(ref $next) {
+ if(Scalar::Util::blessed $next && $next->can('wrap')) {
+ push @middleware, $next;
+ } elsif(ref $next eq 'CODE') {
+ push @middleware, $next;
+ } elsif(ref $next eq 'HASH') {
+ my $namespace = shift @middleware_definitions;
+ my $mw = $class->Catalyst::Utils::build_middleware($namespace, %$next);
+ push @middleware, $mw;
+ } else {
+ die "I can't handle middleware definition ${\ref $next}";
+ }
+ } else {
+ my $mw = $class->Catalyst::Utils::build_middleware($next);
+ push @middleware, $mw;
+ }
+ }
+
+ $class->_psgi_middleware(\@middleware);
}
=head2 $c->stack
at other URIs than that which the app is 'normally' based at with C<mod_rewrite>), the resolution of
C<< $c->request->base >> will be incorrect.
-=back
+=back
=item *
C<using_frontend_proxy> - See L</PROXY SUPPORT>.
+=item *
+
+C<encoding> - See L</ENCODING>
+
+=item *
+
+C<abort_chain_on_error_fix>
+
+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<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
+logical characters. On response, encodes body into encoding.
+
+=head2 Methods
+
+=over 4
+
+=item encoding
+
+Returns an instance of an C<Encode> 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<Encode>.
+
+=item encoding_step
+
+What type of data was being decoded. Valid values are (currently)
+C<params> - for request parameters / arguments / captures
+and C<uploads> - for request upload filenames.
+
+=back
+
+=back
+
=head1 SUPPORT
IRC:
miyagawa: Tatsuhiko Miyagawa <miyagawa@bulknews.net>
+mgrimes: Mark Grimes <mgrimes@cpan.org>
+
mst: Matt S. Trout <mst@shadowcatsystems.co.uk>
mugwump: Sam Vilain
willert: Sebastian Willert <willert@cpan.org>
-wreis: Wallace Reis <wallace@reis.org.br>
+wreis: Wallace Reis <wreis@cpan.org>
Yuval Kogman, C<nothingmuch@woobling.org>