__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_005';
$VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases
sub import {
$c->error('Something bad happened');
+Calling this will always return an arrayref (if there are no errors it
+will be an empty arrayref.
+
=cut
sub error {
sub has_errors { scalar(@{shift->error}) ? 1:0 }
+=head2 $c->last_error
+
+Returns the most recent error in the stack (the one most recently added...)
+or nothing if there are no errors.
+
+=cut
+
+sub last_error { my ($err, @errs) = @{shift->error}; return $err }
+
+=head2 shift_errors
+
+shifts the most recently added error off the error stack and returns if. Returns
+nothing if there are nomore errors.
+
+=cut
+
+sub shift_errors {
+ my ($self) = @_;
+ my ($err, @errors) = @{$self->error};
+ $self->{error} = \@errors;
+ return $err;
+}
+
sub _comp_search_prefixes {
my $c = shift;
return map $c->components->{ $_ }, $c->_comp_names_search_prefixes(@_);
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;
=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
# 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 {
}
}
+ 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; $_; }
# ->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;
}
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{(?<!/)$}{/};
}
if ( my $error = $@ ) {
#rethow if this can be handled by middleware
- if(
- blessed $error && (
- $error->can('as_psgi') ||
- (
- $error->can('code') &&
- $error->code =~m/^[1-5][0-9][0-9]$/
- )
- )
- ) {
+ if ( $c->_handle_http_exception($error) ) {
foreach my $err (@{$c->error}) {
$c->log->error($err);
}
# Support skipping finalize for psgix.io style 'jailbreak'. Used to support
# stuff like cometd and websockets
-
+
if($c->request->_has_io_fh) {
$c->log_response;
return;
$c->engine->finalize_error( $c, @_ );
} else {
my ($error) = @{$c->error};
- if(
- blessed $error &&
- ($error->can('as_psgi') || $error->can('code'))
- ) {
+ if ( $c->_handle_http_exception($error) ) {
# In the case where the error 'knows what it wants', becauses its PSGI
# aware, just rethow and let middleware catch it
$error->can('rethrow') ? $error->rethrow : croak $error;
$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();
- if(my $enc = $c->encoding) {
- my ($ct, $ct_enc) = $c->response->content_type;
-
- # Only touch 'text-like' contents
- if($c->response->content_type =~ /^text|xml$|javascript$/) {
- if ($ct_enc && $ct_enc =~ /charset=([^;]*)/) {
- if (uc($1) ne uc($enc->mime_name)) {
- $c->log->debug("Catalyst encoding config is set to encode in '" .
- $enc->mime_name .
- "', content type is '$1', not encoding ");
- }
- } else {
- $c->res->content_type($c->res->content_type . "; charset=" . $enc->mime_name);
- }
- }
- }
-
# Done
$response->finalized_headers(1);
}
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 $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 ");
+ }
- my $body = $c->response->body;
-
- return unless defined($body);
-
- my $enc = $c->encoding;
-
- return unless $enc;
-
- # Only touch 'text-like' contents
- if($c->response->content_type =~ /^text|xml$|javascript$/) {
- 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);
}
}
$status = $c->finalize;
} catch {
#rethow if this can be handled by middleware
- if(
- blessed($_) && (
- $_->can('as_psgi') ||
- (
- $_->can('code') &&
- $_->code =~m/^[1-5][0-9][0-9]$/
- )
- )
- ) {
+ if ( $class->_handle_http_exception($_) ) {
$_->can('rethrow') ? $_->rethrow : croak $_;
}
chomp(my $error = $_);
=head2 $c->setup_encoding
-Sets up the input/output encoding. See L<ENCODING>
+Sets up the input/output encoding. See L<ENCODING>
=cut
$class => @roles
) if @roles;
}
-}
+}
=head2 registered_middlewares
sub setup_middleware {
my $class = shift;
- my @middleware_definitions = @_ ?
+ my @middleware_definitions = @_ ?
reverse(@_) : reverse(@{$class->config->{'psgi_middleware'}||[]});
my @middleware = ();
->can('build_cgi_struct')->($params);
},
'application/json' => sub {
- Class::Load::load_first_existing_class('JSON::MaybeXS', 'JSON')
- ->can('decode_json')->(do { local $/; $_->getline });
- },
+ my ($fh, $req) = @_;
+ my $parser = Class::Load::load_first_existing_class('JSON::MaybeXS', 'JSON');
+ my $slurped;
+ return eval {
+ local $/;
+ $slurped = $fh->getline;
+ $parser->can("decode_json")->($slurped);
+ } || Catalyst::Exception->throw(sprintf "Error Parsing POST '%s', Error: %s", (defined($slurped) ? $slurped : 'undef') ,$@);
+ },
};
}
+sub _handle_http_exception {
+ my ( $self, $error ) = @_;
+ if (
+ !$self->config->{always_catch_http_exceptions}
+ && blessed $error
+ && (
+ $error->can('as_psgi')
+ || ( $error->can('code')
+ && $error->code =~ m/^[1-5][0-9][0-9]$/ )
+ )
+ )
+ {
+ return 1;
+ }
+}
+
=head2 $c->stack
Returns an arrayref of the internal execution stack (actions that are
=item *
+C<always_catch_http_exceptions> - As of version 5.90060 Catalyst
+rethrows errors conforming to the interface described by
+L<Plack::Middleware::HTTPExceptions> and lets the middleware deal with it.
+Set true to get the deprecated behaviour and have Catalyst catch HTTP exceptions.
+
+=item *
+
C<default_model> - The default model picked if you say C<< $c->model >>. See L<< /$c->model($name) >>.
=item *
=item *
C<use_request_uri_for_path> - Controls if the C<REQUEST_URI> or C<PATH_INFO> environment
-variable should be used for determining the request path.
+variable should be used for determining the request path.
Most web server environments pass the requested path to the application using environment variables,
from which Catalyst has to reconstruct the request base (i.e. the top level path to / in the application,
at other URIs than that which the app is 'normally' based at with C<mod_rewrite>), the resolution of
C<< $c->request->base >> will be incorrect.
-=back
+=back
=item *
processing the remaining actions and then catch the error upon chain end. This
can lead to running actions when the application is in an unexpected state. If
you have this issue, setting this config value to true will promptly exit a
-chain when there is an error raised in any action (thus terminating the chain
+chain when there is an error raised in any action (thus terminating the chain
early.)
use like:
is caught by Catalyst and unless you either catch it yourself (via eval
or something like L<Try::Tiny> or by reviewing the L</error> stack, it
will eventually reach L</finalize_errors> and return either the debugging
-error stack page, or the default error page. However, if your exception
+error stack page, or the default error page. However, if your exception
can be caught by L<Plack::Middleware::HTTPExceptions>, L<Catalyst> will
instead rethrow it so that it can be handled by that middleware (which
is part of the default middleware). For example this would allow
sub throws_exception :Local {
my ($self, $c) = @_;
- http_throw(SeeOther => { location =>
+ http_throw(SeeOther => { location =>
$c->uri_for($self->action_for('redirect')) });
}
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
Danijel Milicevic C<me@danijel.de>
+davewood: David Schmidt <davewood@cpan.org>
+
David Kamholz E<lt>dkamholz@cpan.orgE<gt>
David Naughton, C<naughton@umn.edu>