NAME

Dancer2::Plugin::WebService - Rest APIs with login, persistent data, multiple in/out formats, IP security, role based access

VERSION

version 4.8.1

SYNOPSIS

get '/my_keys' => sub { reply { 'k1'=>'v1' , 'k2'=>'v2' } };

curl $url/my_keys

DESCRIPTION

Create REST APIs with login, logout, persistent session data, IP security, role based access. Multiple input/output supported formats : json , xml , yaml, perl , human Post your data and keys as url parameters or content body text

curl -X GET  "$url/SomeRoute?k1=v1&k2=v2&k3=v3"
curl -X POST  $url/SomeRoute -d '{ "k1":"v1", "k2":"v2", "k3":"v3" }'

NAME

Convert your functions to REST api with minimal effort

URL parameters to format the reply

You can use the from, to, sort, pretty parameters to define the input/output format

from , to

Define the input/output format.

You can define input/output formats independently. from default is the config.yml property plugins.TestService.Default format = json Supported formats are

json or jsn
yaml or yml
xml
perl
human or text or txt

curl "$url/mirror?from=perl&to=xml" -d '{ "k1" => ["v1","v2","v3"] }'
sort

If true the keys are returned sorted. The default is false because it is faster. Valid values are true, 1, yes, false, 0, no

pretty

If false, the data are returned as one line compacted. The default is true, for human readable output. Valid values are true, 1, yes, false, 0, no

METHODS

Plugin methods available for your main Dancer2 code

UserData

Get all or some of the posted data Retruns a hash referense if the data are posted as hash Retruns an array referense if the data are posted as list

UserData               Returns everything
UserData('k1','k2')    Returns only the specific keys of the posted hash/list

get '/SomePath' => sub { reply UserData };

reply

Your last route's statement. Accepts a Perl data structure, and return it as json, xml, yaml, perl or human under the key reply

reply(   'hello world'        )
reply(  \'hello world'        )
reply(   'a', 'b' , 'c'       )
reply( [ 'a', 'b' , 'c' ]     )
reply( { k1=>'v1', k2=>'v2' } )
reply(   &SomeFunction        )
reply(  \&SomeFunction        )

Error

Set the error. Normally at success error should be 0 It does not stop the route execution. You must place it before the reply()

get '/SomePath' => sub { Error('ok'); reply 'hello world' };
get '/SomePath' => sub { Error('oups') };
get '/SomePath' => sub { reply 'a', 'b' };

SessionSet

Store session persistent data, unlike the volatile common posted data. It is a protected method, login is required

They are persistent between requests until they deleted, the user logout or their session get expired.

You must pass your data as hash or hash reference.

Returns a list of the stored keys.

any['get','post'] => '/session_save' => sub
{
@arr = SessionSet(   k1=>'v1' , k2=>'v2'   );
@arr = SessionSet( { k3=>'v3' , k4=>'v4' } );
reply { 'Your saved keys are' => \@arr }
};

curl $url/session_save?token=17398-5c8a71b -H "$H" -X POST -d '{"k1":"v1", "k2":"v2", ... }'

SessionGet

Read session persistent data. It is a protected method, login is required

Returns a hash

any['post','put'] => '/session_read' => sub {
my %hash1 = SessionGet(   'k1','k2'   );       # some records
my %hash2 = SessionGet( [ 'k1','k2' ] );       # some records
my %hash3 = SessionGet();                      # all  records
reply { %hash3 }
};

curl $url/session_read?token=17398-5c8a71b

SessionDel

Deletes session persistent data. It is a protected method, login is required

Returns a list of the deleted keys

SessionDel;                              delete all keys
SessionDel(   'rec1', 'rec2', ...   );   delete selected keys
SessionDel( [ 'rec1', 'rec2', ... ] );   delete selected keys

any['delete'] => '/session_delete' => sub {
my $arg = UserData();
my @arr = SessionDel( $arg );
reply { 'Deleted keys' => \@arr }
};

curl -X DELETE $url/session_delete?token=17398-5c8a71b -H "$H" -d '["k1","k2","k9"]'

{
  "error" : 0,
  "reply" : {
      "Deleted keys" : [ "k1" , "k2" ]
  }
}

Authentication and role based access control

The routes can be either public or protected

protected
routes that you must provide the I<token>, as returned by the I<login> route.
Afer login, you can save, update, read, delete persistent session data

The B<login> route is using the the first active authentication method of the I<config.yml>
public
routes that anyone can use without B<login> , they do not support sessions / persistent data.

Configuration file "Application dir/config.yml"

