X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst.pm;h=a3d8f9e18e7ebf19351542768f01f389987d1183;hp=388b71c4a7d9e6da0a8915c3a64f0e68c53eb5b0;hb=942572d7cd0a89d54b12ddd89de16ddcd19ca077;hpb=c8f4781ebd33f0476ed38e1daf05b0fc04446637 diff --git a/lib/Catalyst.pm b/lib/Catalyst.pm index 388b71c..a3d8f9e 100644 --- a/lib/Catalyst.pm +++ b/lib/Catalyst.pm @@ -16,7 +16,6 @@ use Catalyst::Utils; use Catalyst::Controller; use Data::OptList; use Devel::InnerPackage (); -use File::stat; use Module::Pluggable::Object (); use Text::SimpleTable (); use Path::Class::Dir (); @@ -30,9 +29,14 @@ use Class::C3::Adopt::NEXT; use List::MoreUtils qw/uniq/; use attributes; use String::RewritePrefix; +use Catalyst::EngineLoader; use utf8; use Carp qw/croak carp shortmess/; use Try::Tiny; +use Plack::Middleware::Conditional; +use Plack::Middleware::ReverseProxy; +use Plack::Middleware::IIS6ScriptNameFix; +use Plack::Middleware::LighttpdScriptNameFix; BEGIN { require 5.008004; } @@ -70,18 +74,17 @@ our $GO = Catalyst::Exception::Go->new; #maybe we should just make them attributes with a default? __PACKAGE__->mk_classdata($_) for qw/components arguments dispatcher engine log dispatcher_class - engine_class context_class request_class response_class stats_class - setup_finished/; + engine_loader context_class request_class response_class stats_class + setup_finished _psgi_app loading_psgi_file/; __PACKAGE__->dispatcher_class('Catalyst::Dispatcher'); -__PACKAGE__->engine_class('Catalyst::Engine'); __PACKAGE__->request_class('Catalyst::Request'); __PACKAGE__->response_class('Catalyst::Response'); __PACKAGE__->stats_class('Catalyst::Stats'); # Remember to update this in Catalyst::Runtime as well! -our $VERSION = '5.80029'; +our $VERSION = '5.90005'; sub import { my ( $class, @arguments ) = @_; @@ -145,7 +148,7 @@ documentation and tutorials. use Catalyst qw/-Debug/; # include plugins here as well ### In lib/MyApp/Controller/Root.pm (autocreated) - sub foo : Global { # called for /foo, /foo/1, /foo/1/2, etc. + sub foo : Chained('/') Args() { # called for /foo, /foo/1, /foo/1/2, etc. my ( $self, $c, @args ) = @_; # args are qw/1 2/ for /foo/1/2 $c->stash->{template} = 'foo.tt'; # set the template # lookup something from db -- stash vars are passed to TT @@ -163,50 +166,16 @@ documentation and tutorials. [% END %] # called for /bar/of/soap, /bar/of/soap/10, etc. - sub bar : Path('/bar/of/soap') { ... } - - # called for all actions, from the top-most controller downwards - sub auto : Private { - my ( $self, $c ) = @_; - if ( !$c->user_exists ) { # Catalyst::Plugin::Authentication - $c->res->redirect( '/login' ); # require login - return 0; # abort request and go immediately to end() - } - return 1; # success; carry on to next action - } + sub bar : Chained('/') PathPart('/bar/of/soap') Args() { ... } # called after all actions are finished - sub end : Private { + sub end : Action { my ( $self, $c ) = @_; if ( scalar @{ $c->error } ) { ... } # handle errors return if $c->res->body; # already have a response $c->forward( 'MyApp::View::TT' ); # render template } - ### in MyApp/Controller/Foo.pm - # called for /foo/bar - sub bar : Local { ... } - - # called for /blargle - sub blargle : Global { ... } - - # an index action matches /foo, but not /foo/1, etc. - sub index : Private { ... } - - ### in MyApp/Controller/Foo/Bar.pm - # called for /foo/bar/baz - sub baz : Local { ... } - - # first Root auto is called, then Foo auto, then this - sub auto : Private { ... } - - # powerful regular expression paths are also possible - sub details : Regex('^product/(\w+)/details$') { - my ( $self, $c ) = @_; - # extract the (\w+) from the URI - my $product = $c->req->captures->[0]; - } - See L for additional information. =head1 DESCRIPTION @@ -233,7 +202,7 @@ fully qualify the name by using a unary plus: +Fully::Qualified::Plugin::Name /; -Special flags like C<-Debug> and C<-Engine> can also be specified as +Special flags like C<-Debug> can also be specified as arguments when Catalyst is loaded: use Catalyst qw/-Debug My::Module/; @@ -253,13 +222,6 @@ priority. This sets the log level to 'debug' and enables full debug output on the error screen. If you only want the latter, see L<< $c->debug >>. -=head2 -Engine - -Forces Catalyst to use a specific engine. Omit the -C prefix of the engine name, i.e.: - - use Catalyst qw/-Engine=CGI/; - =head2 -Home Forces Catalyst to use a specific home directory, e.g.: @@ -273,11 +235,11 @@ the name will be replaced with underscores, e.g. MyApp::Web should use 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 envirnoment, Catalyst +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 -application module, without the .pm extension (ie, /foo/MyApp if your +application module, without the .pm extension (e.g., /foo/MyApp if your application was installed at /foo/MyApp.pm) =head2 -Log @@ -345,9 +307,10 @@ call to forward. Note that L<< forward|/"$c->forward( $action [, \@arguments ] )" >> implies an C<< eval { } >> around the call (actually -L<< execute|/"$c->execute( $class, $coderef )" >> does), thus de-fatalizing -all 'dies' within the called action. If you want C to propagate you -need to do something like: +L<< execute|/"$c->execute( $class, $coderef )" >> does), thus rendering all +exceptions thrown by the called action non-fatal and pushing them onto +$c->error instead. If you want C to propagate you need to do something +like: $c->forward('foo'); die join "\n", @{ $c->error } if @{ $c->error }; @@ -409,7 +372,7 @@ L return information for the visited action when they are invoked within the visited action. This is different from the behavior of L<< forward|/"$c->forward( $action [, \@arguments ] )" >>, which continues to use the $c->action object from the caller action even when -invoked from the callee. +invoked from the called action. C<< $c->stash >> is kept unchanged. @@ -863,6 +826,9 @@ sub component { return $c->_filter_component( $comp, @args ) if $comp; } + return + if $c->config->{disable_component_resolution_regex_fallback}; + # This is here so $c->comp( '::M::' ) works my $query = ref $name ? $name : qr{$name}i; @@ -1029,26 +995,11 @@ sub path_to { else { return Path::Class::File->new( $c->config->{home}, @path ) } } -=head2 $c->plugin( $name, $class, @args ) - -Helper method for plugins. It creates a class data accessor/mutator and -loads and instantiates the given class. - - MyApp->plugin( 'prototype', 'HTML::Prototype' ); - - $c->prototype->define_javascript_functions; - -B This method of adding plugins is deprecated. The ability -to add plugins like this B in a Catalyst 5.81. -Please do not use this functionality in new code. - -=cut - sub plugin { my ( $class, $name, $plugin, @args ) = @_; # See block comment in t/unit_core_plugin.t - $class->log->warn(qq/Adding plugin using the ->plugin method is deprecated, and will be removed in Catalyst 5.81/); + $class->log->warn(qq/Adding plugin using the ->plugin method is deprecated, and will be removed in a future release/); $class->_register_plugin( $plugin, 1 ); @@ -1077,6 +1028,9 @@ Catalyst> line. MyApp->setup; MyApp->setup( qw/-Debug/ ); +B You B wrap this method with method modifiers +or bad things will happen - wrap the C method instead. + =cut sub setup { @@ -1116,7 +1070,10 @@ sub setup { $class->setup_log( delete $flags->{log} ); $class->setup_plugins( delete $flags->{plugins} ); $class->setup_dispatcher( delete $flags->{dispatcher} ); - $class->setup_engine( delete $flags->{engine} ); + if (my $engine = delete $flags->{engine}) { + $class->log->warn("Specifying the engine in ->setup is no longer supported, see Catalyst::Upgrading"); + } + $class->setup_engine(); $class->setup_stats( delete $flags->{stats} ); for my $flag ( sort keys %{$flags} ) { @@ -1243,7 +1200,7 @@ EOF A hook to attach modifiers to. This method does not do anything except set the C accessor. -Applying method modifiers to the C method doesn't work, because of quirky thingsdone for plugin setup. +Applying method modifiers to the C method doesn't work, because of quirky things done for plugin setup. Example: @@ -1338,7 +1295,15 @@ sub uri_for { } my $action = $path; - $path = $c->dispatcher->uri_for_action($action, $captures); + # ->uri_for( $action, \@captures_and_args, \%query_values? ) + if( !@args && $action->number_of_args ) { + my $expanded_action = $c->dispatcher->expand_action( $action ); + + my $num_captures = $expanded_action->number_of_captures; + unshift @args, splice @$captures, $num_captures; + } + + $path = $c->dispatcher->uri_for_action($action, $captures); if (not defined $path) { $c->log->debug(qq/Can't find uri_for action '$action' @$captures/) if $c->debug; @@ -1389,9 +1354,9 @@ sub uri_for { $res; } -=head2 $c->uri_for_action( $path, \@captures?, @args?, \%query_values? ) +=head2 $c->uri_for_action( $path, \@captures_and_args?, @args?, \%query_values? ) -=head2 $c->uri_for_action( $action, \@captures?, @args?, \%query_values? ) +=head2 $c->uri_for_action( $action, \@captures_and_args?, @args?, \%query_values? ) =over @@ -1420,6 +1385,31 @@ You can use: and it will create the URI /users/the-list. +=item \@captures_and_args? + +Optional array reference of Captures (i.e. C<req->captures>) +and arguments to the request. Usually used with L +to interpolate all the parameters in the URI. + +=item @args? + +Optional list of extra arguments - can be supplied in the +C<< \@captures_and_args? >> array ref, or here - whichever is easier for your +code. + +Your action can have zero, a fixed or a variable number of args (e.g. +C<< Args(1) >> for a fixed number or C<< Args() >> for a variable number).. + +=item \%query_values? + +Optional array reference of query parameters to append. E.g. + + { foo => 'bar' } + +will generate + + /rest/of/your/uri?foo=bar + =back =cut @@ -1662,7 +1652,9 @@ sub execute { push( @{ $c->stack }, $code ); no warnings 'recursion'; - eval { $c->state( $code->execute( $class, $c, @{ $c->req->args } ) || 0 ) }; + # N.B. This used to be combined, but I have seen $c get clobbered if so, and + # I have no idea how, ergo $ret (which appears to fix the issue) + eval { my $ret = $code->execute( $class, $c, @{ $c->req->args } ) || 0; $c->state( $ret ) }; $c->_stats_finish_execute( $stats_info ) if $c->use_stats and $stats_info; @@ -1684,8 +1676,8 @@ sub execute { $error = qq/Caught exception in $class->$name "$error"/; } $c->error($error); - $c->state(0); } + $c->state(0); } return $c->state; } @@ -1852,21 +1844,30 @@ sub finalize_headers { if ( !$response->has_body ) { # Add a default body if none is already present - $response->body( - qq{

