NAME

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

VERSION

version 4.5.0

SYNOPSIS

The replies have the extra key error . At success error is 0 , while at fail is the error message

The posted keys can be placed as url parameters if wanted

Application code example

package MyApi;
use     Dancer2;
use     Dancer2::Plugin::WebService;

post '/ViewPosteData'  => sub { reply   UserData                             };
post '/ViewPosteSome'  => sub { reply   UserData('k1','k2')                  };
any  '/r3'             => sub { my %H = UserData('k1'); reply 'foo'=> $H{k1} };
get  '/r1'             => sub { reply  'k1'=>'v1','k2'=>'v2'                 };
get  '/r2'             => sub { reply {'k1'=>'v1','k2'=>'v2'}                };
get  '/error'          => sub { reply  'k1', 'v1', 'error', 'oups'           };
any  '/SessionStore'   => sub { reply   SessionSet('s1'=>'sv1', 's2'=>'v1')  };
post '/SessiondDelete' => sub { reply   SessionDel('s1', 's2')               };
any  '/SessionRead'    => sub { reply   SessionGet('s1', 's2')               };
dance;

Route examples

POST login           {"username":"joe", "password":"souvlaki"}
POST login?username=joe&password=souvlaki
POST ViewPosteData
POST ViewPosteSome   {"k1":"v1"}
POST SessionStore    {"token":"2d85b82b158e", "k1":"v1", "k2":"v2"}
POST SessionDelete   {"token":"2d85b82b158e"}
POST SessionRead     {"token":"2d85b82b158e"}
POST logout          {"token":"2d85b82b158e"}

Format and control the output : sort, pretty, to, from

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

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

from , to define the input/output format. You can mix input/output formats independently. Supported formats are

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

from default is the config.yml property

plugins :
  WebService :
    Default format : json

Examples

GET  /SomeRoute?to=human&sort=true&pretty=true
GET  /SomeRoute?to=perl&sort=true&pretty=false
POST /SomeRoute?to=xml&sort=true'      {"k1":"v1"}
POST /SomeRoute?to=yaml'               {"k1":"v1"}
POST /SomeRoute?to=yml'                {"k1":"v1"}
POST /SomeRoute?to=perl'               {"k1":"v1"}
POST /SomeRoute?from=json;to=human'    {"k1":"v1"}
POST /SomeRoute?from=jsn;to=human'     {"k1":"v1"}
POST /SomeRoute?from=xml;to=human'     <Data><k1>v1</k1></Data>
POST /SomeRoute?from=xml;to=yaml'      <Data><k1>v1</k1></Data>

Built in routes

Built in routes

GET  /WebService            list routes
GET  /WebService/about      about
GET  /WebService/version    version
GET  /WebService/client     client information
POST /login                 get a I<token> for using I<protected> routes and storing I<persistent> data
POST /logout                If you logout your session and all your persistent data are deleted

POST /login    {"username":"SomeUser","password":"SomePass"}
POST /logout   {"token":"SomeToken"}

Routes

Your routes can be either public or protected

public are the routes that anyone can use without login , Τhey do not support sessions / persistent data. You can access the posted data using the method UserData

protected are the routes that you must provide a token, returned by the login route. At protected routes you can read, write, delete persistent data using the methods SessionGet , SessionSet , SessionDel

Persistent session data are auto deleted when you logout or if your session expired.

You can define a route as protected at the config.yml

plugins:
  WebService:
    Routes:
      Route1: { Protected: false }
      Route2: { Protected: true }
      Route3: { Protected: true, Groups: [ ftp , storage ] }

or at your application code

setting('plugins')->{'WebService'}->{'Routes'}->{'SomeRoute'} = { Protected: 'true' };

IP access

You can control which clients IP addresses are allowed to login by editing 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.*
    - 192.168.1.23
    - 172.20.*
    - 32.??.34.4?
    - 4.?.?.??
    - ????:????:????:6d00:20c:29ff:*:ffa3
    - "*"

Sessions

Upon successful login, client is in session until logout or get expired due to inactivity. In session you can use the session methods by providing the token you received.

Session persistent storage

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

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

or at your main script

setting('plugins')->{'WebService'}->{'Session directory'} = '/var/lib/WebService';

Be careful this directory must be writable from the user that is running the service

Session expiration

Sessions expired after some seconds of inactivity. You can change the amount of seconds either at the config.yml

plugins:
  WebService:     
    Session idle timeout : 3600

or at your main script

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

Methods

WebService methods for your main Dancer2 code

The posted data can be anything; hashes, lists, scalars

curl -X POST ... -d  '{ "k1":"v1", "k2":"v2", "k3":"v3" }'
curl -X POST ... -d  '[ "k1", "k2", "k3", "k4" ]'

UserData

Get the posted data

           UserData               Everything is posted
my %hash = UserData('k2','k4');   Only some keys  of a hash
my @list = UserData('k2','k4');   Only some items of a list

