use Data::Dump qw/dump/;
use Errno 'EWOULDBLOCK';
use HTML::Entities;
-use HTTP::Body;
use HTTP::Headers;
-use URI::QueryParam;
use Plack::Loader;
use Catalyst::EngineLoader;
-use Encode ();
-use utf8;
-
+use Encode 2.21 'decode_utf8';
+use Plack::Request::Upload;
+use Hash::MultiValue;
use namespace::clean -except => 'meta';
+use utf8;
# Amount of data to read from input on each pass
our $CHUNKSIZE = 64 * 1024;
# XXX - this is only here for compat, do not use!
-has env => ( is => 'rw', writer => '_set_env' );
+has env => ( is => 'rw', writer => '_set_env' , weak_ref=>1);
my $WARN_ABOUT_ENV = 0;
around env => sub {
my ($orig, $self, @args) = @_;
Finalize body. Prints the response output as blocking stream if it looks like
a filehandle, otherwise write it out all in one go. If there is no body in
the response, we assume you are handling it 'manually', such as for nonblocking
-style or asynchronous streaming responses.
-
-By default we do not close the writer object in case we are in an event loop
-and there is deferred activity. However if you have some sloppy code that is
-closing over an unweakened context ($c) this could lead to the writer NEVER
-being closed. In versions of Catalyst 5.90030 and older, we used to forcibly
-close the writer in this method, but we no longer do that since it prevented us
-from introducing proper asynchronous support in Catalyst core. If you have old
-code that is leaking context but was otherwise working and you don't want to fix
-your memory leaks (is really the best idea) you can force enable the old
-behavior (and lose asynchronous support) by setting the global configuration key
-C<aggressively_close_writer_on_finalize_body> to true. See L<Catalyst::Upgrading>
-for more if you have this issue.
+style or asynchronous streaming responses. You do this by calling L</write>
+several times (which sends HTTP headers if needed) or you close over
+C<< $response->write_fh >>.
+
+See L<Catalyst::Response/write> and L<Catalyst::Response/write_fh> for more.
=cut
sub finalize_body {
my ( $self, $c ) = @_;
- my $body = $c->response->body;
- no warnings 'uninitialized';
- if ( blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
- my $got;
- do {
- $got = read $body, my ($buffer), $CHUNKSIZE;
- $got = 0 unless $self->write( $c, $buffer );
- } while $got > 0;
-
- close $body;
- }
- else {
- $self->write( $c, $body );
- }
+ my $res = $c->response; # We use this all over
+
+ ## If we've asked for the write 'filehandle' that means the application is
+ ## doing something custom and is expected to close the response
+ return if $res->_has_write_fh;
+
+ my $body = $res->body; # save some typing
+ if($res->_has_response_cb) {
+ ## we have not called the response callback yet, so we are safe to send
+ ## the whole body to PSGI
+
+ my @headers;
+ $res->headers->scan(sub { push @headers, @_ });
+
+ # We need to figure out what kind of body we have and normalize it to something
+ # PSGI can deal with
+ if(defined $body) {
+ # Handle objects first
+ if(blessed($body)) {
+ if($body->can('getline')) {
+ # Body is an IO handle that meets the PSGI spec. Nothing to normalize
+ } elsif($body->can('read')) {
+
+ # In the past, Catalyst only looked for ->read not ->getline. It is very possible
+ # that one might have an object that respected read but did not have getline.
+ # As a result, we need to handle this case for backcompat.
+
+ # We will just do the old loop for now. In a future version of Catalyst this support
+ # will be removed and one will have to rewrite their custom object or use
+ # Plack::Middleware::AdaptFilehandleRead. In anycase support for this is officially
+ # deprecated and described as such as of 5.90060
+
+ my $got;
+ do {
+ $got = read $body, my ($buffer), $CHUNKSIZE;
+ $got = 0 unless $self->write($c, $buffer );
+ } while $got > 0;
+
+ close $body;
+ return;
+ } else {
+ # Looks like for backcompat reasons we need to be able to deal
+ # with stringyfiable objects.
+ $body = ["$body"];
+ }
+ } elsif(ref $body) {
+ if( (ref($body) eq 'GLOB') or (ref($body) eq 'ARRAY')) {
+ # Again, PSGI can just accept this, no transform needed. We don't officially
+ # document the body as arrayref at this time (and there's not specific test
+ # cases. we support it because it simplifies some plack compatibility logic
+ # and we might make it official at some point.
+ } else {
+ $c->log->error("${\ref($body)} is not a valid value for Response->body");
+ return;
+ }
+ } else {
+ # Body is defined and not an object or reference. We assume a simple value
+ # and wrap it in an array for PSGI
+ $body = [$body];
+ }
+ } else {
+ # There's no body...
+ $body = [];
+ }
+ $res->_response_cb->([ $res->status, \@headers, $body]);
+ $res->_clear_response_cb;
+
+ } else {
+ ## Now, if there's no response callback anymore, that means someone has
+ ## called ->write in order to stream 'some stuff along the way'. I think
+ ## for backcompat we still need to handle a ->body. I guess I could see
+ ## someone calling ->write to presend some stuff, and then doing the rest
+ ## via ->body, like in a template.
+
+ ## We'll just use the old, existing code for this (or most of it)
+
+ if(my $body = $res->body) {
+
+ if ( blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
+
+ ## In this case we have no choice and will fall back on the old
+ ## manual streaming stuff. Not optimal. This is deprecated as of 5.900560+
+
+ my $got;
+ do {
+ $got = read $body, my ($buffer), $CHUNKSIZE;
+ $got = 0 unless $self->write($c, $buffer );
+ } while $got > 0;
+
+ close $body;
+ }
+ else {
+
+ # Case where body was set afgter calling ->write. We'd prefer not to
+ # support this, but I can see some use cases with the way most of the
+ # views work.
+
+ $self->write($c, $body );
+ }
+ }
- if($c->config->{aggressively_close_writer_on_finalize_body}) {
- my $res = $c->response;
- $res->_writer->close;
- $res->_clear_writer;
+ $res->_writer->close;
+ $res->_clear_writer;
}
return;
(pt) Por favor volte mais tarde
(ru) Попробуйте еще раз позже
(ua) Спробуйте ще раз пізніше
+(it) Per favore riprova più tardi
</pre>
$name = '';
sub prepare_query_parameters {
my ($self, $c) = @_;
-
my $env = $c->request->env;
+
my $query_string = exists $env->{QUERY_STRING}
? $env->{QUERY_STRING}
: '';
# Check for keywords (no = signs)
# (yes, index() is faster than a regex :))
if ( index( $query_string, '=' ) < 0 ) {
- $c->request->query_keywords( $self->unescape_uri($query_string) );
+ my $keywords = $self->unescape_uri($query_string);
+ $keywords = decode_utf8 $keywords;
+ $c->request->query_keywords($keywords);
return;
}
- my %query;
+ $query_string =~ s/\A[&;]+//;
- # replace semi-colons
- $query_string =~ s/;/&/g;
+ my $p = Hash::MultiValue->new(
+ map { defined $_ ? decode_utf8($self->unescape_uri($_)) : $_ }
+ map { ( split /=/, $_, 2 )[0,1] } # slice forces two elements
+ split /[&;]+/, $query_string
+ );
- my @params = grep { length $_ } split /&/, $query_string;
-
- for my $item ( @params ) {
-
- my ($param, $value)
- = map { $self->unescape_uri($_) }
- split( /=/, $item, 2 );
-
- $param = $self->unescape_uri($item) unless defined $param;
-
- if ( exists $query{$param} ) {
- if ( ref $query{$param} ) {
- push @{ $query{$param} }, $value;
- }
- else {
- $query{$param} = [ $query{$param}, $value ];
- }
- }
- else {
- $query{$param} = $value;
- }
- }
- $c->request->query_parameters( \%query );
+ $c->request->query_parameters( $c->request->_use_hash_multivalue ? $p : $p->mixed );
}
=head2 $self->prepare_read($c)
my $request = $c->request;
return unless $request->_body;
+ my $enc = $c->encoding;
my $uploads = $request->_body->upload;
my $parameters = $request->parameters;
foreach my $name (keys %$uploads) {
+ $name = $c->_handle_unicode_decoding($name) if $enc;
my $files = $uploads->{$name};
my @uploads;
for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
+ my $filename = $upload->{filename};
+ $filename = $c->_handle_unicode_decoding($filename) if $enc;
+
my $u = Catalyst::Request::Upload->new
(
size => $upload->{size},
type => scalar $headers->content_type,
+ charset => scalar $headers->content_type_charset,
headers => $headers,
tempname => $upload->{tempname},
- filename => $upload->{filename},
+ filename => $filename,
);
push @uploads, $u;
}
return sub {
my ($respond) = @_;
- confess("Did not get a response callback for writer, cannot continiue") unless $respond;
+ confess("Did not get a response callback for writer, cannot continue") unless $respond;
$app->handle_request(env => $env, response_cb => $respond);
};
};