use HTTP::Headers;
use Time::HiRes qw/gettimeofday tv_interval/;
use Text::ASCIITable;
+use Catalyst::Exception;
use Catalyst::Request;
use Catalyst::Request::Upload;
use Catalyst::Response;
+use Catalyst::Utils;
require Module::Pluggable::Fast;
$Data::Dumper::Terse = 1;
__PACKAGE__->mk_classdata('components');
-__PACKAGE__->mk_accessors(qw/request response state/);
+__PACKAGE__->mk_accessors(qw/counter depth request response state/);
*comp = \&component;
*req = \&request;
*res = \&response;
+# For backwards compatibility
+*finalize_output = \&finalize_body;
+
# For statistics
-our $COUNT = 1;
-our $START = time;
+our $COUNT = 1;
+our $START = time;
+our $RECURSION = 1000;
+our $DETACH = "catalyst_detach\n";
=head1 NAME
=cut
sub component {
- my ( $c, $name ) = @_;
+ my $c = shift;
- if ( my $component = $c->components->{$name} ) {
- return $component;
- }
+ if (@_) {
- else {
- for my $component ( keys %{ $c->components } ) {
- return $c->components->{$component} if $component =~ /$name/i;
+ my $name = shift;
+
+ if ( my $component = $c->components->{$name} ) {
+ return $component;
+ }
+
+ else {
+ for my $component ( keys %{ $c->components } ) {
+ return $c->components->{$component} if $component =~ /$name/i;
+ }
}
}
+ return sort keys %{ $c->components };
}
+=item $c->counter
+
+Returns a hashref containing coderefs and execution counts.
+(Needed for deep recursion detection)
+
+=item $c->depth
+
+Returns the actual forward depth.
+
=item $c->error
=item $c->error($error, ...)
sub execute {
my ( $c, $class, $code ) = @_;
- $class = $c->comp($class) || $class;
+ $class = $c->components->{$class} || $class;
$c->state(0);
my $callsub = ( caller(1) )[3];
+ my $action = '';
+ if ( $c->debug ) {
+ $action = $c->actions->{reverse}->{"$code"};
+ $action = "/$action" unless $action =~ /\-\>/;
+ $c->counter->{"$code"}++;
+
+ if ( $c->counter->{"$code"} > $RECURSION ) {
+ my $error = qq/Deep recursion detected in "$action"/;
+ $c->log->error($error);
+ $c->error($error);
+ $c->state(0);
+ return $c->state;
+ }
+
+ $action = "-> $action" if $callsub =~ /forward$/;
+ }
+
+ $c->{depth}++;
eval {
if ( $c->debug )
{
- my $action = $c->actions->{reverse}->{"$code"};
- $action = "/$action" unless $action =~ /\-\>/;
- $action = "-> $action" if $callsub =~ /forward$/;
my ( $elapsed, @state ) =
$c->benchmark( $code, $class, $c, @{ $c->req->args } );
push @{ $c->{stats} }, [ $action, sprintf( '%fs', $elapsed ) ];
$c->state(@state);
}
- else { $c->state( &$code( $class, $c, @{ $c->req->args } ) ) }
+ else { $c->state( &$code( $class, $c, @{ $c->req->args } ) || 0 ) }
};
+ $c->{depth}--;
if ( my $error = $@ ) {
- unless ( ref $error ) {
- chomp $error;
- $error = qq/Caught exception "$error"/;
- }
+ if ( $error eq $DETACH ) { die $DETACH if $c->{depth} > 1 }
+ else {
+ unless ( ref $error ) {
+ chomp $error;
+ $error = qq/Caught exception "$error"/;
+ }
- $c->log->error($error);
- $c->error($error);
- $c->state(0);
+ $c->log->error($error);
+ $c->error($error);
+ $c->state(0);
+ }
}
return $c->state;
}
$c->finalize_error;
}
- if ( !$c->response->output && $c->response->status !~ /^(1|3)\d\d$/ ) {
+ if ( !$c->response->body && $c->response->status == 200 ) {
$c->finalize_error;
}
- if ( $c->response->output && !$c->response->content_length ) {
- use bytes; # play safe with a utf8 aware perl
- $c->response->content_length( length $c->response->output );
+ if ( $c->response->body && !$c->response->content_length ) {
+ $c->response->content_length( bytes::length( $c->response->body ) );
+ }
+
+ if ( $c->response->status =~ /^(1\d\d|[23]04)$/ ) {
+ $c->response->headers->remove_header("Content-Length");
+ $c->response->body('');
+ }
+
+ if ( $c->request->method eq 'HEAD' ) {
+ $c->response->body('');
}
my $status = $c->finalize_headers;
- $c->finalize_output;
+ $c->finalize_body;
return $status;
}
+=item $c->finalize_output
+
+<obsolete>, see finalize_body
+
+=item $c->finalize_body
+
+Finalize body.
+
+=cut
+
+sub finalize_body { }
+
=item $c->finalize_cookies
Finalize cookies.
$name = '';
}
- $c->res->output( <<"" );
+ $c->res->body( <<"" );
<html>
<head>
<title>$title</title>
sub finalize_headers { }
-=item $c->finalize_output
-
-Finalize output.
-
-=cut
-
-sub finalize_output { }
-
-=item $c->handler( $class, $r )
+=item $c->handler( $class, @arguments )
Handles the request.
=cut
sub handler {
- my ( $class, $engine ) = @_;
+ my ( $class, @arguments ) = @_;
# Always expect worst case!
my $status = -1;
my @stats = ();
my $handler = sub {
- my $c = $class->prepare($engine);
+ my $c = $class->prepare(@arguments);
$c->{stats} = \@stats;
$c->dispatch;
return $c->finalize;
my $elapsed;
( $elapsed, $status ) = $class->benchmark($handler);
$elapsed = sprintf '%f', $elapsed;
- my $av = sprintf '%.3f', 1 / $elapsed;
+ my $av = sprintf '%.3f',
+ ( $elapsed == 0 ? '??' : ( 1 / $elapsed ) );
my $t = Text::ASCIITable->new;
$t->setCols( 'Action', 'Time' );
$t->setColWidth( 'Action', 64, 1 );
return $status;
}
-=item $c->prepare($r)
+=item $c->prepare(@arguments)
Turns the engine-specific request( Apache, CGI ... )
into a Catalyst context .
=cut
sub prepare {
- my ( $class, $r ) = @_;
+ my ( $class, @arguments ) = @_;
my $c = bless {
+ counter => {},
+ depth => 0,
request => Catalyst::Request->new(
{
arguments => [],
cookies => {},
headers => HTTP::Headers->new,
parameters => {},
+ secure => 0,
snippets => [],
uploads => {}
}
),
response => Catalyst::Response->new(
- { cookies => {}, headers => HTTP::Headers->new, status => 200 }
+ {
+ body => '',
+ cookies => {},
+ headers => HTTP::Headers->new( 'Content-Length' => 0 ),
+ status => 200
+ }
),
stash => {},
state => 0
$c->res->headers->header( 'X-Catalyst' => $Catalyst::VERSION );
}
- $c->prepare_request($r);
- $c->prepare_path;
+ $c->prepare_request(@arguments);
+ $c->prepare_connection;
$c->prepare_headers;
- $c->prepare_input;
$c->prepare_cookies;
- $c->prepare_connection;
+ $c->prepare_path;
+ $c->prepare_action;
+
+ my $method = $c->req->method || '';
+ my $path = $c->req->path || '';
+ my $address = $c->req->address || '';
- my $method = $c->req->method || '';
- my $path = $c->req->path || '';
- my $hostname = $c->req->hostname || '';
- my $address = $c->req->address || '';
- $c->log->debug(qq/"$method" request for "$path" from $hostname($address)/)
+ $c->log->debug(qq/"$method" request for "$path" from $address/)
if $c->debug;
- $c->prepare_action;
- $c->prepare_parameters;
+ if ( $c->request->method eq 'POST' and $c->request->content_length ) {
+
+ if ( $c->req->content_type eq 'application/x-www-form-urlencoded' ) {
+ $c->prepare_parameters;
+ }
+ elsif ( $c->req->content_type eq 'multipart/form-data' ) {
+ $c->prepare_parameters;
+ $c->prepare_uploads;
+ }
+ else {
+ $c->prepare_body;
+ }
+ }
+
+ if ( $c->request->method eq 'GET' ) {
+ $c->prepare_parameters;
+ }
if ( $c->debug && keys %{ $c->req->params } ) {
my $t = Text::ASCIITable->new;
$t->setCols( 'Key', 'Value' );
$t->setColWidth( 'Key', 37, 1 );
$t->setColWidth( 'Value', 36, 1 );
- for my $key ( keys %{ $c->req->params } ) {
- my $value = $c->req->params->{$key} || '';
+ for my $key ( sort keys %{ $c->req->params } ) {
+ my $param = $c->req->params->{$key};
+ my $value = defined($param) ? $param : '';
$t->addRow( $key, $value );
}
$c->log->debug( 'Parameters are', $t->draw );
}
- $c->prepare_uploads;
return $c;
}
if ( $c->debug && @args );
}
+=item $c->prepare_body
+
+Prepare message body.
+
+=cut
+
+sub prepare_body { }
+
=item $c->prepare_connection
Prepare connection.
=cut
-sub prepare_input { }
-
-=item $c->prepare_input
-
-Prepare message body.
-
-=cut
-
sub prepare_parameters { }
=item $c->prepare_path
sub setup {
my $self = shift;
+
+ # Initialize our data structure
+ $self->components( {} );
+
$self->setup_components;
+
+ if ( $self->debug ) {
+ my $t = Text::ASCIITable->new;
+ $t->setOptions( 'hide_HeadRow', 1 );
+ $t->setOptions( 'hide_HeadLine', 1 );
+ $t->setCols('Class');
+ $t->setColWidth( 'Class', 75, 1 );
+ $t->addRow($_) for sort keys %{ $self->components };
+ $self->log->debug( 'Loaded components', $t->draw )
+ if ( @{ $t->{tbl_rows} } );
+ }
+
+ # Add our self to components, since we are also a component
+ $self->components->{$self} = $self;
+
+ $self->setup_actions;
+
if ( $self->debug ) {
my $name = $self->config->{name} || 'Application';
$self->log->info("$name powered by Catalyst $Catalyst::VERSION");
sub setup_components {
my $self = shift;
- # Components
- my $class = ref $self || $self;
- eval <<"";
- package $class;
- import Module::Pluggable::Fast
- name => '_components',
- search => [
- '$class\::Controller', '$class\::C',
- '$class\::Model', '$class\::M',
- '$class\::View', '$class\::V'
- ];
+ my $callback = sub {
+ my ( $component, $context ) = @_;
+
+ unless ( $component->isa('Catalyst::Base') ) {
+ return $component;
+ }
+
+ my $suffix = Catalyst::Utils::class2classsuffix($component);
+ my $config = $self->config->{$suffix} || {};
+
+ my $instance;
+
+ eval { $instance = $component->new( $context, $config ); };
+
+ if ( my $error = $@ ) {
+
+ chomp $error;
+
+ Catalyst::Exception->throw(
+ message => qq/Couldn't instantiate component "$component", "$error"/
+ );
+ }
+
+ return $instance;
+ };
+
+ eval {
+ Module::Pluggable::Fast->import(
+ name => '_components',
+ search => [
+ "$self\::Controller", "$self\::C",
+ "$self\::Model", "$self\::M",
+ "$self\::View", "$self\::V"
+ ],
+ callback => $callback
+ );
+ };
if ( my $error = $@ ) {
+
chomp $error;
- die qq/Couldn't load components "$error"/;
+
+ Catalyst::Exception->throw(
+ message => qq/Couldn't load components "$error"/
+ );
}
- $self->components( {} );
- my @comps;
- for my $comp ( $self->_components($self) ) {
- $self->components->{ ref $comp } = $comp;
- push @comps, $comp;
+ for my $component ( $self->_components($self) ) {
+ $self->components->{ ref $component || $component } = $component;
}
-
- my $t = Text::ASCIITable->new( { hide_HeadRow => 1, hide_HeadLine => 1 } );
- $t->setCols('Class');
- $t->setColWidth( 'Class', 75, 1 );
- $t->addRow($_) for keys %{ $self->components };
- $self->log->debug( 'Loaded components', $t->draw )
- if ( @{ $t->{tbl_rows} } && $self->debug );
-
- $self->setup_actions( [ $self, @comps ] );
}
=item $c->state
sub stash {
my $self = shift;
- if ( $_[0] ) {
- my $stash = $_[1] ? {@_} : $_[0];
+ if (@_) {
+ my $stash = @_ > 1 ? {@_} : $_[0];
while ( my ( $key, $val ) = each %$stash ) {
$self->{stash}->{$key} = $val;
}