X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FCatalyst%2FEngine.pm;h=84450c499df58123beb012e86bc6b9ca969b33a0;hb=ca3023fce6a4efe8f982a41990592313569b769d;hp=4271aae2e4053c05ccc0a23ce7e20974b4afd47e;hpb=be1c95030e15ef9d081808462688ec09f7daed99;p=catagits%2FCatalyst-Runtime.git
diff --git a/lib/Catalyst/Engine.pm b/lib/Catalyst/Engine.pm
index 4271aae..84450c4 100644
--- a/lib/Catalyst/Engine.pm
+++ b/lib/Catalyst/Engine.pm
@@ -10,10 +10,11 @@ use HTML::Entities;
use HTTP::Body;
use HTTP::Headers;
use URI::QueryParam;
+use Moose::Util::TypeConstraints;
use namespace::clean -except => 'meta';
-has env => (is => 'rw');
+has env => (is => 'ro', writer => '_set_env', clearer => '_clear_env');
# input position and length
has read_length => (is => 'rw');
@@ -21,6 +22,18 @@ has read_position => (is => 'rw');
has _prepared_write => (is => 'rw');
+has _response_cb => (
+ is => 'ro',
+ isa => 'CodeRef',
+ writer => '_set_response_cb',
+);
+
+has _writer => (
+ is => 'ro',
+ isa => duck_type([qw(write close)]),
+ writer => '_set_writer',
+);
+
# Amount of data to read from input on each pass
our $CHUNKSIZE = 64 * 1024;
@@ -50,8 +63,8 @@ sub finalize_body {
if ( blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
my $got;
do {
- read $body, my ($buffer), $CHUNKSIZE;
- last unless $self->write( $c, $buffer );
+ $got = read $body, my ($buffer), $CHUNKSIZE;
+ $got = 0 unless $self->write( $c, $buffer );
} while $got > 0;
close $body;
@@ -59,6 +72,10 @@ sub finalize_body {
else {
$self->write( $c, $body );
}
+
+ $self->_writer->close;
+
+ return;
}
=head2 $self->finalize_cookies($c)
@@ -108,11 +125,29 @@ is in debug mode, or a `please come back later` message otherwise.
=cut
+sub _dump_error_page_element {
+ my ($self, $i, $element) = @_;
+ my ($name, $val) = @{ $element };
+
+ # This is fugly, but the metaclass is _HUGE_ and demands waaay too much
+ # scrolling. Suggestions for more pleasant ways to do this welcome.
+ local $val->{'__MOP__'} = "Stringified: "
+ . $val->{'__MOP__'} if ref $val eq 'HASH' && exists $val->{'__MOP__'};
+
+ my $text = encode_entities( dump( $val ));
+ sprintf <<"EOF", $name, $text;
+
+
+EOF
+}
+
sub finalize_error {
my ( $self, $c ) = @_;
$c->res->content_type('text/html; charset=utf-8');
- my $name = $c->config->{name} || join(' ', split('::', ref $c));
+ my $name = ref($c)->config->{name} || join(' ', split('::', ref $c));
my ( $title, $error, $infos );
if ( $c->debug ) {
@@ -138,14 +173,7 @@ sub finalize_error {
my @infos;
my $i = 0;
for my $dump ( $c->dump_these ) {
- my $name = $dump->[0];
- my $value = encode_entities( dump( $dump->[1] ));
- push @infos, sprintf <<"EOF", $name, $value;
-
-
-EOF
+ push @infos, $self->_dump_error_page_element($i, $dump);
$i++;
}
$infos = join "\n", @infos;
@@ -282,7 +310,16 @@ Abstract method, allows engines to write headers to response
=cut
-sub finalize_headers { }
+sub finalize_headers {
+ my ($self, $ctx) = @_;
+
+ my @headers;
+ $ctx->response->headers->scan(sub { push @headers, @_ });
+
+ $self->_set_writer($self->_response_cb->([ $ctx->response->status, \@headers ]));
+
+ return;
+}
=head2 $self->finalize_read($c)
@@ -317,16 +354,18 @@ sets up the L object body using L
sub prepare_body {
my ( $self, $c ) = @_;
+ my $appclass = ref($c) || $c;
if ( my $length = $self->read_length ) {
my $request = $c->request;
unless ( $request->_body ) {
my $type = $request->header('Content-Type');
$request->_body(HTTP::Body->new( $type, $length ));
- $request->_body->tmpdir( $c->config->{uploadtmp} )
- if exists $c->config->{uploadtmp};
+ $request->_body->tmpdir( $appclass->config->{uploadtmp} )
+ if exists $appclass->config->{uploadtmp};
}
- while ( my $buffer = $self->read($c) ) {
+ # Check for definedness as you could read '0'
+ while ( defined ( my $buffer = $self->read($c) ) ) {
$c->prepare_body_chunk($buffer);
}
@@ -376,7 +415,22 @@ Abstract method implemented in engines.
=cut
-sub prepare_connection { }
+sub prepare_connection {
+ my ($self, $ctx) = @_;
+
+ my $env = $self->env;
+ my $request = $ctx->request;
+
+ $request->address( $env->{REMOTE_ADDR} );
+ $request->hostname( $env->{REMOTE_HOST} )
+ if exists $env->{REMOTE_HOST};
+ $request->protocol( $env->{SERVER_PROTOCOL} );
+ $request->remote_user( $env->{REMOTE_USER} );
+ $request->method( $env->{REQUEST_METHOD} );
+ $request->secure( $env->{'psgi.url_scheme'} eq 'https' );
+
+ return;
+}
=head2 $self->prepare_cookies($c)
@@ -396,7 +450,19 @@ sub prepare_cookies {
=cut
-sub prepare_headers { }
+sub prepare_headers {
+ my ($self, $ctx) = @_;
+
+ my $env = $self->env;
+ my $headers = $ctx->request->headers;
+
+ for my $header (keys %{ $env }) {
+ next unless $header =~ /^(HTTP|CONTENT|COOKIE)/i;
+ (my $field = $header) =~ s/^HTTPS?_//;
+ $field =~ tr/_/-/;
+ $headers->header($field => $env->{$header});
+ }
+}
=head2 $self->prepare_parameters($c)
@@ -434,7 +500,51 @@ abstract method, implemented by engines.
=cut
-sub prepare_path { }
+sub prepare_path {
+ my ($self, $ctx) = @_;
+
+ my $env = $self->env;
+
+ my $scheme = $ctx->request->secure ? 'https' : 'http';
+ my $host = $env->{HTTP_HOST} || $env->{SERVER_NAME};
+ my $port = $env->{SERVER_PORT} || 80;
+ my $base_path = $env->{SCRIPT_NAME} || "/";
+
+ # set the request URI
+ my $req_uri = $env->{REQUEST_URI};
+ $req_uri =~ s/\?.*$//;
+ my $path = $self->unescape_uri($req_uri);
+ $path =~ s{^/+}{};
+
+ # Using URI directly is way too slow, so we construct the URLs manually
+ my $uri_class = "URI::$scheme";
+
+ # HTTP_HOST will include the port even if it's 80/443
+ $host =~ s/:(?:80|443)$//;
+
+ if ($port !~ /^(?:80|443)$/ && $host !~ /:/) {
+ $host .= ":$port";
+ }
+
+ # Escape the path
+ $path =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
+ $path =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE
+
+ my $query = $env->{QUERY_STRING} ? '?' . $env->{QUERY_STRING} : '';
+ my $uri = $scheme . '://' . $host . '/' . $path . $query;
+
+ $ctx->request->uri( bless \$uri, $uri_class );
+
+ # set the base URI
+ # base must end in a slash
+ $base_path .= '/' unless $base_path =~ m{/$};
+
+ my $base_uri = $scheme . '://' . $host . $base_path;
+
+ $ctx->request->base( bless \$base_uri, $uri_class );
+
+ return;
+}
=head2 $self->prepare_request($c)
@@ -445,7 +555,11 @@ process the query string and extract query parameters.
=cut
sub prepare_query_parameters {
- my ( $self, $c, $query_string ) = @_;
+ my ($self, $c) = @_;
+
+ my $query_string = exists $self->env->{QUERY_STRING}
+ ? $self->env->{QUERY_STRING}
+ : '';
# Check for keywords (no = signs)
# (yes, index() is faster than a regex :))
@@ -507,7 +621,10 @@ Populate the context object from the request object.
=cut
-sub prepare_request { }
+sub prepare_request {
+ my ($self, $ctx, %args) = @_;
+ $self->_set_env($args{env});
+}
=head2 $self->prepare_uploads($c)
@@ -565,6 +682,10 @@ sub prepare_write { }
=head2 $self->read($c, [$maxlength])
+Reads from the input stream by calling C<< $self->read_chunk >>.
+
+Maintains the read_length and read_position counters as data is read.
+
=cut
sub read {
@@ -582,6 +703,11 @@ sub read {
my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
my $rc = $self->read_chunk( $c, my $buffer, $readlen );
if ( defined $rc ) {
+ if (0 == $rc) { # Nothing more to read even though Content-Length
+ # said there should be. FIXME - Warn in the log here?
+ $self->finalize_read;
+ return;
+ }
$self->read_position( $self->read_position + $rc );
return $buffer;
}
@@ -594,7 +720,8 @@ sub read {
=head2 $self->read_chunk($c, $buffer, $length)
Each engine implements read_chunk as its preferred way of reading a chunk
-of data.
+of data. Returns the number of bytes read. A return of 0 indicates that
+there is no more data to be read.
=cut
@@ -615,7 +742,19 @@ Start the engine. Implemented by the various engine classes.
=cut
-sub run { }
+sub run {
+ my ($self, $app) = @_;
+
+ return sub {
+ my ($env) = @_;
+
+ return sub {
+ my ($respond) = @_;
+ $self->_set_response_cb($respond);
+ $app->handle_request(env => $env);
+ };
+ };
+}
=head2 $self->write($c, $buffer)
@@ -633,31 +772,10 @@ sub write {
return 0 if !defined $buffer;
- my $len = length($buffer);
- my $wrote = syswrite STDOUT, $buffer;
-
- if ( !defined $wrote && $! == EWOULDBLOCK ) {
- # Unable to write on the first try, will retry in the loop below
- $wrote = 0;
- }
-
- if ( defined $wrote && $wrote < $len ) {
- # We didn't write the whole buffer
- while (1) {
- my $ret = syswrite STDOUT, $buffer, $CHUNKSIZE, $wrote;
- if ( defined $ret ) {
- $wrote += $ret;
- }
- else {
- next if $! == EWOULDBLOCK;
- return;
- }
-
- last if $wrote >= $len;
- }
- }
+ my $len = length($buffer);
+ $self->_writer->write($buffer);
- return $wrote;
+ return $len;
}
=head2 $self->unescape_uri($uri)