As I work my way through Dancer2::Manual, I am having difficulty understanding how to use the 'appname' keyword to distribute code among different packages in order to improve maintainability. I. Starting point: two packages, each in separate file, using 'builder'. cat lib/mywebapp.pm ##### package mywebapp; use v5.10.1; use Dancer2; use Dancer2::Plugin::Database; use Crypt::SaltedHash; use Data::Dump; use Dancer2::Core::Request::Upload; use URL::Encode qw (url_decode); our $VERSION = '0.1'; set session => 'Simple'; hook before => sub { if (!session('user') && request->dispatch_path !~ m{^/login}) { forward '/login', { requested_path => request->dispatch_path }; } }; get '/' => sub { template 'index'; }; get '/index' => sub { redirect '/' }; get '/login' => sub { # Display a login page; the original URL they requested is available as # param('requested_path'), so could be put in a hidden field in the form template 'login', { path => param('requested_path') }; }; post '/login' => sub { my $user_value = body_parameters->get('user'); my $pass_value = url_decode(body_parameters->get('pass')); my $user = database->quick_select('users', { username => $user_value }); if (! $user) { warning "Failed login for unrecognised user $user_value"; redirect '/login?failed=1'; } else { my $csh = Crypt::SaltedHash->new(algorithm => 'SHA-1'); $csh->add($user->{password}); my $salted = $csh->generate; if (Crypt::SaltedHash->validate($salted, $pass_value)) { debug "Password correct"; session user => $user; redirect body_parameters->get('path') || '/'; # Note: When using curl and supplying a value for 'path' among the # KVPs for POST /login, any such endpoint must be defined herein # as: any ['get', 'post'] => '/my_endpoint' to work around curl's # apparent refusal to switch from POST to GET } else { debug "Login failed; password incorrect for: " . $user_value; redirect '/login?failed=1'; } } }; get '/logout' => sub { app->destroy_session; }; any ['get', 'post'] => '/hello' => sub { return "Hello World\n"; }; { hook before => sub { var time => scalar(localtime) }; any ['get', 'post'] => '/welcome/:name' => sub { my $name = route_parameters->get('name'); template 'welcome.tt', { name => $name }; }; } any ['get', 'post'] => '/appname' => sub { return "This is " . config->{appname} . "\n"; }; start; ##### Serialized responses must go into a separate package. Hence, I have written, and successfully used, this package: cat lib/mywebapp/api.pm ##### package mywebapp::api; use v5.10.1; use Dancer2; use Dancer2::Plugin::Database; use Crypt::SaltedHash; use Data::Dump; use Dancer2::Core::Request::Upload; use URL::Encode qw (url_decode); use HTTP::Status qw(:constants status_message); our $VERSION = '0.1'; set session => 'Simple'; set views => path( app->location, "templates" ); set serializer => 'JSON'; any ['get', 'post'] => '/userdata/:user' => sub { my $user_value = route_parameters->get('user'); my $user = database->quick_select('users', { username => $user_value }); if (! $user) { warning "Failed to recognize user '$user_value'"; my $code = HTTP_BAD_REQUEST; { status_code => $code, message => status_message($code), } } else { { username => $user->{username}, first_name => $user->{first_name}, last_name => $user->{last_name}, } } }; start; ##### And, following earlier documentation in Dancer2::Manual, I have gotten these two packages to work together via: ##### $ cat bin/app.psgi #!/usr/bin/env perl use strict; use warnings; use FindBin; use lib "$FindBin::Bin/../lib"; use mywebapp; use mywebapp::api; use Plack::Builder; builder { mount '/' => mywebapp->to_app; mount '/api' => mywebapp::api->to_app; }; ##### With a valid user 'dancer_operator', I am able to say: ##### http://localhost:5000/api/userdata/dancer_operator ##### ... and get back in browser: ##### {"username":"dancer_operator","last_name":"Operator","first_name":"Dancer"} ##### II. Attempt to refactor this with 'appname' If I am reading documentation in Dancer2::Manual and Dancer2::Cookbook correctly, I believe I should be able to make the following revisions and have all my endpoints just work. $ git diff | cat ##### diff --git a/bin/app.psgi b/bin/app.psgi index 46d8845..b236a2d 100755 --- a/bin/app.psgi +++ b/bin/app.psgi @@ -6,11 +6,5 @@ use FindBin; use lib "$FindBin::Bin/../lib"; use mywebapp; -use mywebapp::api; -use Plack::Builder; - -builder { - mount '/' => mywebapp->to_app; - mount '/api' => mywebapp::api->to_app; -}; +mywebapp->to_app; diff --git a/lib/mywebapp/api.pm b/lib/mywebapp/api.pm index 3af1adf..97a9d0e 100644 --- a/lib/mywebapp/api.pm +++ b/lib/mywebapp/api.pm @@ -1,6 +1,6 @@ package mywebapp::api; use v5.10.1; -use Dancer2; +use Dancer2 appname => mywebapp; use Dancer2::Plugin::Database; use Crypt::SaltedHash; use Data::Dump; ##### All the endpoints defined in lib/mywebapp.pm continue to function as expected. But the endpoint, /api/userdata/:user, defined in lib/mywebapp/api.pm, no longer works. When I call, ##### http://localhost:5000/api/userdata/dancer_operator ##### ... I get a 404 NOT_FOUND response. If I omit the '/api' from the URL above, I also get a 404 -- though that's more expected. And when I try using "prefix = '/api'" as suggested by Dancer2::Cookbook, I still get 404s. What am I missing and/or what are the docs failing to explain clearly? Thank you very much. Jim Keenan