[dancer-users] Dancer2::Manual: confused as to use of 'appname' keyword to distribute code across packages
James E Keenan
jkeen at verizon.net
Sun Jul 10 18:20:53 BST 2016
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
More information about the dancer-users
mailing list