reply

Send the reply to the client; it applies any necessary format convertions. This should be the last route's statement

reply                        only the error
reply    k1 => 'v1', ...     anything you want
reply( { k1 => 'v1', ... } ) anything you want
reply   'k1'                 The specific key and its value of the posted data 

SessionSet

Store non volatile session persistent data. login is required Session data are not volatile like normal the user posted data. They are persistent between requests until the user logout or its session get expired. Returns a list of the sored keys. You must pass your data as hash or hash reference.

SessionSet(   'rec1' => 'v1', 'rec2' => 'v2', ...   );
SessionSet( { 'rec1' => 'v1', 'rec2' => 'v2', ... } );

or at your Dancer2 application

get  '/SomeRoute' => sub { reply "session keys" => [ SessionSet( 'k1'=>'v1', 'k2'=>'v2' ) ] };

SessionGet

Read session persistent data. login is required

my %data = SessionGet;                     returns a hash of all keys 
my %data = SessionGet( 'k1', 'k2', ...  ); returns a hash of the selected keys
my %data = SessionGet(['k1', 'k2', ... ]); returns a hash of the selected keys

SessionDel

Deletes session persistent data. login is required

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

It returns a document of the deleted keys, your can use the url to=... modifier e.g.

{
"error" : 0,
"deleted keys" : [ "rec1", "rec2" ]
}

Authentication andd role based access control

For using protected routes, you must provide a valid token received from the login route. The login route is using the the first active authentication method of the config.yml 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

At production enviroments, probably you want to use an external auth script e.g for the native "Linux native" authentication

MODULE_INSTALL_DIR/AuthScripts/Linux_native_authentication.sh

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 the route Groups

If the route's Groups list is empty or missing, the route will run with any valid token ignoring the group

This is usefull because you can have role based access control at your routes. Every user with its token will be able to access only the routes is assigned to.

A sample route definition. Plese mention the \/ path separator

Routes:      
  Route1      :
    Protected : false
  Route\/foo1 :
    Protected : true
    Groups    : [ group1 , group2 ... ]
  Route\/foo2 :
    Protected : true
    Groups    : [ ]

It is easy to write your own scripts for LDAP, Active Directory, OAuth, etc

If the Command needs sudo, you must add the user running the WebService to sudoers e.g

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

Please read the AUTHENTICATION_SCRIPTS for the details

A sample config.yml is the following.

environment             : development
plugins                 :
  WebService            :
    Default format      : json
    Session directory   : /var/lib/WebService
    Session idle timeout: 86400
    Routes              :
      mirror            : { Protected: false }
      somekeys          : { Protected: false }
      data\/m1          : { Protected: false }
      data\/m1          : { Protected: false }
      INeedLogin_store  : { Protected: true, Groups: [ ftp , storage ] }
      INeedLogin_delete : { Protected: true, Groups: log }
      INeedLogin_read   : { Protected: true }

    Allowed hosts:
    - 127.*
    - 10.*
    - 172.16.?.*
    - 192.168.1.23
    - "????:????:????:6d00:20c:29ff:*:ffa3"
    - "*"

    Authentication methods:
    - Name      : INTERNAL
      Active    : true
      Accounts  :
        user1   : pass1
        user2   : <any>
        <any>   : Secret4All

    - Name      : Linux native
      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

Installation

You should run your service a non privileged user e.g. dancer

Create your application ( TestService ) e.g. at /opt/TestService/

dancer2 gen --application TestService --directory TestService --path /opt --overwrite
chown -R dancer:dancer /opt/TestService

Write your code at the file /opt/TestService/lib/TestService.pm

Configure your environment file

/opt/TestService/environments/development.yml

# logger    : file, console
# log level : core, debug, info, warning, error

startup_info     : 1
show_errors      : 1
warnings         : 1
no_server_tokens : 0
log              : 'core'
logger           : 'console'

engines:
  logger:
    file:
      log_format : '{"ts":"%{%Y-%m-%d %H:%M:%S}t","host":"%h","level":"%L","message":"%m"}'
      log_dir    : '/var/log/WebService'
      file_name  : 'TestService.log'
    console:
      log_format : '{"ts":"%{%Y-%m-%d %H:%M:%S}t","host":"%h","level":"%L","message":"%m"}'

Start the service as user dancer

plackup --host 0.0.0.0 --port 3000 -a /opt/TestService/bin/app.psgi --env production  --server Starman --workers=5 
plackup --host 0.0.0.0 --port 3000 -a /opt/TestService/bin/app.psgi --env development --server HTTP::Server::PSGI --Reload /opt/TestService/lib/TestService.pm,/opt/TestService/config.yml
plackup --host 0.0.0.0 --port 3000 -a /opt/TestService/bin/app.psgi

# without Plack
perl /opt/TestService/bin/app.psgi

view the INSTALL document for details

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) 2024 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.