X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst.pm;h=1c7d830741343630b1f916d841488d36363b44d8;hp=ed77ef8b13c2b392e87cce5edf063ee1ff6e2957;hb=dd5b1dc47018c241cafda7f2b565d6a39257a1bf;hpb=aee7cdcc0b2b3b7dc672b4b4a31b8c3b7ef3f1f7 diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index ed77ef8..1c7d830 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -4,7 +4,7 @@ use Moose; use Moose::Meta::Class (); extends 'Catalyst::Component'; use Moose::Util qw/find_meta/; -use B::Hooks::EndOfScope (); +use namespace::clean -except => 'meta'; use Catalyst::Exception; use Catalyst::Exception::Detach; use Catalyst::Exception::Go; @@ -23,6 +23,7 @@ use Path::Class::File (); 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; @@ -33,12 +34,16 @@ use Catalyst::EngineLoader; use utf8; use Carp qw/croak carp shortmess/; use Try::Tiny; +use Safe::Isa; +use Moose::Util 'find_meta'; use Plack::Middleware::Conditional; use Plack::Middleware::ReverseProxy; use Plack::Middleware::IIS6ScriptNameFix; +use Plack::Middleware::IIS7KeepAliveFix; use Plack::Middleware::LighttpdScriptNameFix; +use Class::Load 'load_class'; -BEGIN { require 5.008004; } +BEGIN { require 5.008003; } has stack => (is => 'ro', default => sub { [] }); has stash => (is => 'rw', default => sub { {} }); @@ -46,8 +51,34 @@ has state => (is => 'rw', default => 0); has stats => (is => 'rw'); has action => (is => 'rw'); has counter => (is => 'rw', default => sub { {} }); -has request => (is => 'rw', default => sub { $_[0]->request_class->new({}) }, required => 1, lazy => 1); -has response => (is => 'rw', default => sub { $_[0]->response_class->new({}) }, required => 1, lazy => 1); +has request => ( + is => 'rw', + default => sub { + my $self = shift; + $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($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 || [] }; } @@ -84,7 +115,7 @@ __PACKAGE__->stats_class('Catalyst::Stats'); # Remember to update this in Catalyst::Runtime as well! -our $VERSION = '5.90005'; +our $VERSION = '5.90042'; sub import { my ( $class, @arguments ) = @_; @@ -119,6 +150,8 @@ sub import { sub _application { $_[0] } +=encoding UTF-8 + =head1 NAME Catalyst - The Elegant MVC Web Application Framework @@ -236,9 +269,9 @@ MYAPP_WEB_HOME. If both variables are set, the MYAPP_HOME one will be used. 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), 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), 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) @@ -291,7 +324,18 @@ cookies, HTTP headers, etc.). See L. =head2 $c->forward( $class, $method, [, \@arguments ] ) -Forwards processing to another action, by its private name. If you give a +This is one way of calling another action (method) in the same or +a different controller. You can also use C<< $self->my_method($c, @args) >> +in the same controller or C<< $c->controller('MyController')->my_method($c, @args) >> +in a different controller. +The main difference is that 'forward' uses some of the Catalyst request +cycle overhead, including debugging, which may be useful to you. On the +other hand, there are some complications to using 'forward', restrictions +on values returned from 'forward', and it may not handle errors as you prefer. +Whether you use 'forward' or not is up to you; it is not considered superior to +the other ways to call a method. + +'forward' calls another action, by its private name. If you give a class name but no method, C is called. You may also optionally pass arguments in an arrayref. The action will receive the arguments in C<@_> and C<< $c->req->args >>. Upon returning from the function, @@ -356,8 +400,12 @@ When called with no arguments it escapes the processing chain entirely. sub detach { my $c = shift; $c->dispatcher->detach( $c, @_ ) } +=head2 $c->visit( $action [, \@arguments ] ) + =head2 $c->visit( $action [, \@captures, \@arguments ] ) +=head2 $c->visit( $class, $method, [, \@arguments ] ) + =head2 $c->visit( $class, $method, [, \@captures, \@arguments ] ) Almost the same as L<< forward|/"$c->forward( $action [, \@arguments ] )" >>, @@ -386,8 +434,12 @@ transfer control to another action as if it had been reached directly from a URL sub visit { my $c = shift; $c->dispatcher->visit( $c, @_ ) } +=head2 $c->go( $action [, \@arguments ] ) + =head2 $c->go( $action [, \@captures, \@arguments ] ) +=head2 $c->go( $class, $method, [, \@arguments ] ) + =head2 $c->go( $class, $method, [, \@captures, \@arguments ] ) The relationship between C and @@ -520,13 +572,13 @@ sub _comp_names_search_prefixes { # 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 @@ -617,7 +669,7 @@ sub controller { 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}; @@ -655,7 +707,7 @@ sub model { 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}; @@ -714,7 +766,7 @@ sub view { 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} ) { @@ -1160,29 +1212,6 @@ EOF $class->log->info("$name powered by Catalyst $Catalyst::VERSION"); } - # Make sure that the application class becomes immutable at this point, - B::Hooks::EndOfScope::on_scope_end { - return if $@; - my $meta = Class::MOP::get_metaclass_by_name($class); - if ( - $meta->is_immutable - && ! { $meta->immutable_options }->{replace_constructor} - && ( - $class->isa('Class::Accessor::Fast') - || $class->isa('Class::Accessor') - ) - ) { - warn "You made your application class ($class) immutable, " - . "but did not inline the\nconstructor. " - . "This will break catalyst, as your app \@ISA " - . "Class::Accessor(::Fast)?\nPlease pass " - . "(replace_constructor => 1)\nwhen making your class immutable.\n"; - } - $meta->make_immutable( - replace_constructor => 1, - ) unless $meta->is_immutable; - }; - if ($class->config->{case_sensitive}) { $class->log->warn($class . "->config->{case_sensitive} is set."); $class->log->warn("This setting is deprecated and planned to be removed in Catalyst 5.81."); @@ -1265,7 +1294,7 @@ path, use C<< $c->uri_for_action >> instead. 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 .= '/'; @@ -1282,7 +1311,7 @@ sub uri_for { $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' @@ -1326,9 +1355,13 @@ sub uri_for { 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{(?req->base; + $class = ref($base); + $base =~ s{(?

If you want to jump right into web development with Catalyst you might want to start with a tutorial.

-
perldoc Catalyst::Manual::Tutorial
+
perldoc Catalyst::Manual::Tutorial
 

Afterwards you can go on to check out a more complete look at our features.

-perldoc Catalyst::Manual::Intro
+perldoc Catalyst::Manual::Intro
 
 

What to do next?

Next it's time to write an actual application. Use the - helper scripts to generate controllers, - models, and - views; + helper scripts to generate controllers, + models, and + views; they can save you a lot of work.

script/${prefix}_create.pl --help

Also, be sure to check out the vast and growing @@ -1576,7 +1609,7 @@ EOF =head2 run_options Contains a hash of options passed from the application script, including -the original ARGV the script receieved, the processed values from that +the original ARGV the script received, the processed values from that ARGV and any extra arguments to the script which were not processed. This can be used to add custom options to your application's scripts @@ -1773,6 +1806,14 @@ sub finalize { $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') ) { @@ -1787,7 +1828,7 @@ sub finalize { $c->finalize_error; } - $c->finalize_headers; + $c->finalize_headers unless $c->response->finalized_headers; # HEAD request if ( $c->request->method eq 'HEAD' ) { @@ -1800,7 +1841,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" ); @@ -1854,6 +1895,7 @@ sub finalize_headers { if ( !$response->has_body ) { # Add a default body if none is already present + my $encoded_location = encode_entities($location); $response->body(<<"EOF"); @@ -1861,7 +1903,7 @@ sub finalize_headers { Moved -

This item has moved here.

+

This item has moved here.

EOF @@ -1897,7 +1939,7 @@ EOF $c->finalize_cookies; - $c->engine->finalize_headers( $c, @_ ); + $c->response->finalize_headers(); # Done $response->finalized_headers(1); @@ -1983,6 +2025,11 @@ etc.). =cut +has _uploadtmp => ( + is => 'ro', + predicate => '_has_uploadtmp', +); + sub prepare { my ( $class, @arguments ) = @_; @@ -1991,10 +2038,9 @@ sub prepare { # into the application. $class->context_class( ref $class || $class ) unless $class->context_class; - my $c = $class->context_class->new({}); + my $uploadtmp = $class->config->{uploadtmp}; + my $c = $class->context_class->new({ $uploadtmp ? (_uploadtmp => $uploadtmp) : ()}); - # For on-demand data - $c->request->_context($c); $c->response->_context($c); #surely this is not the most efficient way to do things... @@ -2012,8 +2058,8 @@ sub prepare { $c->prepare_request(@arguments); $c->prepare_connection; $c->prepare_query_parameters; - $c->prepare_headers; - $c->prepare_cookies; + $c->prepare_headers; # Just hooks, no longer needed - they just + $c->prepare_cookies; # cause the lazy attribute on req to build $c->prepare_path; # Prepare the body for reading, either by prepare_body @@ -2025,6 +2071,7 @@ sub prepare { $c->prepare_body; } } + $c->prepare_action; } # VERY ugly and probably shouldn't rely on ->finalize actually working catch { @@ -2032,19 +2079,19 @@ sub prepare { $c->response->status(400); $c->response->content_type('text/plain'); $c->response->body('Bad Request'); + # Note we call finalize and then die here, which escapes + # finalize being called in the enclosing block.. + # It in fact couldn't be called, as we don't return $c.. + # This is a mess - but I'm unsure you can fix this without + # breaking compat for people doing crazy things (we should set + # the 400 and just return the ctx here IMO, letting finalize get called + # above... $c->finalize; die $_; }; - my $method = $c->req->method || ''; - my $path = $c->req->path; - $path = '/' unless length $path; - my $address = $c->req->address || ''; - $c->log_request; - $c->prepare_action; - return $c; } @@ -2105,24 +2152,28 @@ Prepares connection. sub prepare_connection { my $c = shift; - $c->engine->prepare_connection( $c, @_ ); + # XXX - This is called on the engine (not the request) to maintain + # Engine::PSGI back compat. + $c->engine->prepare_connection($c); } =head2 $c->prepare_cookies -Prepares cookies. +Prepares cookies by ensuring that the attribute on the request +object has been built. =cut -sub prepare_cookies { my $c = shift; $c->engine->prepare_cookies( $c, @_ ) } +sub prepare_cookies { my $c = shift; $c->request->cookies } =head2 $c->prepare_headers -Prepares headers. +Prepares request headers by ensuring that the attribute on the request +object has been built. =cut -sub prepare_headers { my $c = shift; $c->engine->prepare_headers( $c, @_ ) } +sub prepare_headers { my $c = shift; $c->request->headers } =head2 $c->prepare_parameters @@ -2405,7 +2456,7 @@ $c->request. You must handle all body parsing yourself. =cut -sub read { my $c = shift; return $c->engine->read( $c, @_ ) } +sub read { my $c = shift; return $c->request->read( @_ ) } =head2 $c->run @@ -2415,11 +2466,35 @@ Starts the engine. sub run { my $app = shift; + $app->_make_immutable_if_needed; $app->engine_loader->needs_psgi_engine_compat_hack ? $app->engine->run($app, @_) : $app->engine->run( $app, $app->_finalized_psgi_app, @_ ); } +sub _make_immutable_if_needed { + my $class = shift; + my $meta = find_meta($class); + my $isa_ca = $class->isa('Class::Accessor::Fast') || $class->isa('Class::Accessor'); + if ( + $meta->is_immutable + && ! { $meta->immutable_options }->{replace_constructor} + && $isa_ca + ) { + warn("You made your application class ($class) immutable, " + . "but did not inline the\nconstructor. " + . "This will break catalyst, as your app \@ISA " + . "Class::Accessor(::Fast)?\nPlease pass " + . "(replace_constructor => 1)\nwhen making your class immutable.\n"); + } + unless ($meta->is_immutable) { + # XXX - FIXME warning here as you should make your app immutable yourself. + $meta->make_immutable( + replace_constructor => 1, + ); + } +} + =head2 $c->set_action( $action, $code, $namespace, $attrs ) Sets an action in a given namespace. @@ -2590,7 +2665,7 @@ sub setup_dispatcher { $dispatcher = $class->dispatcher_class; } - Class::MOP::load_class($dispatcher); + load_class($dispatcher); # dispatcher instance $class->dispatcher( $dispatcher->new ); @@ -2640,7 +2715,7 @@ sub setup_engine { # Don't really setup_engine -- see _setup_psgi_app for explanation. return if $class->loading_psgi_file; - Class::MOP::load_class($engine); + load_class($engine); if ($ENV{MOD_PERL}) { my $apache = $class->engine_loader->auto; @@ -2652,7 +2727,7 @@ sub setup_engine { $meta->add_method(handler => sub { my $r = shift; - my $psgi_app = $class->psgi_app; + my $psgi_app = $class->_finalized_psgi_app; $apache->call_app($r, $psgi_app); }); @@ -2743,13 +2818,32 @@ sub apply_default_middlewares { # If we're running under Lighttpd, swap PATH_INFO and SCRIPT_NAME # http://lists.scsys.co.uk/pipermail/catalyst/2006-June/008361.html - $psgi_app = Plack::Middleware::LighttpdScriptNameFix->wrap($psgi_app); + $psgi_app = Plack::Middleware::Conditional->wrap( + $psgi_app, + builder => sub { Plack::Middleware::LighttpdScriptNameFix->wrap($_[0]) }, + condition => sub { + my ($env) = @_; + return unless $env->{SERVER_SOFTWARE} && $env->{SERVER_SOFTWARE} =~ m!lighttpd[-/]1\.(\d+\.\d+)!; + return unless $1 < 4.23; + 1; + }, + ); # we're applying this unconditionally as the middleware itself already makes # sure it doesn't fuck things up if it's not running under one of the right # 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; } @@ -2885,21 +2979,42 @@ the plugin name does not begin with C. my ( $proto, $plugin, $instant ) = @_; my $class = ref $proto || $proto; - Class::MOP::load_class( $plugin ); + load_class( $plugin ); $class->log->warn( "$plugin inherits from 'Catalyst::Component' - this is deprecated and will not work in 5.81" ) if $plugin->isa( 'Catalyst::Component' ); - $proto->_plugins->{$plugin} = 1; - unless ($instant) { + my $plugin_meta = Moose::Meta::Class->create($plugin); + if (!$plugin_meta->has_method('new') + && ( $plugin->isa('Class::Accessor::Fast') || $plugin->isa('Class::Accessor') ) ) { + $plugin_meta->add_method('new', Moose::Object->meta->get_method('new')) + } + if (!$instant && !$proto->_plugins->{$plugin}) { my $meta = Class::MOP::get_metaclass_by_name($class); $meta->superclasses($plugin, $meta->superclasses); } + $proto->_plugins->{$plugin} = 1; 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 { @@ -2912,7 +3027,7 @@ the plugin name does not begin with C. } @{ $plugins }; for my $plugin ( reverse @plugins ) { - Class::MOP::load_class($plugin->[0], $plugin->[1]); + load_class($plugin->[0], $plugin->[1]); my $meta = find_meta($plugin->[0]); next if $meta && $meta->isa('Moose::Meta::Role'); @@ -2974,10 +3089,10 @@ your output data, if known. sub write { my $c = shift; - # Finalize headers if someone manually writes output + # Finalize headers if someone manually writes output (for compat) $c->finalize_headers; - return $c->engine->write( $c, @_ ); + return $c->response->write( @_ ); } =head2 version @@ -3091,12 +3206,33 @@ 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. + =back =head1 INTERNAL ACTIONS @@ -3157,7 +3293,26 @@ headers. If you do not wish to use the proxy support at all, you may set: - MyApp->config(ignore_frontend_proxy => 1); + MyApp->config(ignore_frontend_proxy => 0); + +=head2 Note about psgi files + +Note that if you supply your own .psgi file, calling +C<< MyApp->psgi_app(@_); >>, then B. + +You either need to apply L yourself +in your psgi, for example: + + builder { + enable "Plack::Middleware::ReverseProxy"; + MyApp->psgi_app + }; + +This will unconditionally add the ReverseProxy support, or you need to call +C<< $app = MyApp->apply_default_middlewares($app) >> (to conditionally +apply the support depending upon your config). + +See L for more information. =head1 THREAD SAFETY @@ -3169,6 +3324,53 @@ 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 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: @@ -3206,8 +3408,6 @@ Wiki: =head2 L - The test suite. -=begin stopwords - =head1 PROJECT FOUNDER sri: Sebastian Riedel @@ -3298,6 +3498,8 @@ marcus: Marcus Ramberg miyagawa: Tatsuhiko Miyagawa +mgrimes: Mark Grimes + mst: Matt S. Trout mugwump: Sam Vilain @@ -3342,7 +3544,7 @@ Will Hawes C willert: Sebastian Willert -wreis: Wallace Reis +wreis: Wallace Reis Yuval Kogman, C @@ -3350,8 +3552,6 @@ rainboxx: Matthias Dietrich, C dd070: Dhaval Dhanani -=end stopwords - =head1 COPYRIGHT Copyright (c) 2005, the above named PROJECT FOUNDER and CONTRIBUTORS.