[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