use Catalyst::Controller;
use Data::OptList;
use Devel::InnerPackage ();
-use File::stat;
use Module::Pluggable::Object ();
use Text::SimpleTable ();
use Path::Class::Dir ();
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; }
#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::CGI');
__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.80025';
+our $VERSION = '5.89002';
sub import {
my ( $class, @arguments ) = @_;
$meta->superclasses(grep { $_ ne 'Moose::Object' } $meta->superclasses);
unless( $meta->has_method('meta') ){
- $meta->add_method(meta => sub { Moose::Meta::Class->initialize("${caller}") } );
+ if ($Moose::VERSION >= 1.15) {
+ $meta->_add_meta_method('meta');
+ }
+ else {
+ $meta->add_method(meta => sub { Moose::Meta::Class->initialize("${caller}") } );
+ }
}
$caller->arguments( [@arguments] );
and access it from the stash.
+Keep in mind that the C<end> method used is that of the caller action. So a C<$c-E<gt>detach> inside a forwarded action would run the C<end> method from the original action requested.
+
=cut
sub forward { my $c = shift; no warnings 'recursion'; $c->dispatcher->forward( $c, @_ ) }
C<go> escapes the processing of the current request chain on completion, and
does not return to its caller.
+@arguments are arguments to the final destination of $action. @captures are
+arguments to the intermediate steps, if any, on the way to the final sub of
+$action.
+
=cut
sub go { my $c = shift; $c->dispatcher->go( $c, @_ ) }
unless ( ref($name) ) { # Direct component hash lookup to avoid costly regexps
my $comps = $c->components;
my $check = $appclass."::View::".$name;
- return $c->_filter_component( $comps->{$check}, @args ) if exists $comps->{$check};
+ if( exists $comps->{$check} ) {
+ return $c->_filter_component( $comps->{$check}, @args );
+ }
+ else {
+ $c->log->warn( "Attempted to use view '$check', but does not exist" );
+ }
}
my @result = $c->_comp_search_prefixes( $name, qw/View V/ );
return map { $c->_filter_component( $_, @args ) } @result if ref $name;
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;
use Moose;
# this attr will receive 'baz' at construction time
- has 'bar' => (
+ has 'bar' => (
is => 'rw',
isa => 'Str',
);
You can then get the value 'baz' by calling $c->model('Foo')->bar
+(or $self->bar inside code in the model).
+
+B<NOTE:> you MUST NOT call C<< $self->config >> or C<< __PACKAGE__->config >>
+as a way of reading config within your code, as this B<will not> give you the
+correctly merged config back. You B<MUST> take the config values supplied to
+the constructor and use those instead.
=cut
$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} ) {
A hook to attach modifiers to. This method does not do anything except set the
C<setup_finished> accessor.
-Applying method modifiers to the C<setup> method doesn't work, because of quirky thingsdone for plugin setup.
+Applying method modifiers to the C<setup> method doesn't work, because of quirky things done for plugin setup.
Example:
Constructs an absolute L<URI> object based on the application root, the
provided path, and the additional arguments and query parameters provided.
When used as a string, provides a textual URI. If you need more flexibility
-than this (i.e. the option to provide relative URIs etc. see
+than this (i.e. the option to provide relative URIs etc.) see
L<Catalyst::Plugin::SmartURI>.
If no arguments are provided, the URI for the current action is returned.
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;
$error = qq/Caught exception in $class->$name "$error"/;
}
$c->error($error);
- $c->state(0);
}
+ $c->state(0);
}
return $c->state;
}
my $parent = $c->stack->[-1];
# forward, locate the caller
- if ( exists $c->counter->{"$parent"} ) {
+ if ( defined $parent && exists $c->counter->{"$parent"} ) {
$c->stats->profile(
begin => $action,
parent => "$parent" . $c->counter->{"$parent"},
}
# 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') )
+ 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');
# Always expect worst case!
my $status = -1;
- eval {
+ try {
if ($class->debug) {
my $secs = time - $START || 1;
my $av = sprintf '%.3f', $COUNT / $secs;
my $c = $class->prepare(@arguments);
$c->dispatch;
$status = $c->finalize;
- };
-
- if ( my $error = $@ ) {
- chomp $error;
- $class->log->error(qq/Caught exception in engine "$error"/);
}
+ catch {
+ chomp(my $error = $_);
+ $class->log->error(qq/Caught exception in engine "$error"/);
+ };
$COUNT++;
$c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
}
- #XXX reuse coderef from can
- # Allow engine to direct the prepare flow (for POE)
- if ( $c->engine->can('prepare') ) {
- $c->engine->prepare( $c, @arguments );
- }
- else {
- $c->prepare_request(@arguments);
- $c->prepare_connection;
- $c->prepare_query_parameters;
- $c->prepare_headers;
- $c->prepare_cookies;
- $c->prepare_path;
-
- # Prepare the body for reading, either by prepare_body
- # or the user, if they are using $c->read
- $c->prepare_read;
-
- # Parse the body unless the user wants it on-demand
- unless ( ref($c)->config->{parse_on_demand} ) {
- $c->prepare_body;
+ try {
+ # Allow engine to direct the prepare flow (for POE)
+ if ( my $prepare = $c->engine->can('prepare') ) {
+ $c->engine->$prepare( $c, @arguments );
+ }
+ else {
+ $c->prepare_request(@arguments);
+ $c->prepare_connection;
+ $c->prepare_query_parameters;
+ $c->prepare_headers;
+ $c->prepare_cookies;
+ $c->prepare_path;
+
+ # Prepare the body for reading, either by prepare_body
+ # or the user, if they are using $c->read
+ $c->prepare_read;
+
+ # Parse the body unless the user wants it on-demand
+ unless ( ref($c)->config->{parse_on_demand} ) {
+ $c->prepare_body;
+ }
}
}
+ # VERY ugly and probably shouldn't rely on ->finalize actually working
+ catch {
+ # failed prepare is always due to an invalid request, right?
+ $c->response->status(400);
+ $c->response->content_type('text/plain');
+ $c->response->body('Bad Request');
+ $c->finalize;
+ die $_;
+ };
my $method = $c->req->method || '';
my $path = $c->req->path;
$c->log->debug("Query keywords are: $keywords");
}
- $c->log_request_parameters( query => $request->query_parameters, body => $request->body_parameters );
+ $c->log_request_parameters( query => $request->query_parameters, $request->_has_body ? (body => $request->body_parameters) : () );
$c->log_request_uploads($request);
}
=head2 $c->request_class
-Returns or sets the request class.
+Returns or sets the request class. Defaults to L<Catalyst::Request>.
=head2 $c->response_class
-Returns or sets the response class.
+Returns or sets the response class. Defaults to L<Catalyst::Response>.
=head2 $c->read( [$maxlength] )
=cut
-sub run { my $c = shift; return $c->engine->run( $c, @_ ) }
+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 )
my $config = $class->config->{ setup_components };
- my @comps = sort { length $a <=> length $b }
- $class->locate_components($config);
+ my @comps = $class->locate_components($config);
my %comps = map { $_ => 1 } @comps;
my $deprecatedcatalyst_component_names = grep { /::[CMV]::/ } @comps;
%$config
);
- my @comps = $locator->plugins;
+ # XXX think about ditching this sort entirely
+ my @comps = sort { length $a <=> length $b } $locator->plugins;
return @comps;
}
=cut
+sub engine_class {
+ my $class = shift;
+ $class->engine_loader->catalyst_engine_class(@_);
+}
+
sub setup_engine {
- my ( $class, $engine ) = @_;
+ my ($class, $requested_engine) = @_;
+
+ $class->engine_loader(
+ Catalyst::EngineLoader->new({
+ application_name => $class,
+ (defined $requested_engine
+ ? (requested_engine => $requested_engine) : ()),
+ }),
+ );
+
+ # Don't really setup_engine -- see _setup_psgi_app for explanation.
+ return if $class->loading_psgi_file;
- if ($engine) {
- $engine = 'Catalyst::Engine::' . $engine;
+ my $engine = $class->engine_class;
+ Class::MOP::load_class($engine);
+
+ if ($ENV{MOD_PERL}) {
+ 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 $psgi_app = $class->psgi_app;
+ $apache->call_app($r, $psgi_app);
+ });
+
+ $meta->make_immutable(%immutable_options) if $was_immutable;
}
- if ( my $env = Catalyst::Utils::env_value( $class, 'ENGINE' ) ) {
- $engine = 'Catalyst::Engine::' . $env;
+ $class->engine( $engine->new );
+
+ return;
+}
+
+sub _finalized_psgi_app {
+ my ($app) = @_;
+
+ unless ($app->_psgi_app) {
+ my $psgi_app = $app->_setup_psgi_app;
+ $app->_psgi_app($psgi_app);
}
- if ( $ENV{MOD_PERL} ) {
- my $meta = Class::MOP::get_metaclass_by_name($class);
+ return $app->_psgi_app;
+}
- # create the apache method
- $meta->add_method('apache' => sub { shift->engine->apache });
+sub _setup_psgi_app {
+ my ($app) = @_;
- my ( $software, $version ) =
- $ENV{MOD_PERL} =~ /^(\S+)\/(\d+(?:[\.\_]\d+)+)/;
+ for my $home (Path::Class::Dir->new($app->config->{home})) {
+ my $psgi_file = $home->file(
+ Catalyst::Utils::appprefix($app) . '.psgi',
+ );
- $version =~ s/_//g;
- $version =~ s/(\.[^.]+)\./$1/g;
+ next unless -e $psgi_file;
- if ( $software eq 'mod_perl' ) {
+ # 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.
- if ( !$engine ) {
+ $app->loading_psgi_file(1);
+ my $psgi_app = Plack::Util::load_psgi($psgi_file);
+ $app->loading_psgi_file(0);
- if ( $version >= 1.99922 ) {
- $engine = 'Catalyst::Engine::Apache2::MP20';
- }
+ return $psgi_app
+ unless $app->engine_loader->needs_psgi_engine_compat_hack;
- elsif ( $version >= 1.9901 ) {
- $engine = 'Catalyst::Engine::Apache2::MP19';
- }
+ warn <<"EOW";
+Found a legacy Catalyst::Engine::PSGI .psgi file at ${psgi_file}.
- elsif ( $version >= 1.24 ) {
- $engine = 'Catalyst::Engine::Apache::MP13';
- }
+Its content has been ignored. Please consult the Catalyst::Upgrading
+documentation on how to upgrade from Catalyst::Engine::PSGI.
+EOW
+ }
- else {
- Catalyst::Exception->throw( message =>
- qq/Unsupported mod_perl version: $ENV{MOD_PERL}/ );
- }
+ return $app->apply_default_middlewares($app->psgi_app);
+}
- }
+=head2 $c->apply_default_middlewares
- # install the correct mod_perl handler
- if ( $version >= 1.9901 ) {
- *handler = sub : method {
- shift->handle_request(@_);
- };
- }
- else {
- *handler = sub ($$) { shift->handle_request(@_) };
- }
+Adds the following L<Plack> middlewares to your application, since they are
+useful and commonly needed:
- }
+L<Plack::Middleware::ReverseProxy>, (conditionally added based on the status
+of your $ENV{REMOTE_ADDR}, and can be forced on with C<using_frontend_proxy>
+or forced off with C<ignore_frontend_proxy>), L<Plack::Middleware::LighttpdScriptNameFix>
+(if you are using Lighttpd), L<Plack::Middleware::IIS6ScriptNameFix> (always
+applied since this middleware is smart enough to conditionally apply itself).
- elsif ( $software eq 'Zeus-Perl' ) {
- $engine = 'Catalyst::Engine::Zeus';
- }
+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}
- else {
- Catalyst::Exception->throw(
- message => qq/Unsupported mod_perl: $ENV{MOD_PERL}/ );
- }
- }
+=cut
- unless ($engine) {
- $engine = $class->engine_class;
- }
- Class::MOP::load_class($engine);
+sub apply_default_middlewares {
+ my ($app, $psgi_app) = @_;
- # 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;
- }
+ $psgi_app = Plack::Middleware::Conditional->wrap(
+ $psgi_app,
+ builder => sub { Plack::Middleware::ReverseProxy->wrap($_[0]) },
+ condition => sub {
+ my ($env) = @_;
+ return if $app->config->{ignore_frontend_proxy};
+ return $env->{REMOTE_ADDR} eq '127.0.0.1'
+ || $app->config->{using_frontend_proxy};
+ },
+ );
- elsif ( $engine->isa('Catalyst::Engine::Server::Base')
- && Catalyst::Engine::Server->VERSION le '0.02' )
- {
- $old_engine = 1;
- }
+ my $server_matches = sub {
+ my ($re) = @_;
+ return sub {
+ my ($env) = @_;
+ my $server = $env->{SERVER_SOFTWARE};
+ return unless $server;
+ return $server =~ $re ? 1 : 0;
+ };
+ };
- elsif ($engine->isa('Catalyst::Engine::HTTP::POE')
- && $engine->VERSION eq '0.01' )
- {
- $old_engine = 1;
- }
+ # 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,
+ condition => $server_matches->(qr/^nginx/),
+ builder => sub {
+ my ($to_wrap) = @_;
+ return sub {
+ my ($env) = @_;
+ my $script_name = $env->{SCRIPT_NAME};
+ $env->{PATH_INFO} =~ s/^$script_name//g;
+ return $to_wrap->($env);
+ };
+ },
+ );
- elsif ($engine->isa('Catalyst::Engine::Zeus')
- && $engine->VERSION eq '0.01' )
- {
- $old_engine = 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);
- if ($old_engine) {
- Catalyst::Exception->throw( message =>
- qq/Engine "$engine" is not supported by this version of Catalyst/
- );
- }
+ return $psgi_app;
+}
- # engine instance
- $class->engine( $engine->new );
+=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.
+
+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 psgi_app {
+ my ($app) = @_;
+ return $app->engine->build_psgi_app($app);
}
=head2 $c->setup_home
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) {
rainboxx: Matthias Dietrich, C<perl@rainboxx.de>
+dd070: Dhaval Dhanani <dhaval070@gmail.com>
+
+=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