fixed another regression
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Response.pm
index 28cf7b7..74d59fe 100644 (file)
@@ -67,7 +67,7 @@ sub _build_write_fh {
   my $requires_encoding = $_[0]->encodable_response;
   my %fields = (
     _writer => $writer,
-    _encoding => $_[0]->_context->encoding,
+    _context => $_[0]->_context,
     _requires_encoding => $requires_encoding,
   );
 
@@ -148,7 +148,10 @@ sub from_psgi_response {
         my ($status, $headers, $body) = @$psgi_res;
         $self->status($status);
         $self->headers(HTTP::Headers->new(@$headers));
-        $self->body($body);
+        # Can be arrayref or filehandle...
+        if(defined $body) { # probably paranoia
+          ref $body eq 'ARRAY' ? $self->body(join('', @$body)) : $self->body($body);
+        }
     } elsif(ref $psgi_res eq 'CODE') {
         $psgi_res->(sub {
             my $response = shift;
@@ -156,7 +159,8 @@ sub from_psgi_response {
             $self->status($status);
             $self->headers(HTTP::Headers->new(@$headers));
             if(defined $maybe_body) {
-                $self->body($maybe_body);
+                # Can be arrayref or filehandle...
+                ref $maybe_body eq 'ARRAY' ? $self->body(join('', @$maybe_body)) : $self->body($maybe_body);
             } else {
                 return $self->write_fh;
             }
@@ -164,6 +168,15 @@ sub from_psgi_response {
      } else {
         die "You can't set a Catalyst response from that, expect a valid PSGI response";
     }
+
+    # Encoding compatibilty.   If the response set a charset, well... we need
+    # to assume its properly encoded and NOT encode for this response.  Otherwise
+    # We risk double encoding.
+    if($self->content_type_charset) {
+      # We have to do this since for backcompat reasons having a charset doesn't always
+      # mean that the body is already encoded :(
+      $self->_context->clear_encoding;
+    }
 }
 
 =head1 NAME
@@ -199,12 +212,13 @@ will turn the Catalyst::Response into a HTTP Response and return it to the clien
     $c->response->body('Catalyst rocks!');
 
 Sets or returns the output (text or binary data). If you are returning a large body,
-you might want to use a L<IO::Handle> type of object (Something that implements the read method
-in the same fashion), or a filehandle GLOB. Catalyst
-will write it piece by piece into the response.
+you might want to use a L<IO::Handle> type of object (Something that implements the getline method 
+in the same fashion), or a filehandle GLOB. These will be passed down to the PSGI
+handler you are using and might be optimized using server specific abilities (for
+example L<Twiggy> will attempt to server a real local file in a non blocking manner).
 
 If you are using a filehandle as the body response you are responsible for
-making sure it comforms to the L<PSGI> specification with regards to content
+making sure it conforms to the L<PSGI> specification with regards to content
 encoding.  Unlike with scalar body values or when using the streaming interfaces
 we currently do not attempt to normalize and encode your filehandle.  In general
 this means you should be sure to be sending bytes not UTF8 decoded multibyte
@@ -228,7 +242,11 @@ it is recommended that you set the content length in the response headers
 yourself, which will be respected and sent by Catalyst in the response.
 
 Please note that the object needs to implement C<getline>, not just
-C<read>.
+C<read>.  Older versions of L<Catalyst> expected your filehandle like objects
+to do read.  If you have code written for this expectation and you cannot
+change the code to meet the L<PSGI> specification, you can try the following
+middleware L<Plack::Middleware::AdaptFilehandleRead> which will attempt to
+wrap your object in an interface that so conforms.
 
 Starting from version 5.90060, when using an L<IO::Handle> object, you
 may want to use L<Plack::Middleware::XSendfile>, to delegate the
@@ -537,7 +555,13 @@ Example:
     }
 
 Please note this does not attempt to map or nest your PSGI application under
-the Controller and Action namespace or path.  
+the Controller and Action namespace or path. You may wish to review 'PSGI Helpers'
+under L<Catalyst::Utils> for help in properly nesting applications.
+
+B<NOTE> If your external PSGI application returns a response that has a character
+set associated with the content type (such as "text/html; charset=UTF-8") we set
+$c->clear_encoding to remove any additional content type encoding processing later
+in the application (this is done to avoid double encoding issues).
 
 =head2 encodable_content_type
 
@@ -584,16 +608,28 @@ sub encodable_response {
   return 0 unless $self->_context; # Cases like returning a HTTP Exception response you don't have a context here...
   return 0 unless $self->_context->encoding;
 
+  # The response is considered to have a 'manual charset' when a charset is already set on
+  # the content type of the response AND it is not the same as the one we set in encoding.
+  # If there is no charset OR we are asking for the one which is the same as the current
+  # required encoding, that is a flag that we want Catalyst to encode the response automatically.
   my $has_manual_charset = 0;
   if(my $charset = $self->content_type_charset) {
     $has_manual_charset = (uc($charset) ne uc($self->_context->encoding->mime_name)) ? 1:0;
   }
 
+  # Content type is encodable if it matches the regular expression stored in this attribute
+  my $encodable_content_type = $self->content_type =~ m/${\$self->encodable_content_type}/ ? 1:0;
+
+  # The content encoding is allowed (for charset encoding) only if its empty or is set to identity
+  my $allowed_content_encoding = (!$self->content_encoding || $self->content_encoding eq 'identity') ? 1:0;
+
+  # The content type must be an encodable type, and there must be NO manual charset and also
+  # the content encoding must be the allowed values;
   if(
-      ($self->content_type =~ m/${\$self->encodable_content_type}/) and
-      (!$has_manual_charset) and
-      (!$self->content_encoding || $self->content_encoding eq 'identity' )
-  ) { 
+      $encodable_content_type and
+      !$has_manual_charset and
+      $allowed_content_encoding
+  ) {
     return 1;
   } else {
     return 0;