Dancer2
view release on metacpan or search on metacpan
lib/Dancer2/Core/App.pm view on Meta::CPAN
return $triggers;
},
);
has 'mime_type' => (
'is' => 'ro',
'isa' => InstanceOf['Dancer2::Core::MIME'],
'default' => sub { Dancer2::Core::MIME->new() },
);
sub _build_logger_engine {
my $self = shift;
my $value = shift;
my $config = shift;
defined $config or $config = $self->config;
defined $value or $value = $config->{logger};
is_ref($value) and return $value;
# XXX This is needed for the tests that create an app without
# a runner.
defined $value or $value = 'console';
is_module_name($value)
or croak "Cannot load logger engine '$value': illegal module name";
my $engine_options =
$self->_get_config_for_engine( logger => $value, $config );
my $logger = $self->_factory->create(
logger => $value,
%{$engine_options},
location => $self->config_reader->config_location,
environment => $self->environment,
app_name => $self->name,
postponed_hooks => $self->postponed_hooks
);
exists $config->{log} and $logger->log_level($config->{log});
return $logger;
}
sub _build_session_engine {
my $self = shift;
my $value = shift;
my $config = shift;
defined $config or $config = $self->config;
defined $value or $value = $config->{'session'} || 'simple';
is_ref($value) and return $value;
is_module_name($value)
or croak "Cannot load session engine '$value': illegal module name";
my $engine_options =
$self->_get_config_for_engine( session => $value, $config );
Scalar::Util::weaken( my $weak_self = $self );
# Note that engine options will replace the default session_dir (if provided).
return $self->_factory->create(
session => $value,
session_dir => $self->_appdir_path->child('sessions')->stringify,
%{$engine_options},
postponed_hooks => $self->postponed_hooks,
log_cb => sub { $weak_self->log(@_) },
);
}
sub _build_template_engine {
my $self = shift;
my $value = shift;
my $config = shift;
defined $config or $config = $self->config;
defined $value or $value = $config->{'template'};
defined $value or return;
is_ref($value) and return $value;
is_module_name($value)
or croak "Cannot load template engine '$value': illegal module name";
my $engine_options =
$self->_get_config_for_engine( template => $value, $config );
my $engine_attrs = {
config => $engine_options,
layout => $config->{layout},
layout_dir => ( $config->{layout_dir} || 'layouts' ),
views => $config->{views},
charset => $config->{charset},
};
Scalar::Util::weaken( my $weak_self = $self );
return $self->_factory->create(
template => $value,
%{$engine_attrs},
postponed_hooks => $self->postponed_hooks,
log_cb => sub { $weak_self->log(@_) },
);
}
sub _build_serializer_engine {
my $self = shift;
my $value = shift;
my $config = shift;
defined $config or $config = $self->config;
defined $value or $value = $config->{serializer};
defined $value or return;
is_ref($value) and return $value;
my $engine_options =
$self->_get_config_for_engine( serializer => $value, $config );
$engine_options->{strict_utf8} //= $config->{strict_utf8};
Scalar::Util::weaken( my $weak_self = $self );
return $self->_factory->create(
serializer => $value,
config => $engine_options,
postponed_hooks => $self->postponed_hooks,
log_cb => sub { $weak_self->log(@_) },
);
}
sub _get_config_for_engine {
my $self = shift;
my $engine = shift;
my $name = shift;
my $config = shift;
defined $config->{'engines'} && defined $config->{'engines'}{$engine}
or return {};
# try name, camelized name and fully-qualified name (without plus)
my $full_name = $name;
my $was_fully_qualified = ( $full_name =~ s/^\+// ); # strip any leading '+'
my $engine_config = {};
foreach my $engine_name (
$name,
Dancer2::Core::camelize($name),
( $was_fully_qualified ? $full_name : () )
) {
if ( defined $config->{'engines'}{$engine}{$engine_name} ) {
$engine_config = $config->{'engines'}{$engine}{$engine_name};
last;
}
}
return $engine_config;
}
has postponed_hooks => (
is => 'ro',
isa => HashRef,
default => sub { {} },
);
# TODO I'd be happier with a HashRef, really
has plugins => (
is => 'rw',
isa => ArrayRef,
default => sub { [] },
);
has route_handlers => (
is => 'rw',
isa => ArrayRef,
default => sub { [] },
);
has name => (
is => 'ro',
isa => Str,
default => sub { (caller(1))[0] },
);
has request => (
is => 'ro',
isa => InstanceOf['Dancer2::Core::Request'],
writer => '_set_request',
clearer => 'clear_request',
predicate => 'has_request',
);
sub set_request {
my ($self, $request, $defined_engines) = @_;
# typically this is passed in as an optimization within the
# dispatch loop but may be called elsewhere
$defined_engines ||= $self->defined_engines;
# populate request in app and all engines
$self->_set_request($request);
Scalar::Util::weaken( my $weak_request = $request );
$_->set_request( $weak_request ) for @{$defined_engines};
}
has response => (
is => 'ro',
isa => InstanceOf['Dancer2::Core::Response'],
lazy => 1,
writer => 'set_response',
clearer => 'clear_response',
builder => '_build_response',
predicate => 'has_response',
);
has with_return => (
is => 'ro',
predicate => 1,
writer => 'set_with_return',
clearer => 'clear_with_return',
);
has session => (
is => 'ro',
isa => InstanceOf['Dancer2::Core::Session'],
lazy => 1,
builder => '_build_session',
writer => 'set_session',
clearer => 'clear_session',
predicate => '_has_session',
);
has config_reader => (
is => 'ro',
isa => InstanceOf['Dancer2::ConfigReader'],
lazy => 0,
builder => '_build_config_reader',
);
sub _build_config_reader {
my ($self) = @_;
my $cfgr = Dancer2::ConfigReader->new(
environment => $self->environment,
location => $ENV{DANCER_CONFDIR} || $self->location,
default_config => $self->_build_default_config(),
);
return $cfgr;
}
has '+config' => (
is => 'ro',
isa => HashRef,
lazy => 1,
builder => '_build_config',
);
sub _build_config {
my ($self) = @_;
my $config_reader = $self->config_reader;
my $config = $config_reader->config;
if ( $config && $config->{'engines'} ) {
$self->_validate_engine($_) for keys %{ $config->{'engines'} };
}
return $config;
}
sub _build_response {
my $self = shift;
return Dancer2::Core::Response->new(
mime_type => $self->mime_type,
server_tokens => !$self->config->{'no_server_tokens'},
charset => $self->config->{charset},
strict_utf8 => $self->config->{strict_utf8},
do {
Scalar::Util::weaken( my $weak_self = $self );
log_cb => sub { $weak_self && $weak_self->log(@_) };
},
$self->has_serializer_engine
? ( serializer => $self->serializer_engine )
: (),
);
}
sub _build_session {
my $self = shift;
my $session;
# Find the session engine
my $engine = $self->session_engine;
# find the session cookie if any
if ( !$self->has_destroyed_session ) {
my $session_id;
my $session_cookie = $self->cookie( $engine->cookie_name );
defined $session_cookie and
$session_id = $session_cookie->value;
# if we have a session cookie, try to retrieve the session
if ( defined $session_id ) {
eval {
$EVAL_SHIM->(sub {
$session = $engine->retrieve( id => $session_id );
});
1;
}
or do {
my $err = $@ || "Zombie Error";
if ( $err !~ /Unable to retrieve session/ ) {
croak "Failed to retrieve session: $err"
} else {
# XXX we throw away the error entirely? Why?
}
};
}
}
# create the session if none retrieved
return $session ||= $engine->create();
}
sub has_session {
my $self = shift;
my $engine = $self->session_engine;
return $self->_has_session
|| ( $self->cookie( $engine->cookie_name )
&& !$self->has_destroyed_session );
}
has destroyed_session => (
is => 'ro',
isa => InstanceOf ['Dancer2::Core::Session'],
predicate => 1,
writer => 'set_destroyed_session',
lib/Dancer2/Core/App.pm view on Meta::CPAN
local $Dancer2::Core::Route::RESPONSE = $self->response;
my ( $hook, @args ) = @_;
if ( !$self->has_hook($hook) ) {
foreach my $cand ( $self->hook_candidates ) {
$cand->has_hook($hook) and return $cand->execute_hook(@_);
}
}
return $self->$orig(@_);
};
sub _build_default_config {
my $self = shift;
my $public = $ENV{DANCER_PUBLIC} || $self->_location_path->child('public')->stringify;
return {
content_type => ( $ENV{DANCER_CONTENT_TYPE} || 'text/html' ),
charset => ( $ENV{DANCER_CHARSET} || 'UTF-8' ),
strict_utf8 => ( $ENV{DANCER_STRICT_UTF8} || 0 ),
logger => ( $ENV{DANCER_LOGGER} || 'console' ),
views => ( $ENV{DANCER_VIEWS}
|| $self->_location_path->child('views')->stringify ),
environment => $self->environment,
appdir => $self->location,
public_dir => $public,
template => 'Tiny',
strict_config => 0, # We can look at changing this in a future version
route_handlers => [
[
AutoPage => 1
],
],
};
}
sub _build_appdir_path {
my $self = shift;
return Path::Tiny::path( $self->config->{appdir} || $self->location );
}
sub _build_views_path {
my $self = shift;
return Path::Tiny::path( $self->config->{views} );
}
sub _build_public_dir_path {
my $self = shift;
my $dir = $ENV{DANCER_PUBLIC}
|| $self->config->{public_dir}
|| $self->_location_path->child('public')->stringify;
return Path::Tiny::path($dir);
}
sub _init_hooks {
my $self = shift;
# Hook to flush the session at the end of the request,
# this way, we're sure we flush only once per request
#
# Note: we create a weakened copy $self
# before closing over the weakened copy
# to avoid circular memory refs.
Scalar::Util::weaken(my $app = $self);
$self->add_hook(
Dancer2::Core::Hook->new(
name => 'core.app.after_request',
code => sub {
my $response = $Dancer2::Core::Route::RESPONSE;
# make sure an engine is defined, if not, nothing to do
my $engine = $app->session_engine;
defined $engine or return;
# if a session has been instantiated or we already had a
# session, first flush the session so cookie-based sessions can
# update the session ID if needed, then set the session cookie
# in the response
#
# if there is NO session object but the request has a cookie with
# a session key, create a dummy session with the same ID (without
# actually retrieving and flushing immediately) and generate the
# cookie header from the dummy session. Lazy Sessions FTW!
if ( $app->has_session ) {
my $session;
if ( $app->_has_session ) { # Session object exists
$session = $app->session;
$session->is_dirty and $engine->flush( session => $session );
}
else { # Cookie header exists. Create a dummy session object
my $cookie = $app->cookie( $engine->cookie_name );
my $session_id = $cookie->value;
$session = Dancer2::Core::Session->new( id => $session_id );
}
$engine->set_cookie_header(
response => $response,
session => $session
);
}
elsif ( $app->has_destroyed_session ) {
my $session = $app->destroyed_session;
$engine->set_cookie_header(
response => $response,
session => $session,
destroyed => 1
);
}
},
)
);
}
sub supported_hooks {
qw/
core.app.before_request
core.app.after_request
core.app.route_exception
core.app.hook_exception
core.app.before_file_render
core.app.after_file_render
core.error.before
core.error.after
lib/Dancer2/Core/App.pm view on Meta::CPAN
my $aliases = $self->hook_aliases;
for my $plugin ( grep { $_->can('hook_aliases') } @{ $self->plugins } ) {
$aliases = { %{$aliases}, %{ $plugin->hook_aliases } };
}
return $aliases;
}
sub log {
my $self = shift;
my $level = shift;
my $logger = $self->logger_engine
or croak "No logger defined";
$logger->$level(@_);
}
sub send_as {
my $self = shift;
my ( $type, $data, $options ) = @_;
$options ||= {};
$type or croak "Can not send_as using an undefined type";
if ( lc($type) eq 'html' || lc($type) eq 'plain' ) {
if ( $type ne lc $type ) {
local $Carp::CarpLevel = 2;
carp sprintf( "Please use %s as the type for 'send_as', not %s", lc($type), $type );
}
$options->{charset} //= $self->config->{charset};
my $content = $data;
if ( defined $options->{charset} && length $options->{charset} ) {
$content = Encode::encode( $options->{charset}, $data );
}
$options->{content_type} ||= join '/', 'text', lc $type;
# Explicit return needed here, as if we are currently rendering a
# template then with_return will not longjump
return $self->send_file( \$content, %$options );
}
# Try and load the serializer class
my $serializer_class = "Dancer2::Serializer::$type";
eval {
$EVAL_SHIM->(sub {
require_module( $serializer_class );
});
1;
} or do {
my $err = $@ || "Zombie Error";
croak "Unable to load serializer class for $type: $err";
};
# load any serializer engine config
my $engine_options =
$self->_get_config_for_engine( serializer => $type, $self->config ) || {};
$engine_options->{strict_utf8} //= $self->config->{strict_utf8};
Scalar::Util::weaken( my $weak_self = $self );
my $serializer = $self->_factory->create(
serializer => $type,
config => $engine_options,
postponed_hooks => $self->postponed_hooks,
log_cb => sub { $weak_self->log(@_) },
);
my $content = $serializer->serialize( $data );
$options->{content_type} ||= $serializer->content_type;
$self->send_file( \$content, %$options );
}
sub send_error {
my $self = shift;
my ( $message, $status ) = @_;
my $err = Dancer2::Core::Error->new(
message => $message,
app => $self,
charset => $self->config->{charset},
( status => $status )x!! $status,
$self->has_serializer_engine
? ( serializer => $self->serializer_engine )
: (),
)->throw;
# Immediately return to dispatch if with_return coderef exists
$self->has_with_return && $self->with_return->($err);
return $err;
}
sub send_file {
my $self = shift;
my $thing = shift;
my %options = @_;
my ($content_type, $charset, $file_path);
# are we're given a filehandle? (based on what Plack::Middleware::Lint accepts)
my $is_filehandle = Plack::Util::is_real_fh($thing)
|| ( is_globref($thing) && *{$thing}{IO} && *{$thing}{IO}->can('getline') )
|| ( Scalar::Util::blessed($thing) && $thing->can('getline') );
my ($fh) = ($thing)x!! $is_filehandle;
# if we're given an IO::Scalar object, DTRT (take the scalar ref from it)
if (Scalar::Util::blessed($thing) && $thing->isa('IO::Scalar')) {
$thing = $thing->sref;
}
# if we're given a SCALAR reference, build a filehandle to it
if ( is_scalarref($thing) ) {
## no critic qw(InputOutput::RequireCheckedOpen)
open $fh, "<", $thing;
}
# If we haven't got a filehandle, create one to the requested content
if (! $fh) {
my $path = $thing;
# remove prefix from given path (if not a filehandle)
my $prefix = $self->prefix;
lib/Dancer2/Core/App.pm view on Meta::CPAN
$self->with_return->( $self->response );
};
# resolve relative paths (with '../') as much as possible
# On Windows, absolute paths (e.g., C:/foo) must not be joined with
# the rootdir since Path::Tiny would produce an invalid /C:/foo path.
my $pt_path = Path::Tiny::path($path);
$file_path = ( $pt_path->is_absolute
? $pt_path
: Path::Tiny::path( $dir, $path )
)->realpath;
# We need to check whether they are trying to access
# a directory outside their scope
$err_response->(403) if !$dir->realpath->subsumes($file_path);
# other error checks
$err_response->(403) if !$file_path->exists;
$err_response->(404) if !$file_path->is_file;
$err_response->(403) if !-r $file_path;
# Read file content as bytes
$fh = $file_path->openr_raw();
$content_type = $self->mime_type->for_file($file_path) || 'text/plain';
if ( $content_type =~ m!^text/! ) {
$charset = $self->config->{charset};
}
# cleanup for other functions not assuming on Path::Tiny
$file_path = $file_path->stringify;
}
# Now we are sure we can render the file...
$self->execute_hook( 'core.app.before_file_render', $file_path );
# response content type and charset
( exists $options{'content_type'} ) and $content_type = $options{'content_type'};
( exists $options{'charset'} ) and $charset = $options{'charset'};
$content_type .= "; charset=$charset" if $content_type and $charset;
( defined $content_type )
and $self->response->header('Content-Type' => $content_type );
# content disposition
( exists $options{filename} )
and $self->response->header( 'Content-Disposition' =>
($options{content_disposition} || "attachment") . "; filename=\"$options{filename}\"" );
# use a delayed response unless server does not support streaming
my $use_streaming = exists $options{streaming} ? $options{streaming} : 1;
my $response;
my $env = $self->request->env;
if ( $env->{'psgi.streaming'} && $use_streaming ) {
my $cb = sub {
my $responder = $Dancer2::Core::Route::RESPONDER;
my $res = $Dancer2::Core::Route::RESPONSE;
return $responder->(
[ $res->status, $res->headers_to_array, $fh ]
);
};
Scalar::Util::weaken( my $weak_self = $self );
$response = Dancer2::Core::Response::Delayed->new(
error_cb => sub { $weak_self->logger_engine->log( warning => @_ ) },
cb => $cb,
request => $Dancer2::Core::Route::REQUEST,
response => $Dancer2::Core::Route::RESPONSE,
);
}
else {
$response = $self->response;
# direct assignment to hash element, avoids around modifier
# trying to serialise this this content.
# optimized slurp
{
## no critic qw(Variables::RequireInitializationForLocalVars)
local $/;
$response->{'content'} = <$fh>;
}
$response->is_encoded(1); # bytes are already encoded
}
$self->execute_hook( 'core.app.after_file_render', $response );
$self->with_return->( $response );
}
sub BUILD {
my $self = shift;
$self->init_route_handlers();
$self->_init_hooks();
}
sub finish {
my $self = shift;
# normalize some values that require calculations
defined $self->config->{'static_handler'}
or $self->config->{'static_handler'} = -d $self->config->{'public_dir'};
$self->register_route_handlers;
$self->compile_hooks;
@{$self->plugins}
&& $self->plugins->[0]->can('_add_postponed_plugin_hooks')
&& $self->plugins->[0]->_add_postponed_plugin_hooks(
$self->postponed_hooks
);
foreach my $prep_cb ( @{ $self->prep_apps } ) {
$prep_cb->($self);
}
}
sub init_route_handlers {
my $self = shift;
my $handlers_config = $self->config->{route_handlers};
for my $handler_data ( @{$handlers_config} ) {
my ($handler_name, $config) = @{$handler_data};
$config = {} if !is_ref($config);
my $handler = $self->_factory->create(
Handler => $handler_name,
app => $self,
%$config,
postponed_hooks => $self->postponed_hooks,
);
push @{ $self->route_handlers }, {
name => $handler_name,
handler => $handler,
};
}
}
sub register_route_handlers {
my $self = shift;
for my $handler ( @{$self->route_handlers} ) {
my $handler_code = $handler->{handler};
$handler_code->register($self);
}
}
sub compile_hooks {
my ($self) = @_;
for my $position ( $self->supported_hooks ) {
my $compiled_hooks = [];
for my $hook ( @{ $self->hooks->{$position} } ) {
Scalar::Util::weaken( my $app = $self );
my $compiled = set_subname subname($hook) => sub {
# don't run the filter if halt has been used
$Dancer2::Core::Route::RESPONSE &&
$Dancer2::Core::Route::RESPONSE->is_halted
and return;
eval { $EVAL_SHIM->($hook,@_); 1; }
or do {
my $err = $@ || "Zombie Error";
my $is_hook_exception = $position eq 'core.app.hook_exception';
# Don't execute the hook_exception hook if the exception
# has been generated from a hook exception handler itself,
# thus preventing potentially recursive code.
$app->execute_hook( 'core.app.hook_exception', $app, $err, $position )
unless $is_hook_exception;
my $is_halted = $app->response->is_halted; # Capture before cleanup
# We can't cleanup if we're in the hook for a hook
# exception, as this would clear the custom response that
# may have been set by the hook. However, there is no need
# to do so, as the upper hook that called this hook
# exception will perform the cleanup instead anyway
$app->cleanup
unless $is_hook_exception;
# Allow the hook function to halt the response, thus
# retaining any response it may have set. Otherwise the
# croak from this function will overwrite any content that
# may have been set by the hook
return if $is_halted;
# Default behavior if nothing else defined
$app->log('error', "Exception caught in '$position' filter: $err");
croak "Exception caught in '$position' filter: $err";
};
};
push @{$compiled_hooks}, $compiled;
}
$self->replace_hook( $position, $compiled_hooks );
}
}
sub lexical_prefix {
my $self = shift;
my $prefix = shift;
my $cb = shift;
$prefix eq '/' and undef $prefix;
# save the app prefix
my $app_prefix = $self->prefix;
# alter the prefix for the callback
my $new_prefix =
( defined $app_prefix ? $app_prefix : '' )
. ( defined $prefix ? $prefix : '' );
# if the new prefix is empty, it's a meaningless prefix, just ignore it
length $new_prefix and $self->prefix($new_prefix);
my $err;
my $ok= eval { $EVAL_SHIM->($cb); 1 }
lib/Dancer2/Core/App.pm view on Meta::CPAN
my $err = $@ || "Zombie Error";
return [
500,
[ 'Content-Type' => 'text/plain' ],
[ "Internal Server Error\n\n$err" ],
];
};
return $response;
};
# Only add static content handler if required
if ( $self->config->{'static_handler'} ) {
# Use App::File to "serve" the static content
my $static_app = Plack::App::File->new(
root => $self->_public_dir_path->stringify,
content_type => sub { $self->mime_type->for_file( $_[0] ) },
)->to_app;
# Conditionally use the static handler wrapped with ConditionalGET
# when the file exists. Otherwise the request passes into our app.
$psgi = Plack::Middleware::Conditional->wrap(
$psgi,
condition => sub {
my $env = shift;
$self->_public_dir_path->child(
defined $env->{'PATH_INFO'} && length $env->{'PATH_INFO'}
? ($env->{'PATH_INFO'})
: (),
)->is_file;
},
builder => sub { Plack::Middleware::ConditionalGET->wrap( $static_app ) },
);
}
# Wrap with common middleware
if ( ! $self->config->{'no_default_middleware'} ) {
# FixMissingBodyInRedirect
$psgi = Plack::Middleware::FixMissingBodyInRedirect->wrap( $psgi );
# Apply Head. After static so a HEAD request on static content DWIM.
$psgi = Plack::Middleware::Head->wrap( $psgi );
}
return $psgi;
}
sub dispatch {
my $self = shift;
my $env = shift;
my $runner = Dancer2::runner();
my $request;
my $request_built_successfully = eval {
$EVAL_SHIM->(sub {
$request = $runner->{'internal_request'} || $self->build_request($env);
});
1;
};
# Catch bad content causing deserialization to fail when building the request
if ( ! $request_built_successfully ) {
my $err = $@;
Scalar::Util::weaken(my $app = $self);
return Dancer2::Core::Error->new(
app => $app,
message => $err,
status => 400, # 400 Bad request (dont send again), rather than 500
charset => $self->config->{charset},
)->throw;
}
my $cname = $self->session_engine->cookie_name;
my $defined_engines = $self->defined_engines;
DISPATCH:
while (1) {
my $http_method = lc $request->method;
my $path_info = $request->path_info;
# Add request to app and engines
$self->set_request($request, $defined_engines);
$self->log( core => "looking for $http_method $path_info" );
ROUTE:
foreach my $route ( @{ $self->routes->{$http_method} } ) {
#warn "testing route " . $route->regexp . "\n";
# TODO store in route cache
# go to the next route if no match
my $match = $route->match($request)
or next ROUTE;
$request->_set_route_params($match);
$request->_set_route_parameters($match);
$request->_set_route($route);
# Add session to app *if* we have a session and the request
# has the appropriate cookie header for _this_ app.
if ( my $sess = $runner->{'internal_sessions'}{$cname} ) {
$self->set_session($sess);
}
# calling the actual route
my $response;
# this is very evil, but allows breaking out of multiple stack
# frames without throwing an exception. Avoiding exceptions means
# a naive eval won't swallow our flow control mechanisms, and
# avoids __DIE__ handlers. It also prevents some cleanup routines
# from working, since they are expecting control to return to them
# after an eval.
DANCER2_CORE_APP_ROUTE_RETURN: {
if (!$self->has_with_return) {
$self->set_with_return(sub {
$response = shift;
no warnings 'exiting';
last DANCER2_CORE_APP_ROUTE_RETURN;
});
}
$response = $self->_dispatch_route($route);
};
lib/Dancer2/Core/App.pm view on Meta::CPAN
return $self->response_not_found($request);
}
$request = $response;
next DISPATCH;
}
# from here we assume the response is a Dancer2::Core::Response
# halted response, don't process further
if ( $response->is_halted ) {
$self->cleanup;
delete $runner->{'internal_request'};
return $response;
}
# pass the baton if the response says so...
if ( $response->has_passed ) {
## A previous route might have used splat, failed
## this needs to be cleaned from the request.
exists $request->{_params}{splat}
and delete $request->{_params}{splat};
$response->has_passed(0); # clear for the next round
# clear the content because if you pass it,
# the next route is in charge of catching it
$response->clear_content;
next ROUTE;
}
# it's just a regular response
$self->execute_hook( 'core.app.after_request', $response );
$self->cleanup;
delete $runner->{'internal_request'};
return $response;
}
# we don't actually want to continue the loop
last;
}
# No response! ensure Core::Dispatcher recognizes this failure
# so it can try the next Core::App
# and set the created request so we don't create it again
# (this is important so we don't ignore the previous body)
if ( $runner->{'internal_dispatch'} ) {
$runner->{'internal_404'} = 1;
$runner->{'internal_request'} = $request;
}
# Render 404 response, cleanup, and return the response.
my $response = $self->response_not_found($request);
$self->cleanup;
return $response;
}
sub build_request {
my ( $self, $env ) = @_;
Scalar::Util::weaken( my $weak_self = $self );
# If we have an app, send the serialization engine
my $request = Dancer2::Core::Request->new(
env => $env,
is_behind_proxy => $self->settings->{'behind_proxy'} || 0,
uri_for_route => sub { shift; $weak_self->uri_for_route(@_) },
strict_utf8 => $self->config->{strict_utf8},
$self->has_serializer_engine
? ( serializer => $self->serializer_engine )
: (),
);
return $request;
}
# Call any before hooks then the matched route.
sub _dispatch_route {
my ( $self, $route ) = @_;
local $@;
eval {
$EVAL_SHIM->(sub {
$self->execute_hook( 'core.app.before_request', $self );
});
1;
} or do {
my $err = $@ || "Zombie Error";
return $self->response_internal_error($err);
};
my $response = $self->response;
if ( $response->is_halted ) {
return $self->_prep_response( $response );
}
eval {
$EVAL_SHIM->(sub{ $response = $route->execute($self) });
1;
} or do {
my $err = $@ || "Zombie Error";
return $self->response_internal_error($err);
};
return $response;
}
sub _prep_response {
my ( $self, $response, $content ) = @_;
# The response object has no back references to the content or app
# Update the default_content_type of the response if any value set in
# config so it can be applied when the response is encoded/returned.
my $config = $self->config;
if ( exists $config->{content_type}
and my $ct = $config->{content_type} ) {
$response->default_content_type($ct);
}
exists $config->{charset}
and $response->charset( $config->{charset} );
( run in 1.991 second using v1.01-cache-2.11-cpan-39bf76dae61 )