This file customize the application name, version, securrity, routes and Authentication methods. The following is an example

appname                 : TestService
appversion              : 1.0.0
environment             : development
layout                  : main
charset                 : UTF-8
template                : template_toolkit
engines:
  template:
    template_toolkit:
      EVAL_PERL         : 0
      start_tag         : '<%'
      end_tag           : '%>'
plugins:
  WebService:
    Session enable      : true
    Session directory   : /var/lib/WebService
    Session idle timeout: 86400
    Default format      : json
    Allowed hosts       :
    - "127.*"
    - "172.20.20.*"
    - "????:????:????:6d00:20c:29ff:*:ffa3"
    - "10.*.?.*"
    - "*"

    Routes:
      text              : { Protected: false }
      mirror            : { Protected: false }
      Protected         : { Protected: true  }
      Protected_text_ref: { Protected: true  }
      list              : { Protected: false }
      list_ref          : { Protected: false }
      hash              : { Protected: false }
      code\/text        : { Protected: false }
      code\/list        : { Protected: false }
      code\/hash        : { Protected: false }
      code\/text_ref    : { Protected: false }
      code\/list_ref    : { Protected: false }
      keys_selected     : { Protected: false }
      git\/commit       : { Protected: true, Groups: [ git , ansibleremote ] }
      session_save      : { Protected: true, Groups: [] }
      session_read      : { Protected: true, Groups: [] }
      session_delete    : { Protected: true, Groups: [] }

    Authentication methods:

    - Name      : INTERNAL
      Active    : true
      Accounts  :
        user1   : s3cr3T+PA55sW0rD
        user2   : <any>
        <any>   : S3cREt-4-aLl
      #<any>   : <any>

    - Name      : Linux native users
      Active    : false
      Command   : MODULE_INSTALL_DIR/AuthScripts/Linux_native_authentication.sh
      Arguments : [ ]
      Use sudo  : true

    - Name      : Basic Apache auth for simple users
      Active    : false
      Command   : MODULE_INSTALL_DIR/AuthScripts/HttpBasic.sh
      Arguments : [ "/etc/htpasswd" ]
      Use sudo  : false

Authentication methods

Authentication method can be INTERNAL or external executable Command.

At INTERNAL you define the usernames / passwords directly at the config.yml . The <any> means any username or password, so if you want to allow all users to login no matter the username or the password use

<any> : <any>

This make sense if you just want to give anyone the ability for persistent data

The protected routes, at config.yml have Protected:true and their required groups e.g. Groups:[grp1,grp2 ...]

The user must be member to all defined groups

If the route's Groups list is empty or missing, then the groups membership is ignored

This way you can have user based access, because every user is allowed to access his assigned routes.

Authentication scripts

At production enviroments, probably you want to use external authenticators, accessed by plugable scripts e.g for the native "Linux native" authentication

MODULE_INSTALL_DIR/AuthScripts/Linux_native_authentication.sh

It is easy to write your own scripts for LDAP, Active Directory, OAuth 2.0, Keycload, etc external authenticators.

If the script needs sudo, you must add the user running the application to sudoers e.g

dendrodb ALL=(ALL:ALL) NOPASSWD: /usr/share/perl5/site_perl/Dancer2/Plugin/AuthScripts/some_auth_script.sh

Please read the file AUTHENTICATION_SCRIPTS for the details

IP access

You can control which clients are allowed to use your application at the file config.yml

The rules are checked from up to bottom until there is a match. If no rule match then the client can not login. At rules your can use the wildcard characters * ?

...
plugins:
  WebService:
    Allowed hosts:
    - 127.*
    - 10.*
    - 172.20.*
    - 32.??.34.4?
    - 4.?.?.??
    - ????:????:????:6d00:20c:29ff:*:ffa3
    - 192.168.0.153
    - "*"

Sessions

Upon successful login, the client is in session until logout or its session expired due to inactivity.

While in session you can access protected routes and save, read, delete session persistent data.

at the config.yml You can change persistent data storage directory and session expiration

Storage directory
Be careful this directory must be writable from the user that is running the service
To set the sessions directory

plugins:
  WebService:
    Session directory : /var/lib/WebService

or at your application

setting('plugins')->{'WebService'}->{'Session directory'} = '/var/lib/WebService';
Session expiration
Sessions are expiring after some seconds of inactivity. You can change the amount of seconds either at the I<config.yml>

plugins:
  WebService:     
    Session idle timeout : 3600

or at your application

setting('plugins')->{'WebService'}->{'Session idle timeout'} = 3600;

