NAME
Developer::Dashboard::Web::DancerApp - Dancer2 route layer for Developer Dashboard
SYNOPSIS
my $psgi_app = Developer::Dashboard::Web::DancerApp->build_psgi_app(
app => $web_app,
default_headers => \%headers,
paths => $paths,
);
DESCRIPTION
This module owns the HTTP route table for the dashboard web UI under Dancer2. It loads active skill Dancer2 extensions before constructing the shared app, normalizes each request, enforces authorization for dashboard and skill routes, and delegates the page and action work to Developer::Dashboard::Web::App. The skill lib/Dashboard.pm modules load while the PSGI app is constructed at server startup, not once per request; their registered hooks still execute for each matching request. A hook before => sub { ... } may set Dancer2 app variables or response headers. The page CODE runtime can read a hook variable when it imports Dancer2 appname => 'DeveloperDashboard'. When a hook sets a header also present in the dashboard defaults, that existing hook value wins; an explicit header returned by the backend wins over both.
For example, a skill extension can set a request-scoped value and a response policy:
use Dancer2 appname => 'DeveloperDashboard';
hook before => sub {
var foo => 'bar';
response_header 'Content-Security-Policy'
=> "script-src 'self' 'unsafe-inline' 'unsafe-eval'";
};
Skill bookmark CODE that needs the variable imports that same app:
use Dancer2 appname => 'DeveloperDashboard';
print var('foo');
The route adapter intentionally hands the namespaced /app, /ajax, /js, /css, and /others surfaces back to the backend dispatcher so the installed PSGI server stays in lock-step with the backend smart router. The /favicon.ico route is deliberately registered without the authorization wrapper, because browsers request the tab icon implicitly on every page load, including on the login page itself. The header normalizer forwards the Origin, Referer, and Sec-Fetch-Site headers on every request so the backend's cross-site request forgery check can refuse requests that arrive from a foreign browser context — including the unauthorized /login POST route, whose backend handler applies the same check itself. Sec-Fetch-Site has to ride along too because it is the only one of the three that defends a GET: the browser sets it, and page script can neither forge nor suppress it, which is what stops a foreign page from executing a saved /ajax handler on the cookie-less loopback-admin tier. Each direct backend call also scopes runtime-layer and installed-skill inventories to the current request, matching Developer::Dashboard::Web::App dispatch. The scope is discarded after the response so a later request sees runtime directories created or removed in the meantime.
METHODS
build_psgi_app, _load_skill_dashboard_modules, _authorize_skill_dashboard_routes, _current_backend, _request_headers, _request_args, _response_header_overrides, _response_from_result, _with_request_scopes, _run_backend, _run_authorized
Build and serve the Dancer2 application around the dashboard route handlers.
_with_request_scopes($backend, $callback)
Runs one direct route callback with request-local runtime-layer and installed- skill inventories plus exact path-identity results when the backend exposes a PageStore backed by a real PathRegistry. This keeps Dancer's direct-method route path consistent with Web::App::handle(): repeated reads share one filesystem view and exact path spellings reuse their canonical identity for the request, while the next request builds fresh scopes. Backends without that registry run the callback without these scopes. The callback's return value is preserved, and callback errors propagate to the existing route error handler.
Inputs are the current backend descriptor hash and a callback code reference. The callback may inspect or use the app's path registry. Output is exactly the callback's return value.
_request_args and _request_headers
_request_args captures the active Dancer request and its PSGI environment once, then returns the normalized path, query, method, body, remote address, and selected headers expected by the backend. It reads selected header values from their standard PSGI HTTP_* keys through _request_headers. If Host is empty, the normalized host is still assembled from SERVER_NAME and SERVER_PORT; the remote address still falls back from REMOTE_ADDR to SERVER_ADDR and then SERVER_NAME. _request_headers may also be called without an argument, in which case it obtains the current Dancer request's environment itself. Neither helper retains the request object or environment after it returns.
Both helpers take the current Dancer request implicitly for ordinary callers; _request_headers additionally accepts an optional PSGI environment hash to avoid reacquiring request header objects during normalization. It rejects non-hash environment values explicitly. They return hash references and do not mutate request state.
PURPOSE
This module adapts the dashboard route backend to PSGI/Dancer-style request handling. It turns the backend app object into a PSGI coderef, translates request and response state, and preserves the dashboard default headers while keeping the route logic out of the transport adapter. Direct Dancer handlers establish request-local runtime-layer, installed-skill, and path-identity scopes around authorization and backend dispatch, matching the freshness boundary of Web::App's handle method.
WHY IT EXISTS
It exists because the transport adapter should be small and separate from the actual route behavior. The dashboard needs a bridge from PSGI requests to the backend app object without forcing the backend to know about Dancer internals.
WHEN TO USE
Use this file when changing PSGI wrapping, response translation, direct-route request scopes, or the way the backend app is exposed to Plack and Starman.
HOW TO USE
Call build_psgi_app with the backend app object, default headers, and path registry, then pass the returned coderef to a PSGI server. Active installed skills are scanned recursively; each lib/Dashboard.pm is loaded with its own lib/ first in @INC so it can add routes or settings using use Dancer2 appname => 'DeveloperDashboard'. Skill routes are inserted before the final catch-all and are checked through the dashboard authorization handler. Each direct handler scopes runtime-layer, installed-skill, and path-identity reads to one request and discards all request caches afterward. These are trusted local Perl extensions; load failures stop startup with an explicit error. Route behavior and core auth logic should stay in Developer::Dashboard::Web::App, including the smart namespaced route resolution for installed skill-local pages, Ajax handlers, and public assets.
WHAT USES IT
It is used by app.psgi, by Developer::Dashboard::Web::Server, and by tests that verify the PSGI adapter keeps dashboard headers and route responses intact.
EXAMPLES
Example 1:
perl -Ilib -MDeveloper::Dashboard::Web::DancerApp -e 1
Do a direct compile-and-load check against the module from a source checkout.
Example 2:
prove -lv t/03-web-app.t t/08-web-update-coverage.t t/web_app_static_files.t
Run the focused regression tests that most directly exercise this module's behavior.
Example 3:
HARNESS_PERL_SWITCHES=-MDevel::Cover prove -lr t
Recheck the module under the repository coverage gate rather than relying on a load-only probe.
Example 4:
prove -lr t
Put any module-level change back through the entire repository suite before release.