Stop the request needing the context, just pass in the logger instead
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Request.pm
index 30d7be4..997b3ba 100644 (file)
@@ -14,10 +14,37 @@ use namespace::clean -except => 'meta';
 
 with 'MooseX::Emulate::Class::Accessor::Fast';
 
+has env => (is => 'ro', writer => '_set_env');
+
+has _read_position => ( is => 'rw', default => 0 );
+has _read_length => ( is => 'ro',
+    default => sub {
+        my $self = shift;
+        $self->header('Content-Length') || 0;
+    },
+    lazy => 1,
+);
+
 has action => (is => 'rw');
 has address => (is => 'rw');
 has arguments => (is => 'rw', default => sub { [] });
-has cookies => (is => 'rw', default => sub { {} });
+has cookies => (is => 'ro', builder => 'prepare_cookies', lazy => 1);
+
+=head2 $self->prepare_cookies($c)
+
+Parse cookies from header. Sets a L<CGI::Simple::Cookie> object.
+
+=cut
+
+sub prepare_cookies {
+    my ( $self ) = @_;
+
+    if ( my $header = $self->header('Cookie') ) {
+        return { CGI::Simple::Cookie->parse($header) };
+    }
+    {};
+}
+
 has query_keywords => (is => 'rw');
 has match => (is => 'rw');
 has method => (is => 'rw');
@@ -31,18 +58,69 @@ has headers => (
   is      => 'rw',
   isa     => 'HTTP::Headers',
   handles => [qw(content_encoding content_length content_type header referer user_agent)],
-  default => sub { HTTP::Headers->new() },
-  required => 1,
+  builder => 'prepare_headers',
   lazy => 1,
 );
 