Built in plugin routes

These are plugin built in routes

WebService            version
WebService/client     client propertis
WebService/routes     list the built-in and application routes
login                 login
logout                logout

Usage examples

export url=http://127.0.0.1:3000 H="Content-Type: application/json"
alias curl="$(/usr/bin/which curl) --silent --user-agent Perl"

curl  $url/WebService
curl  $url/WebService/client
curl  $url/WebService/routes?sort=true
curl "$url/WebService?to=json&pretty=true&sort=true"
curl  $url/WebService?to=yaml
curl "$url/WebService?to=xml&pretty=false"
curl "$url/WebService?to=xml&pretty=true"
curl  $url/WebService?to=human
curl  $url/WebService?to=perl
curl  $url

Application routes

Based on the code of our TestService ( lib/TestService.pm ) some examples of how to login, logout, and route usage

curl "$url/mirror?from=json&to=json&k1=a&k2=b"  -d '{"k1" : ["one","two","three"]}'
curl "$url/mirror?to=xml&pretty=true"           -d '{"k1" : ["one","two","three"]}'
curl "$url/mirror?from=yaml&to=perl"            -d '"k1"  : ["one","two","three"]'
curl "$url/mirror?from=xml&to=yaml"             -d '<root><k1>one</k1><k2>two</k2></root>'

Login

curl -X POST $url/login -H "$H" -d '{"username": "user1", "password": "s3cr3T+PA55sW0rD"}'

Protected application routes

curl  $url/text
curl  $url/text?token=17393926-5c8-0
curl  $url/session_save?token=17393926-5c8-0 -H "$H" -X POST -d '{"k1":"v1", "k2":"v2", "k3":"v3"}'
curl  $url/session_read?token=17393926-5c8-0
curl  $url/session_delete?token=17393926-5c8-0 -H "$H" -X DELETE -d '["k3","k8","k9"]'
curl  $url/session_read?token=17393926-5c8-0

Logout

curl  $url/logout?token=17393926-5c8-0
curl  $url/logout -d '{"token":"17393926-5c8-0"}' -H "$H" -X POST

Plugin Installation

You should your run your APIs as a non privileged user e.g. the "dancer"

getent group  dancer >/dev/null || groupadd dancer
getent passwd dancer >/dev/null || useradd -g dancer -l -m -c "Dancer2 WebService" -s $(which nologin) dancer
i=/var/lib/WebService; [ -d $i ] || { mkdir $i; chown -R dancer:dancer $i; }
i=/var/log/WebService; [ -d $i ] || { mkdir $i; chown -R dancer:dancer $i; }
cpanm Dancer2
cpanm Dancer2::Plugin::WebService

Create a sample application e.g. the "TestService"

Follow the CREATE_SAMPLE_APPLICATION document to create the sample application TestService

Start the application

To start it manual as user dancer from the command line

Production
sudo -u dancer plackup --host 0.0.0.0 --port 3000 --server Starman --workers=5 --env development -a /home/dancer/TestService/bin/app.psgi
While developing
sudo -u dancer plackup --host 0.0.0.0 --port 3000 --env development --app /home/dancer/TestService/bin/app.psgi --server HTTP::Server::PSGI

view also the INSTALL document for details

Configure the loggger at the environment file

Application dir/environments/[development|production].yml

log              : "debug"  # core, debug, info, warning, error
show_stacktrace  : 0
no_server_tokens : 1
warnings         : 1          # should Dancer2 consider warnings as critical errors?
show_errors      : 1          # if true shows a detailed debug error page , otherse the views/404.tt or public/404.html
startup_info     : 1          # print the banner
no_server_tokens : 1          # disable server tokens in production environments
logger           : "file"     # console: to STDOUT , file:to file
engines          :
  logger         :
    file         :      
      log_format : '{"ts":"%T","host":"%h","pid":"%P","message":"%m"}'
      log_dir    : "/tmp"
      file_name  : "test.log"

See also

Plack::Middleware::REST Route PSGI requests for RESTful web applications

Dancer2::Plugin::REST A plugin for writing RESTful apps with Dancer2

RPC::pServer Perl extension for writing pRPC servers

RPC::Any A simple, unified interface to XML-RPC and JSON-RPC

XML::RPC Pure Perl implementation for an XML-RPC client and server.

JSON::RPC JSON RPC Server Implementation

AUTHOR

George Bouras <george.mpouras@yandex.com>

COPYRIGHT AND LICENSE

This software is copyright (c) 2025 by George Bouras.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.