This item has moved here.

} - ); + $response->body(<<"EOF"); + + + + Moved + + +

This item has moved here.

+ + +EOF + $response->content_type('text/html; charset=utf-8'); } } # Content-Length - if ( $response->body && !$response->content_length ) { + if ( defined $response->body && length $response->body && !$response->content_length ) { # get the length from a filehandle if ( blessed( $response->body ) && $response->body->can('read') || ref( $response->body ) eq 'GLOB' ) { - my $stat = stat $response->body; - if ( $stat && $stat->size > 0 ) { - $response->content_length( $stat->size ); + my $size = -s $response->body; + if ( $size ) { + $response->content_length( $size ); } else { $c->log->warn('Serving filehandle without a content-length'); @@ -1965,7 +1966,7 @@ sub handle_request { return $status; } -=head2 $c->prepare( @arguments ) +=head2 $class->prepare( @arguments ) Creates a Catalyst context from an engine-specific request (Apache, CGI, etc.). @@ -2238,7 +2239,7 @@ sub log_response_status_line { =head2 $c->log_response_headers($headers); -Hook method which can be wrapped by plugins to log the responseheaders. +Hook method which can be wrapped by plugins to log the response headers. No-op in the default implementation. =cut @@ -2373,11 +2374,11 @@ sub prepare_write { my $c = shift; $c->engine->prepare_write( $c, @_ ) } =head2 $c->request_class -Returns or sets the request class. +Returns or sets the request class. Defaults to L. =head2 $c->response_class -Returns or sets the response class. +Returns or sets the response class. Defaults to L. =head2 $c->read( [$maxlength] ) @@ -2402,7 +2403,12 @@ Starts the engine. =cut -sub run { my $c = shift; return $c->engine->run( $c, $c->psgi_app, @_ ) } +sub run { + my $app = shift; + $app->engine_loader->needs_psgi_engine_compat_hack ? + $app->engine->run($app, @_) : + $app->engine->run( $app, $app->_finalized_psgi_app, @_ ); +} =head2 $c->set_action( $action, $code, $namespace, $attrs ) @@ -2586,61 +2592,61 @@ Sets up engine. =cut -sub setup_engine { - my ($class, $engine) = @_; +sub engine_class { + my ($class, $requested_engine) = @_; - unless ($engine) { - $engine = $class->engine_class; - } - else { - $engine = String::RewritePrefix->rewrite( { '' => 'Catalyst::Engine::', '+' => '' }, $engine ); + if (!$class->engine_loader || $requested_engine) { + $class->engine_loader( + Catalyst::EngineLoader->new({ + application_name => $class, + (defined $requested_engine + ? (catalyst_engine_class => $requested_engine) : ()), + }), + ); } - $engine = 'Catalyst::Engine' if $engine eq 'Catalyst::Engine::HTTP'; + $class->engine_loader->catalyst_engine_class; +} - Class::MOP::load_class($engine); +sub setup_engine { + my ($class, $requested_engine) = @_; - # check for old engines that are no longer compatible - my $old_engine; - if ( $engine->isa('Catalyst::Engine::Apache') - && !Catalyst::Engine::Apache->VERSION ) - { - $old_engine = 1; - } + my $engine = do { + my $loader = $class->engine_loader; - elsif ( $engine->isa('Catalyst::Engine::Server::Base') - && Catalyst::Engine::Server->VERSION le '0.02' ) - { - $old_engine = 1; - } + if (!$loader || $requested_engine) { + $loader = Catalyst::EngineLoader->new({ + application_name => $class, + (defined $requested_engine + ? (requested_engine => $requested_engine) : ()), + }), - elsif ($engine->isa('Catalyst::Engine::HTTP::POE') - && $engine->VERSION eq '0.01' ) - { - $old_engine = 1; - } + $class->engine_loader($loader); + } - elsif ($engine->isa('Catalyst::Engine::Zeus') - && $engine->VERSION eq '0.01' ) - { - $old_engine = 1; - } + $loader->catalyst_engine_class; + }; - if ($old_engine) { - Catalyst::Exception->throw( message => - qq/Engine "$engine" is not supported by this version of Catalyst/ - ); - } + # Don't really setup_engine -- see _setup_psgi_app for explanation. + return if $class->loading_psgi_file; + + Class::MOP::load_class($engine); if ($ENV{MOD_PERL}) { - require 'Catalyst/Engine/Loader.pm'; - my $apache = Catalyst::Engine::Loader->auto; - # FIXME - Immutable - $class->meta->add_method(handler => sub { + my $apache = $class->engine_loader->auto; + + my $meta = find_meta($class); + my $was_immutable = $meta->is_immutable; + my %immutable_options = $meta->immutable_options; + $meta->make_mutable if $was_immutable; + + $meta->add_method(handler => sub { my $r = shift; - my $app = $class->psgi_app; - $apache->call_app($r, $app); + my $psgi_app = $class->psgi_app; + $apache->call_app($r, $psgi_app); }); + + $meta->make_immutable(%immutable_options) if $was_immutable; } $class->engine( $engine->new ); @@ -2648,38 +2654,74 @@ sub setup_engine { return; } -sub psgi_app { +sub _finalized_psgi_app { my ($app) = @_; - $app->setup_psgi_app; -} - -=head2 $c->setup_psgi_app - -Builds a PSGI application coderef for the catalyst application C<$c>. -If we're able to locate a C<${myapp}.psgi> file in the applications home -directory, we'll use that to obtain our code reference. - -Otherwise the raw psgi app, without any middlewares is created using -C and wrapped into L -conditionally. See L. + unless ($app->_psgi_app) { + my $psgi_app = $app->_setup_psgi_app; + $app->_psgi_app($psgi_app); + } -=cut + return $app->_psgi_app; +} -sub setup_psgi_app { +sub _setup_psgi_app { my ($app) = @_; - if (my $home = Path::Class::Dir->new($app->config->{home})) { + for my $home (Path::Class::Dir->new($app->config->{home})) { my $psgi_file = $home->file( Catalyst::Utils::appprefix($app) . '.psgi', ); - return Plack::Util::load_psgi($psgi_file) - if -e $psgi_file; + next unless -e $psgi_file; + + # If $psgi_file calls ->setup_engine, it's doing so to load + # Catalyst::Engine::PSGI. But if it does that, we're only going to + # throw away the loaded PSGI-app and load the 5.9 Catalyst::Engine + # anyway. So set a flag (ick) that tells setup_engine not to populate + # $c->engine or do any other things we might regret. + + $app->loading_psgi_file(1); + my $psgi_app = Plack::Util::load_psgi($psgi_file); + $app->loading_psgi_file(0); + + return $psgi_app + unless $app->engine_loader->needs_psgi_engine_compat_hack; + + warn <<"EOW"; +Found a legacy Catalyst::Engine::PSGI .psgi file at ${psgi_file}. + +Its content has been ignored. Please consult the Catalyst::Upgrading +documentation on how to upgrade from Catalyst::Engine::PSGI. +EOW } - return Plack::Middleware::Conditional->wrap( - $app->raw_psgi_app, + return $app->apply_default_middlewares($app->psgi_app); +} + +=head2 $c->apply_default_middlewares + +Adds the following L middlewares to your application, since they are +useful and commonly needed: + +L, (conditionally added based on the status +of your $ENV{REMOTE_ADDR}, and can be forced on with C +or forced off with C), L +(if you are using Lighttpd), L (always +applied since this middleware is smart enough to conditionally apply itself). + +Additionally if we detect we are using Nginx, we add a bit of custom middleware +to solve some problems with the way that server handles $ENV{PATH_INFO} and +$ENV{SCRIPT_NAME} + +=cut + + +sub apply_default_middlewares { + my ($app, $psgi_app) = @_; + + $psgi_app = Plack::Middleware::Conditional->wrap( + $psgi_app, builder => sub { Plack::Middleware::ReverseProxy->wrap($_[0]) }, condition => sub { my ($env) = @_; @@ -2688,18 +2730,31 @@ sub setup_psgi_app { || $app->config->{using_frontend_proxy}; }, ); + + # 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); + + # 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); + + return $psgi_app; } -=head2 $c->raw_psgi_app +=head2 $c->psgi_app Returns a PSGI application code reference for the catalyst application C<$c>. This is the bare application without any middlewares -applied. C<${myapp}.psgi> is not taken into account. See -Lsetup_psgi_app">. +applied. C<${myapp}.psgi> is not taken into account. + +This is what you want to be using to retrieve the PSGI application code +reference of your Catalyst application for use in F<.psgi> files. =cut -sub raw_psgi_app { +sub psgi_app { my ($app) = @_; return $app->engine->build_psgi_app($app); } @@ -2794,7 +2849,7 @@ sub setup_stats { =head2 $c->registered_plugins Returns a sorted list of the plugins which have either been stated in the -import list or which have been added via C<< MyApp->plugin(@args); >>. +import list. If passed a given plugin name, it will report a boolean value indicating whether or not that plugin is loaded. A fully qualified name is required if @@ -2821,7 +2876,7 @@ the plugin name does not begin with C. my $class = ref $proto || $proto; Class::MOP::load_class( $plugin ); - $class->log->warn( "$plugin inherits from 'Catalyst::Component' - this is decated and will not work in 5.81" ) + $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) { @@ -2965,7 +3020,7 @@ welcome screens C - The request body (for example file uploads) will not be parsed until it is accessed. This allows you to (for example) check authentication (and reject -the upload) before actually recieving all the data. See L +the upload) before actually receiving all the data. See L =item * @@ -2986,9 +3041,47 @@ to be shown in hit debug tables in the test server. =item * -C - Controlls if the C or C environment -variable should be used for determining the request path. See L -for more information. +C - Controls if the C or C environment +variable should be used for determining the request path. + +Most web server environments pass the requested path to the application using environment variables, +from which Catalyst has to reconstruct the request base (i.e. the top level path to / in the application, +exposed as C<< $c->request->base >>) and the request path below that base. + +There are two methods of doing this, both of which have advantages and disadvantages. Which method is used +is determined by the C<< $c->config(use_request_uri_for_path) >> setting (which can either be true or false). + +=over + +=item use_request_uri_for_path => 0 + +This is the default (and the) traditional method that Catalyst has used for determining the path information. +The path is generated from a combination of the C and C environment variables. +The allows the application to behave correctly when C is being used to redirect requests +into the application, as these variables are adjusted by mod_rewrite to take account for the redirect. + +However this method has the major disadvantage that it is impossible to correctly decode some elements +of the path, as RFC 3875 says: "C<< Unlike a URI path, the PATH_INFO is not URL-encoded, and cannot +contain path-segment parameters. >>" This means PATH_INFO is B decoded, and therefore Catalyst +can't distinguish / vs %2F in paths (in addition to other encoded values). + +=item use_request_uri_for_path => 1 + +This method uses the C and C environment variables. As C is never +decoded, this means that applications using this mode can correctly handle URIs including the %2F character +(i.e. with C set to C in Apache). + +Given that this method of path resolution is provably more correct, it is recommended that you use +this unless you have a specific need to deploy your application in a non-standard environment, and you are +aware of the implications of not being able to handle encoded URI paths correctly. + +However it also means that in a number of cases when the app isn't installed directly at a path, but instead +is having paths rewritten into it (e.g. as a .cgi/fcgi in a public_html directory, with mod_rewrite in a +.htaccess file, or when SSI is used to rewrite pages into the app, or when sub-paths of the app are exposed +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 =item * @@ -3103,6 +3196,8 @@ Wiki: =head2 L - The test suite. +=begin stopwords + =head1 PROJECT FOUNDER sri: Sebastian Riedel @@ -3243,6 +3338,14 @@ Yuval Kogman, C rainboxx: Matthias Dietrich, C +dd070: Dhaval Dhanani + +=end stopwords + +=head1 COPYRIGHT + +Copyright (c) 2005, the above named PROJECT FOUNDER and CONTRIBUTORS. + =head1 LICENSE This library is free software. You can redistribute it and/or modify it under