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 )