use URI ();
use URI::http;
use URI::https;
+use HTML::Entities;
use Tree::Simple qw/use_weak_refs/;
use Tree::Simple::Visitor::FindByUID;
use Class::C3::Adopt::NEXT;
use utf8;
use Carp qw/croak carp shortmess/;
use Try::Tiny;
+use Safe::Isa;
use Plack::Middleware::Conditional;
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; }
is => 'rw',
default => sub {
my $self = shift;
- my %p = ( _log => $self->log );
- $p{_uploadtmp} = $self->_uploadtmp if $self->_has_uploadtmp;
- $self->request_class->new(\%p);
+ $self->request_class->new($self->_build_request_constructor_args);
},
lazy => 1,
);
+sub _build_request_constructor_args {
+ my $self = shift;
+ my %p = ( _log => $self->log );
+ $p{_uploadtmp} = $self->_uploadtmp if $self->_has_uploadtmp;
+ \%p;
+}
+
has response => (
is => 'rw',
default => sub {
my $self = shift;
- $self->response_class->new({ _log => $self->log });
+ $self->response_class->new($self->_build_response_constructor_args);
},
lazy => 1,
);
+sub _build_response_constructor_args {
+ my $self = shift;
+ { _log => $self->log };
+}
+
has namespace => (is => 'rw');
sub depth { scalar @{ shift->stack || [] }; }
# Remember to update this in Catalyst::Runtime as well!
-our $VERSION = '5.90012';
+our $VERSION = '5.90042';
sub import {
my ( $class, @arguments ) = @_;
sub _application { $_[0] }
+=encoding UTF-8
+
=head1 NAME
Catalyst - The Elegant MVC Web Application Framework
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)
# undef for a name will return all
return keys %eligible if !defined $name;
- my $query = ref $name ? $name : qr/^$name$/i;
+ my $query = $name->$_isa('Regexp') ? $name : qr/^$name$/i;
my @result = grep { $eligible{$_} =~ m{$query} } keys %eligible;
return @result if @result;
# if we were given a regexp to search against, we're done.
- return if ref $name;
+ return if $name->$_isa('Regexp');
# skip regexp fallback if configured
return
my $appclass = ref($c) || $c;
if( $name ) {
- unless ( ref($name) ) { # Direct component hash lookup to avoid costly regexps
+ unless ( $name->$_isa('Regexp') ) { # Direct component hash lookup to avoid costly regexps
my $comps = $c->components;
my $check = $appclass."::Controller::".$name;
return $c->_filter_component( $comps->{$check}, @args ) if exists $comps->{$check};
my ( $c, $name, @args ) = @_;
my $appclass = ref($c) || $c;
if( $name ) {
- unless ( ref($name) ) { # Direct component hash lookup to avoid costly regexps
+ unless ( $name->$_isa('Regexp') ) { # Direct component hash lookup to avoid costly regexps
my $comps = $c->components;
my $check = $appclass."::Model::".$name;
return $c->_filter_component( $comps->{$check}, @args ) if exists $comps->{$check};
my $appclass = ref($c) || $c;
if( $name ) {
- unless ( ref($name) ) { # Direct component hash lookup to avoid costly regexps
+ unless ( $name->$_isa('Regexp') ) { # Direct component hash lookup to avoid costly regexps
my $comps = $c->components;
my $check = $appclass."::View::".$name;
if( exists $comps->{$check} ) {
sub uri_for {
my ( $c, $path, @args ) = @_;
- if (blessed($path) && $path->isa('Catalyst::Controller')) {
+ if ( $path->$_isa('Catalyst::Controller') ) {
$path = $path->path_prefix;
$path =~ s{/+\z}{};
$path .= '/';
$arg =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
}
- if ( blessed($path) ) { # action object
+ if ( $path->$_isa('Catalyst::Action') ) { # action object
s|/|%2F|g for @args;
my $captures = [ map { s|/|%2F|g; $_; }
( scalar @args && ref $args[0] eq 'ARRAY'
my $args = join('/', grep { defined($_) } @args);
$args =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE
$args =~ s!^/+!!;
- my $base = $c->req->base;
- my $class = ref($base);
- $base =~ s{(?<!/)$}{/};
+
+ my ($base, $class) = ('/', 'URI::_generic');
+ if(blessed($c)) {
+ $base = $c->req->base;
+ $class = ref($base);
+ $base =~ s{(?<!/)$}{/};
+ }
my $query = '';
$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->finalize_error;
}
- $c->finalize_headers;
+ $c->finalize_headers unless $c->response->finalized_headers;
# HEAD request
if ( $c->request->method eq 'HEAD' ) {
$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" );
if ( !$response->has_body ) {
# Add a default body if none is already present
+ my $encoded_location = encode_entities($location);
$response->body(<<"EOF");
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<title>Moved</title>
</head>
<body>
- <p>This item has moved <a href="$location">here</a>.</p>
+ <p>This item has moved <a href="$encoded_location">here</a>.</p>
</body>
</html>
EOF
$c->finalize_cookies;
- $c->engine->finalize_headers( $c, @_ );
+ $c->response->finalize_headers();
# Done
$response->finalized_headers(1);
my $uploadtmp = $class->config->{uploadtmp};
my $c = $class->context_class->new({ $uploadtmp ? (_uploadtmp => $uploadtmp) : ()});
+ $c->response->_context($c);
+
#surely this is not the most efficient way to do things...
$c->stats($class->stats_class->new)->enable($c->use_stats);
if ( $c->debug || $c->config->{enable_catalyst_header} ) {
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) = @_;
# IIS versions
$psgi_app = Plack::Middleware::IIS6ScriptNameFix->wrap($psgi_app);
+ # And another IIS issue, this time with IIS7.
+ $psgi_app = Plack::Middleware::Conditional->wrap(
+ $psgi_app,
+ builder => sub { Plack::Middleware::IIS7KeepAliveFix->wrap($_[0]) },
+ condition => sub {
+ my ($env) = @_;
+ return $env->{SERVER_SOFTWARE} && $env->{SERVER_SOFTWARE} =~ m!IIS/7\.[0-9]!;
+ },
+ );
+
return $psgi_app;
}
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 {
}
}
+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</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)
+
+=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
+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</psgi_middleware>) 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<Plack::Middleware> 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<registered_middlewares> 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
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.
+
=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 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>