-has _context => (
-  is => 'rw',
-  weak_ref => 1,
-  handles => ['read'],
-  clearer => '_clear_context',
+=head2 $self->prepare_headers($c)
+
+=cut
+
+sub prepare_headers {
+    my ($self) = @_;
+
+    my $env = $self->env;
+    my $headers = HTTP::Headers->new();
+
+    for my $header (keys %{ $env }) {
+        next unless $header =~ /^(HTTP|CONTENT|COOKIE)/i;
+        (my $field = $header) =~ s/^HTTPS?_//;
+        $field =~ tr/_/-/;
+        $headers->header($field => $env->{$header});
+    }
+    return $headers;
+}
+
+has _log => (
+    is => 'ro',
+    weak_ref => 1,
+    required => 1,
 );
 
+# Amount of data to read from input on each pass
+our $CHUNKSIZE = 64 * 1024;
+
+sub read {
+    my ($self, $maxlength) = @_;
+    my $remaining = $self->_read_length - $self->_read_position;
+    $maxlength ||= $CHUNKSIZE;
+
+    # Are we done reading?
+    if ( $remaining <= 0 ) {
+        return;
+    }
+
+    my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
+    my $rc = $self->read_chunk( my $buffer, $readlen );
+    if ( defined $rc ) {
+        if (0 == $rc) { # Nothing more to read even though Content-Length
+                        # said there should be.
+            return;
+        }
+        $self->_read_position( $self->_read_position + $rc );
+        return $buffer;
+    }
+    else {
+        Catalyst::Exception->throw(
+            message => "Unknown error reading input: $!" );
+    }
+}
+
+sub read_chunk {
+    my $self = shift;
+    return $self->env->{'psgi.input'}->read(@_);
+}
+
 has body_parameters => (
   is => 'rw',
   required => 1,
@@ -57,10 +135,9 @@ has uploads => (
 );
 
 has parameters => (
-  is => 'rw',
-  required => 1,
-  lazy => 1,
-  default => sub { {} },
+    is => 'rw',
+    lazy => 1,
+    builder => 'prepare_parameters',
 );
 
 # TODO:
@@ -71,17 +148,124 @@ has parameters => (
 #  these lazy build from there and kill all the direct hash access
 #  in Catalyst.pm and Engine.pm?
 
-before $_ => sub {
+sub prepare_parameters {
+    my ( $self ) = @_;
+
+    $self->prepare_body;
+    my $parameters = {};
+    my $body_parameters = $self->body_parameters;
+    my $query_parameters = $self->query_parameters;
+    # We copy, no references
+    foreach my $name (keys %$query_parameters) {
+        my $param = $query_parameters->{$name};
+        $parameters->{$name} = ref $param eq 'ARRAY' ? [ @$param ] : $param;
+    }
+
+    # Merge query and body parameters
+    foreach my $name (keys %$body_parameters) {
+        my $param = $body_parameters->{$name};
+        my @values = ref $param eq 'ARRAY' ? @$param : ($param);
+        if ( my $existing = $parameters->{$name} ) {
+          unshift(@values, (ref $existing eq 'ARRAY' ? @$existing : $existing));
+        }
+        $parameters->{$name} = @values > 1 ? \@values : $values[0];
+    }
+    $parameters;
+}
+
+before body_parameters => sub {
     my ($self) = @_;
-    my $context = $self->_context || return;
-    $context->prepare_body;
-} for qw/parameters body_parameters/;
+    $self->prepare_body;
+    $self->prepare_body_parameters;
+};
+
+=head2 $self->prepare_body()
+
+sets up the L<Catalyst::Request> object body using L<HTTP::Body>
+
+=cut
+
+has _uploadtmp => (
+    is => 'ro',
+    predicate => '_has_uploadtmp',
+);
+
+sub prepare_body {
+    my ( $self ) = @_;
+
+    if ( my $length = $self->_read_length ) {
+        unless ( $self->_body ) {
+            my $type = $self->header('Content-Type');
+            $self->_body(HTTP::Body->new( $type, $length ));
+            $self->_body->cleanup(1); # Make extra sure!
+            $self->_body->tmpdir( $self->_uploadtmp )
+              if $self->_has_uploadtmp;
+        }
+
+        # Check for definedness as you could read '0'
+        while ( defined ( my $buffer = $self->read() ) ) {
+            $self->prepare_body_chunk($buffer);
+        }
+
+        # paranoia against wrong Content-Length header
+        my $remaining = $length - $self->_read_position;
+        if ( $remaining > 0 ) {
+            Catalyst::Exception->throw(
+                "Wrong Content-Length value: $length" );
+        }
+    }
+    else {
+        # Defined but will cause all body code to be skipped
+        $self->_body(0);
+    }
+}
+
+=head2 $self->prepare_body_chunk()
+
+Add a chunk to the request body.
+
+=cut
+
+sub prepare_body_chunk {
+    my ( $self, $chunk ) = @_;
 
+    $self->_body->add($chunk);
+}
+
+=head2 $self->prepare_body_parameters()
+
+Sets up parameters from body.
+
+=cut
+
+sub prepare_body_parameters {
+    my ( $self ) = @_;
+
+    return unless $self->_body;
+
+    $self->{body_parameters} = $self->_body->param; # FIXME!! Recursion here.
+}
+
+sub prepare_connection {
+    my ($self) = @_;
+
+    my $env = $self->env;
+
+    $self->address( $env->{REMOTE_ADDR} );
+    $self->hostname( $env->{REMOTE_HOST} )
+        if exists $env->{REMOTE_HOST};
+    $self->protocol( $env->{SERVER_PROTOCOL} );
+    $self->remote_user( $env->{REMOTE_USER} );
+    $self->method( $env->{REQUEST_METHOD} );
+    $self->secure( $env->{'psgi.url_scheme'} eq 'https' ? 1 : 0 );
+}
+
+# XXX - FIXME - method is here now, move this crap...
 around parameters => sub {
     my ($orig, $self, $params) = @_;
     if ($params) {
         if ( !ref $params ) {
-            $self->_context->log->warn(
+            $self->_log->warn(
                 "Attempt to retrieve '$params' with req->params(), " .
                 "you probably meant to call req->param('$params')"
             );
@@ -109,8 +293,8 @@ has _body => (
 #             and provide a custom reader..
 sub body {
   my $self = shift;
-  $self->_context->prepare_body();
-  $self->_body(@_) if scalar @_;
+  $self->prepare_body();
+  croak 'body is a reader' if scalar @_;
   return blessed $self->_body ? $self->_body->body : $self->_body;
 }
 
@@ -120,7 +304,7 @@ has hostname => (
   lazy      => 1,
   default   => sub {
     my ($self) = @_;
-    gethostbyaddr( inet_aton( $self->address ), AF_INET ) || 'localhost'
+    gethostbyaddr( inet_aton( $self->address ), AF_INET ) || $self->address
   },
 );
 
@@ -138,6 +322,8 @@ sub query_params    { shift->query_parameters(@_) }
 sub path_info       { shift->path(@_) }
 sub snippets        { shift->captures(@_) }
 
+=for stopwords param params
+
 =head1 NAME
 
 Catalyst::Request - provides information about the current client request
@@ -210,7 +396,7 @@ Returns a reference to an array containing the arguments.
 
 For example, if your action was
 
-    package MyApp::C::Foo;
+    package MyApp::Controller::Foo;
 
     sub moose : Local {
         ...
@@ -228,7 +414,7 @@ Shortcut for L</arguments>.
 =head2 $req->base
 
 Contains the URI base. This will always have a trailing slash. Note that the
-URI scheme (eg., http vs. https) must be determined through heuristics;
+URI scheme (e.g., http vs. https) must be determined through heuristics;
 depending on your server configuration, it may be incorrect. See $req->secure
 for more info.
 
@@ -316,7 +502,7 @@ Returns an L<HTTP::Headers> object containing the headers for the current reques
 
 =head2 $req->hostname
 
-Returns the hostname of the client.
+Returns the hostname of the client. Use C<< $req->uri->host >> to get the hostname of the server.
 
 =head2 $req->input
 
@@ -427,6 +613,10 @@ Shortcut for $req->parameters.
 
 Returns the path, i.e. the part of the URI after $req->base, for the current request.
 
+    http://localhost/path/foo
+
+    $c->request->path will contain 'path/foo'
+
 =head2 $req->path_info
 
 Alias for path, added for compatibility with L<CGI>.
@@ -474,6 +664,10 @@ Reads a chunk of data from the request body. This method is intended to be
 used in a while loop, reading $maxlength bytes on every call. $maxlength
 defaults to the size of the request if not specified.
 
+=head2 $req->read_chunk(\$buff, $max)
+
+Reads a chunk..
+
 You have to set MyApp->config(parse_on_demand => 1) to use this directly.
 
 =head2 $req->referer
@@ -483,8 +677,8 @@ Shortcut for $req->headers->referer. Returns the referring page.
 =head2 $req->secure
 
 Returns true or false, indicating whether the connection is secure
-(https). Note that the URI scheme (eg., http vs. https) must be determined
-through heuristics, and therefore the reliablity of $req->secure will depend
+(https). Note that the URI scheme (e.g., http vs. https) must be determined
+through heuristics, and therefore the reliability of $req->secure will depend
 on your server configuration. If you are serving secure pages on the standard
 SSL port (443) and/or setting the HTTPS environment variable, $req->secure
 should be valid.
@@ -673,14 +867,6 @@ sub uri_with {
     return $uri;
 }
 
-=head2 $req->user
-
-Returns the currently logged in user. B<Highly deprecated>, do not call,
-this will be removed in version 5.81. To retrieve the currently authenticated
-user, see C<< $c->user >> and C<< $c->user_exists >> in
-L<Catalyst::Plugin::Authentication>. For the C<REMOTE_USER> provided by the
-webserver, see C<< $req->remote_user >> below.
-
 =head2 $req->remote_user
 
 Returns the value of the C<REMOTE_USER> environment variable.