starting to prep release version info
[catagits/Catalyst-Runtime.git] / lib / Catalyst.pm
index 725494c..3ef4bc8 100644 (file)
@@ -86,8 +86,10 @@ has response => (
     lazy => 1,
 );
 sub _build_response_constructor_args {
-    my $self = shift;
-    { _log => $self->log };
+    return +{
+      _log => $_[0]->log,
+      encoding => $_[0]->encoding,
+    };
 }
 
 has namespace => (is => 'rw');
@@ -127,7 +129,7 @@ __PACKAGE__->stats_class('Catalyst::Stats');
 __PACKAGE__->_encode_check(Encode::FB_CROAK | Encode::LEAVE_SRC);
 
 # Remember to update this in Catalyst::Runtime as well!
-our $VERSION = '5.90080_001';
+our $VERSION = '5.90079_004';
 $VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases
 
 sub import {
@@ -1011,12 +1013,31 @@ And later:
 Your log class should implement the methods described in
 L<Catalyst::Log>.
 
+=head2 has_encoding
+
+Returned True if there's a valid encoding
+
+=head2 clear_encoding
+
+Clears the encoding for the current context
+
 =head2 encoding
 
 Sets or gets the application encoding.
 
 =cut
 
+sub has_encoding { shift->encoding ? 1:0 }
+
+sub clear_encoding {
+    my $c = shift;
+    if(blessed $c) {
+        $c->encoding(undef);
+    } else {
+        $c->debug->error("You can't clear encoding on the application");
+    }
+}
+
 sub encoding {
     my $c = shift;
     my $encoding;
@@ -1333,6 +1354,8 @@ sub setup_finalize {
 
 =head2 $c->uri_for( $action, \@captures?, @args?, \%query_values? )
 
+=head2 $c->uri_for( $action, [@captures, @args], \%query_values? )
+
 Constructs an absolute L<URI> object based on the application root, the
 provided path, and the additional arguments and query parameters provided.
 When used as a string, provides a textual URI.  If you need more flexibility
@@ -1372,6 +1395,10 @@ path, use C<< $c->uri_for_action >> instead.
   # Path to a static resource
   $c->uri_for('/static/images/logo.png');
 
+In general the scheme of the generated URI object will follow the incoming request
+however if your targeted action or action chain has the Scheme attribute it will
+use that instead.
+
 =cut
 
 sub uri_for {
@@ -1407,6 +1434,7 @@ sub uri_for {
       }
     }
 
+    my $target_action = $path->$_isa('Catalyst::Action') ? $path : undef;
     if ( $path->$_isa('Catalyst::Action') ) { # action object
         s|/|%2F|g for @encoded_args;
         my $captures = [ map { s|/|%2F|g; $_; }
@@ -1418,7 +1446,6 @@ sub uri_for {
         # ->uri_for( $action, \@captures_and_args, \%query_values? )
         if( !@encoded_args && $action->number_of_args ) {
             my $expanded_action = $c->dispatcher->expand_action( $action );
-
             my $num_captures = $expanded_action->number_of_captures;
             unshift @encoded_args, splice @$captures, $num_captures;
         }
@@ -1450,7 +1477,19 @@ sub uri_for {
     my ($base, $class) = ('/', 'URI::_generic');
     if(blessed($c)) {
       $base = $c->req->base;
-      $class = ref($base);
+      if($target_action) {
+        $target_action = $c->dispatcher->expand_action($target_action);
+        if(my $s = $target_action->scheme) {
+          $s = lc($s);
+          $class = "URI::$s";
+          $base->scheme($s);
+        } else {
+          $class = ref($base);
+        }
+      } else {
+        $class = ref($base);
+      }
+
       $base =~ s{(?<!/)$}{/};
     }
 
@@ -1802,7 +1841,15 @@ sub execute {
 
     if ( my $error = $@ ) {
         #rethow if this can be handled by middleware
-        if(blessed $error && ($error->can('as_psgi') || $error->can('code'))) {
+        if(
+          blessed $error && (
+            $error->can('as_psgi') ||
+            (
+              $error->can('code') &&
+              $error->code =~m/^[1-5][0-9][0-9]$/
+            )
+          )
+        ) {
             foreach my $err (@{$c->error}) {
                 $c->log->error($err);
             }
@@ -2021,6 +2068,8 @@ sub finalize_headers {
 
     $c->finalize_cookies;
 
+    # This currently is a NOOP but I don't want to remove it since I guess people
+    # might have Response subclasses that use it for something... (JNAP)
     $c->response->finalize_headers();
 
     # Done
@@ -2029,43 +2078,49 @@ sub finalize_headers {
 
 =head2 $c->finalize_encoding
 
-Make sure your headers and body are encoded properly IF you set an encoding.
+Make sure your body is encoded properly IF you set an encoding.  By
+default the encoding is UTF-8 but you can disable it by explictly setting the
+encoding configuration value to undef.
+
+We can only encode when the body is a scalar.  Methods for encoding via the
+streaming interfaces (such as C<write> and C<write_fh> on L<Catalyst::Response>
+are available).
+
 See L</ENCODING>.
 
 =cut
 
 sub finalize_encoding {
     my $c = shift;
-
-    my $body = $c->response->body;
-
-    return unless defined($body);
-
-    my $enc = $c->encoding;
-
-    return unless $enc;
-
-    my ($ct, $ct_enc) = $c->response->content_type;
-
-    # Only touch 'text-like' contents
-    return unless $c->response->content_type =~ /^text|xml$|javascript$/;
-
-    if ($ct_enc && $ct_enc =~ /charset=([^;]*)/) {
-        if (uc($1) ne uc($enc->mime_name)) {
-            $c->log->debug("Unicode::Encoding is set to encode in '" .
-                           $enc->mime_name .
-                           "', content type is '$1', not encoding ");
-            return;
-        }
-    } else {
-        $c->res->content_type($c->res->content_type . "; charset=" . $enc->mime_name);
+    my $res = $c->res || return;
+
+    # Warn if the set charset is different from the one you put into encoding.  We need
+    # to do this early since encodable_response is false for this condition and we need
+    # to match the debug output for backcompat (there's a test for this...) -JNAP
+    if(
+      $res->content_type_charset and $c->encoding and 
+      (uc($c->encoding->mime_name) ne uc($res->content_type_charset))
+    ) {
+        my $ct = lc($res->content_type_charset);
+        $c->log->debug("Catalyst encoding config is set to encode in '" .
+            $c->encoding->mime_name .
+            "', content type is '$ct', not encoding ");
     }
 
-    # Oh my, I wonder what filehandle responses and streams do... - jnap.
-    # Encode expects plain scalars (IV, NV or PV) and segfaults on ref's
-    if (ref(\$body) eq 'SCALAR') {
-      $c->response->body( $c->encoding->encode( $body, $c->_encode_check ) );
-    };
+    if(
+      ($res->encodable_response) and
+      (defined($res->body)) and
+      (ref(\$res->body) eq 'SCALAR')
+    ) {
+        $c->res->body( $c->encoding->encode( $c->res->body, $c->_encode_check ) );
+
+        # Set the charset if necessary.  This might be a bit bonkers since encodable response
+        # is false when the set charset is not the same as the encoding mimetype (maybe 
+        # confusing action at a distance here..
+        # Don't try to set the charset if one already exists
+        $c->res->content_type($c->res->content_type . "; charset=" . $c->encoding->mime_name)
+          unless($c->res->content_type_charset);
+    }
 }
 
 =head2 $c->finalize_output
@@ -2129,7 +2184,15 @@ sub handle_request {
         $status = $c->finalize;
     } catch {
         #rethow if this can be handled by middleware
-        if(blessed $_ && ($_->can('as_psgi') || $_->can('code'))) {
+        if(
+          blessed($_) && (
+            $_->can('as_psgi') ||
+            (
+              $_->can('code') &&
+              $_->code =~m/^[1-5][0-9][0-9]$/
+            )
+          )
+        ) {
             $_->can('rethrow') ? $_->rethrow : croak $_;
         }
         chomp(my $error = $_);
@@ -3045,9 +3108,14 @@ Sets up the input/output encoding.  See L<ENCODING>
 
 sub setup_encoding {
     my $c = shift;
-    # This is where you'd set a default encoding
-    my $enc = delete $c->config->{encoding};
-    $c->encoding( $enc ) if defined $enc;
+    if( exists($c->config->{encoding}) && !defined($c->config->{encoding}) ) {
+        # Ok, so the user has explicitly said "I don't want encoding..."
+        return;
+    } else {
+      my $enc = defined($c->config->{encoding}) ?
+        delete $c->config->{encoding} : 'UTF-8'; # not sure why we delete it... (JNAP)
+      $c->encoding($enc);
+    }
 }
 
 =head2 handle_unicode_encoding_exception
@@ -3603,6 +3671,9 @@ C<using_frontend_proxy> - See L</PROXY SUPPORT>.
 
 C<encoding> - See L</ENCODING>
 
+This now defaults to 'UTF-8'.  You my turn it off by setting this configuration
+value to undef.
+
 =item *
 
 C<abort_chain_on_error_fix>
@@ -3948,6 +4019,36 @@ Please see L<PSGI> for more on middleware.
 On request, decodes all params from encoding into a sequence of
 logical characters. On response, encodes body into encoding.
 
+By default encoding is now 'UTF-8'.  You may turn it off by setting
+the encoding configuration to undef.
+
+Encoding is automatically applied when the content-type is set to
+a type that can be encoded.  Currently we encode when the content type
+matches the following regular expression:
+
+    $content_type =~ /^text|xml$|javascript$/
+
+Encoding is set on the application, but it is copied to the response object
+so you can override encoding rules per request (See L<Catalyst::Response>
+for more information).
+
+Be default we don't automatically encode 'application/json' since the most
+popular JSON encoders (such as L<JSON::MaybeXS> which is the library that
+L<Catalyst> can make use of) will do the UTF8 encoding and decoding automatically.
+Having it on in Catalyst could result in double encoding.
+
+If you are producing JSON response in an unconventional manner (such
+as via a template or manual strings) you should perform the UTF8 encoding
+manually as well such as to conform to the JSON specification.
+
+NOTE: We also examine the value of $c->response->content_encoding.  If
+you set this (like for example 'gzip', and manually gzipping the body)
+we assume that you have done all the neccessary encoding yourself, since
+we cannot encode the gzipped contents.  If you use a plugin like
+L<Catalyst::Plugin::Compress> we will be updating that plugin to work 
+with the new UTF8 encoding code, or you can use L<Plack::Middleware::Deflater>
+or (probably best) do your compression on a front end proxy.
+
 =head2 Methods
 
 =over 4