The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Dancer2::Plugin::WebService - RESTful Web Services with login, persistent data, multiple input/output formats, IP security, role base access

VERSION

version 4.2.2

SYNOPSIS

The replies through this module have the extra key error . At success error is 0 , while at fail is the error description

Built in routes

  GET  WebService
  GET  WebService/client
  GET  WebService/about
  GET  WebService/version

Your routes

  POST AllKeys?to=yaml    posted data  {"k1":"v1"}
  POST SomeKeys?to=xml    posted data  {"k1":"v1"}
  POST login              posted data  {"username":"joe", "password":"souvlaki"}
  POST LoginNeeded_store  posted data  {"token":"2d85b82b158e", "k1":"v1", "k2":"v2"}
  POST LoginNeeded_delete posted data  {"token":"2d85b82b158e"}
  POST LoginNeeded_read   posted data  {"token":"2d85b82b158e"}
  POST logout             posted data  {"token":"2d85b82b158e"}

Code example

  package MyService;
  use     Dancer2;
  use     Dancer2::Plugin::WebService;

  post '/AllKeys'  => sub { reply   posted_data            };
  post '/SomeKeys' => sub { reply   posted_data('k1','k2') };
  get  '/data1'    => sub { reply  'k1'=>'v1', 'k2'=>'v2'  };
  get  '/data2'    => sub { reply {'k1'=>'v1', 'k2'=>'v2'} };
  any  '/data3'    => sub { my %H = posted_data('k1', 'k2');
                      reply 'foo'=> $H{k1}, 'boo'=>$H{k2}
                      };
  get  '/error'             => sub { reply 'k1', 'v1', 'error', 'oups' };
  any  '/LoginNeeded_store' => sub { reply session_set('s1'=>'sv1', 's2'=>'v1') };
  post '/LoginNeeded_delete'=> sub { reply session_del('s1', 's2') };
  any  '/LoginNeeded_read'  => sub { reply session_get('s1', 's2') };

  dance;

Control output : sort, pretty, to, from

url parameters to control the reply

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
  xml
  yaml
  perl
  human

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'    posted data  {"k1":"v1"}
  POST  SomeRoute?to=yaml'             posted data  {"k1":"v1"}
  POST  SomeRoute?to=perl'             posted data  {"k1":"v1"}
  POST  SomeRoute?from=json;to=human'  posted data  {"k1":"v1"}
  POST  SomeRoute?from=xml;to=human'   posted data  <Data><k1>v1</k1></Data>
  POST  SomeRoute?from=xml;to=yaml'    posted data  <Data><k1>v1</k1></Data>

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, but you can post and access data using the method posted_data

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 session_get , session_set , session_del

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:
        SomeRoute: protected

or at your application code

  setting('plugins')->{'WebService'}->{'Routes'}->{'SomeRoute'} = 'protected';

BUILT-IN ROUTES

public informational routes

You can use "to" format modifiers if you want

  GET  WebService            The available routes
  GET  WebService/about      About
  GET  WebService/version    Perl, Dancer2, WebService, apllication version
  GET  WebService/client     Your client information

LOGIN

public route

Login to get a token for using protected routes and storing persistent data

  POST login   posted data {"username":"SomeUser","password":"SomePass"}  e.g.
  curl -X POST 0/login -d '{"username":"jonathan","password":"__1453__"}'

LOGOUT

protected route

If you logout your session and all your persistent data are deleted

  POST logout      posted data  {"token":"SomeToken"}  e.g
  curl -X POST 0/logout --data '{"token":"a105076d9"}'

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

reply

public method

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 

posted_data

public method

Get the data posted by the user

  posted_data                  hash of all the posted data
  posted_data('k1', 'k2');     hash of the selected posted keys and their values

session_get

session method

Read session persistent data

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

session_set session method

Store non volatile session persistent data;

You must pass your data as key / value pairs

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

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

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

session_del

session method

Deletes session persistent data

  session_del;                              delete all keys
  session_del(   'rec1', 'rec2', ...   );   delete selected keys
  session_del( [ '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, 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 authentication

  MODULE_INSTALL_DIR/AuthScripts/linux.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 otherelse the route will not run.

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

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 Active Directory, LDAP, facebook integration or whatever.

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

Please read the AUTHENTICATION_SCRIPTS for the details

A sample config.yml is the following.

  version                 : 3.0.0
  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 users
        Active    : false
        Command   : MODULE_INSTALL_DIR/AuthScripts/linux.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 --env production  --server Starman --workers=5 -a /opt/TestService/bin/app.psgi
  plackup --host 0.0.0.0 --port 3000 --env development --server HTTP::Server::PSGI --Reload /opt/TestService/lib/TestService.pm,/opt/TestService/config.yml -a /opt/TestService/bin/app.psgi
  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) 2019 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.