use URI::https;
use URI::QueryParam;
use HTTP::Headers;
+use Stream::Buffered;
+use Hash::MultiValue;
+use Scalar::Util;
use Moose;
with 'MooseX::Emulate::Class::Accessor::Fast';
-has env => (is => 'ro', writer => '_set_env');
+has env => (is => 'ro', writer => '_set_env', predicate => 'has_env');
# XXX Deprecated crap here - warn?
has action => (is => 'rw');
# XXX: Deprecated in docs ages ago (2006), deprecated with warning in 5.8000 due
required => 1,
);
+has io_fh => (
+ is=>'ro',
+ predicate=>'has_io_fh',
+ lazy=>1,
+ builder=>'_build_io_fh');
+
+sub _build_io_fh {
+ my $self = shift;
+ return $self->env->{'psgix.io'}
+ || (
+ $self->env->{'net.async.http.server.req'} &&
+ $self->env->{'net.async.http.server.req'}->stream) ## Until I can make ioasync cabal see the value of supportin psgix.io (jnap)
+ || die "Your Server does not support psgix.io";
+};
+
+has data_handlers => ( is=>'ro', isa=>'HashRef', default=>sub { +{} } );
+
+has body_data => (
+ is=>'ro',
+ lazy=>1,
+ builder=>'_build_body_data');
+
+sub _build_body_data {
+ my ($self) = @_;
+ my $content_type = $self->content_type;
+ my ($match) = grep { $content_type =~/$_/i }
+ keys(%{$self->data_handlers});
+
+ if($match) {
+ my $fh = $self->body;
+ local $_ = $fh;
+ return $self->data_handlers->{$match}->($fh, $self);
+ } else {
+ return undef;
+ }
+}
+
# Amount of data to read from input on each pass
our $CHUNKSIZE = 64 * 1024;
return $self->parameters;
}
-
-
sub _build_parameters {
my ( $self ) = @_;
my $parameters = {};
my $body_parameters = $self->body_parameters;
my $query_parameters = $self->query_parameters;
+
+ ## setup for downstream plack
+ $self->env->{'plack.request.merged'} ||= do {
+ my $query = $self->env->{'plack.request.query'} || Hash::MultiValue->new;
+ my $body = $self->env->{'plack.request.body'} || Hash::MultiValue->new;
+ Hash::MultiValue->new($query->flatten, $body->flatten);
+ };
+
# We copy, no references
foreach my $name (keys %$query_parameters) {
my $param = $query_parameters->{$name};
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;
- }
+ # If previously applied middleware created the HTTP::Body object, then we
+ # just use that one.
- # Check for definedness as you could read '0'
- while ( defined ( my $buffer = $self->read() ) ) {
- $self->prepare_body_chunk($buffer);
- }
+ if(my $plack_body = $self->env->{'plack.request.http.body'}) {
+ $self->_body($plack_body);
+ $self->_body->cleanup(1);
+ return;
+ }
- # paranoia against wrong Content-Length header
- my $remaining = $length - $self->_read_position;
- if ( $remaining > 0 ) {
- Catalyst::Exception->throw(
- "Wrong Content-Length value: $length" );
- }
+ # Define PSGI ENV placeholders, or for empty should there be no content
+ # body (typical in HEAD or GET). Looks like from Plack::Request that
+ # middleware would probably expect to see this, even if empty
+
+ $self->env->{'plack.request.body'} = Hash::MultiValue->new;
+ $self->env->{'plack.request.upload'} = Hash::MultiValue->new;
+
+ # If there is nothing to read, set body to naught and return. This
+ # will cause all body code to be skipped
+
+ return $self->_body(0) unless my $length = $self->_read_length;
+
+ # Unless the body has already been set, create it. Not sure about this
+ # code, how else might it be set, but this was existing logic.
+
+ unless ($self->_body) {
+ my $type = $self->header('Content-Type');
+ $self->_body(HTTP::Body->new( $type, $length ));
+ $self->_body->cleanup(1);
+
+ # JNAP: I'm not sure this is doing what we expect, but it also doesn't
+ # seem to be hurting (seems ->_has_uploadtmp is true more than I would
+ # expect.
+
+ $self->_body->tmpdir( $self->_uploadtmp )
+ if $self->_has_uploadtmp;
}
- else {
- # Defined but will cause all body code to be skipped
- $self->_body(0);
+
+ # Ok if we get this far, we have to read psgi.input into the new body
+ # object. Lets play nice with any plack app or other downstream, so
+ # we create a buffer unless one exists.
+
+ my $stream_buffer;
+ if ($self->env->{'psgix.input.buffered'}) {
+ # Be paranoid about previous psgi middleware or apps that read the
+ # input but didn't return the buffer to the start.
+ $self->env->{'psgi.input'}->seek(0, 0);
+ } else {
+ $stream_buffer = Stream::Buffered->new($length);
+ }
+
+ # Check for definedness as you could read '0'
+ while ( defined ( my $chunk = $self->read() ) ) {
+ $self->prepare_body_chunk($chunk);
+ $stream_buffer->print($chunk) if $stream_buffer;
+ }
+
+ # Ok, we read the body. Lets play nice for any PSGI app down the pipe
+
+ if ($stream_buffer) {
+ $self->env->{'psgix.input.buffered'} = 1;
+ $self->env->{'psgi.input'} = $stream_buffer->rewind;
+ } else {
+ $self->env->{'psgi.input'}->seek(0, 0); # Reset the buffer for downstream middleware or apps
+ }
+
+ $self->env->{'plack.request.http.body'} = $self->_body;
+ $self->env->{'plack.request.body'} = Hash::MultiValue->from_mixed($self->_body->param);
+
+ # paranoia against wrong Content-Length header
+ my $remaining = $length - $self->_read_position;
+ if ( $remaining > 0 ) {
+ Catalyst::Exception->throw("Wrong Content-Length value: $length" );
}
}
# and provide a custom reader..
sub body {
my $self = shift;
- $self->prepare_body unless ! $self->_has_body;
+ $self->prepare_body unless $self->_has_body;
croak 'body is a reader' if scalar @_;
return blessed $self->_body ? $self->_body->body : $self->_body;
}
$req->args;
$req->base;
$req->body;
+ $req->body_data;
$req->body_parameters;
$req->content_encoding;
$req->content_length;
unless Content-Type is C<application/x-www-form-urlencoded>, C<text/xml>, or
C<multipart/form-data>, in which case a L<File::Temp> object is returned.
+=head2 $req->body_data
+
+Returns a Perl representation of POST/PUT body data that is not classic HTML
+form data, such as JSON, XML, etc. By default, Catalyst will parse incoming
+data of the type 'application/json' and return access to that data via this
+method. You may define addition data_handlers via a global configuration
+setting. See L<Catalyst\DATA HANDLERS> for more information.
+
=head2 $req->body_parameters
Returns a reference to a hash containing body (POST) parameters. Values can
Shortcut to $req->headers->user_agent. Returns the user agent (browser)
version string.
+=head2 $req->io_fh
+
+Returns a psgix.io bidirectional socket, if your server supports one. Used for
+when you want to jailbreak out of PSGI and handle bidirectional client server
+communication manually, such as when you are using cometd or websockets.
+
=head1 SETUP METHODS
You should never need to call these yourself in application code,