NAME

WWW::Suffit::Server - The Suffit API web-server class

SYNOPSIS

use Mojo::File qw/ path /;

my $root = path()->child('test')->to_string;
my $app = MyApp->new(
    project_name => 'MyApp',
    project_version => '0.01',
    moniker => 'myapp',
    debugmode => 1,
    loglevel => 'debug',
    max_history_size => 25,

    # System
    uid => 1000,
    gid => 1000,

    # Dirs and files
    homedir => path($root)->child('share')->make_path->to_string,
    datadir => path($root)->child('var')->make_path->to_string,
    tempdir => path($root)->child('tmp')->make_path->to_string,
    documentroot => path($root)->child('www')->make_path->to_string,
    logfile => path($root)->child('log')->make_path->child('myapp.log')->to_string,
    pidfile => path($root)->child('run')->make_path->child('myapp.pid')->to_string,

    # Server
    server_addr => '*',
    server_port => 8080,
    server_url => 'http://127.0.0.1:8080',
    trustedproxies => ['127.0.0.1'],
    accepts => 10000,
    clients => 1000,
    requests => 100,
    workers => 4,
    spare => 2,
    reload_sig => 'USR2',
    no_daemonize => 1,

    # Security
    mysecret => 'Eph9Ce$quo.p2@oW3',
    rsa_keysize => 2048,
    private_key => undef, # Auto
    public_key => undef, # Auto

    # Initialization options
    all_features    => 'no',
    config_opts     => {
        file => path($root)->child('etc')->make_path->child('myapp.conf')->to_string,
        defaults => {foo => 'bar'},
    },
);

# Run preforked application
$app->preforked_run( 'start' );

1;

package MyApp;

use Mojo::Base 'WWW::Suffit::Server';

sub init { shift->routes->any('/' => {text => 'Hello World!'}) }

1;

DESCRIPTION

This module provides API web-server functionality

OPTIONS

sub startup {
    my $self = shift->SUPER::startup( OPTION_NAME => VALUE, ... );

    # ...
}

Options passed as arguments to the startup function allow you to customize the initialization of plugins at the level of your descendant class, and options are considered to have higher priority than attributes of the same name.

List of allowed options (pairs of name-value):

admin_routes_opts

admin_routes_opts => {
    prefix_path => "/admin",
    prefix_name => "admin",
}
prefix_name
prefix_name => "admin"

This option defines prefix of admin api route name

Default: 'admin'

prefix_path
prefix_path => "/admin"

This option defines prefix of admin api route

Default: '/admin'

all_features

all_features => 'on'

This option enables all of the init_* options, which are described bellow

Default: off

api_routes_opts

api_routes_opts => {
    prefix_path => "/api",
    prefix_name => "api",
}
prefix_name
prefix_name => "api"

This option defines prefix of api route name

Default: 'api'

prefix_path
prefix_path => "/api"

This option defines prefix of api route

Default: '/api'

authdb_opts

authdb_opts => {
    uri => "sqlite://<PATH_TO_DB_FILE>?sqlite_unicode=1",
    cachedconnection => 'on',
    cacheexpiration => 300,
    cachemaxkeys => 1024*1024,
    sourcefile => '/tmp/authdb.json',
}
uri, url
uri => "sqlite:///tmp/auth.db?sqlite_unicode=1",

Default: See config AuthDBURL or AuthDBURI directive

cachedconnection
cachedconnection => 'on',

Default: See config AuthDBCachedConnection directive. Default: on

cacheexpire, cacheexpiration
cacheexpiration => 300,

Default: See config AuthDBCacheExpire or AuthDBCacheExpiration directive. Default: 300

cachemaxkeys
cachemaxkeys => 1024*1024,

Default: See config AuthDBCacheMaxKeys directive. Default: 1024*1024

sourcefile
sourcefile => '/tmp/authdb.json',

Default: See config AuthDBSourceFile directive

config_opts

config_opts => { ... }

This option sets Mojolicious::Plugin::ConfigGeneral plugin options

Default:

`noload => 1` if $self->configobj exists
`defaults => $self->config` if $self->config is not void

init_admin_routes

init_admin_routes => 'on'

This option enables Admin Suffit API routes

Default: off

init_authdb

init_authdb => 'on'

This option enables AuthDB initialize

Default: off

init_api_routes

init_api_routes => 'on'

This option enables Suffit API routes

Default: off

init_rsa_keys

init_rsa_keys => 'on'

This option enables RSA keys initialize

Default: off

init_user_routes

init_user_routes => 'on'

This option enables User Suffit API routes

Default: off

syslog_opts

syslog_opts => { ... }

This option sets WWW::Suffit::Plugin::Syslog plugin options

Default:

`enable => 1` if the `Log` config directive is "syslog"

user_routes_opts

user_routes_opts => {
    prefix_path => "/user",
    prefix_name => "user",
}
prefix_name
prefix_name => "user"

This option defines prefix of user api route name

Default: 'user'

prefix_path
prefix_path => "/user"

This option defines prefix of user api route

Default: '/user'

ATTRIBUTES

This class implements the following attributes

accepts

accepts => 0,

Maximum number of connections a worker is allowed to accept, before stopping gracefully and then getting replaced with a newly started worker, passed along to "max_accepts" in Mojo::IOLoop

Default: 10000

See "accepts" in Mojo::Server::Prefork

cache

The WWW::Suffit::Cache object

clients

clients => 0,

Maximum number of accepted connections this server is allowed to handle concurrently, before stopping to accept new incoming connections, passed along to "max_connections" in Mojo::IOLoop

Default: 1000

See "max_clients" in Mojo::Server::Daemon

configobj

The Config::General object or undef

acruxconfig

The Acrux::Config object or undef

datadir

datadir => '/var/lib/myapp',

The sharedstate data directory (data dir)

Default: /var/lib/<MONIKER>

debugmode

debugmode => 0,

If this attribute is enabled then this server is no daemonize performs

documentroot

documentroot => '/var/www/myapp',

Document root directory

Default: /var/www/<MONIKER>

gid

gid => 1000,
gid => getgrnam( 'anonymous' ),

This attribute pass GID to set the real group identifier and the effective group identifier for this process

homedir

homedir => '/usr/share/myapp',

The Project home directory

Default: /usr/share/<MONIKER>

logfile

logfile => '/var/log/myapp.log',

The log file

Default: /var/log/<MONIKER>.log

loglevel

loglevel => 'warn',

This attribute performs set the log level

Default: warn

max_history_size

max_history_size => 25,

Maximum number of logged messages to store in "history"

Default: 25

moniker

moniker => 'myapp',

Project name in lowercase notation, project nickname, moniker. This value often used as default filename for configuration files and the like

Default: decamelizing the application class

See "moniker" in Mojolicious

mysecret

mysecret => 'dgdfg',

Default secret string

Default: <DEFAULT_SECRET>

no_daemonize

no_daemonize => 1,

This attribute disables the daemonize process

Default: 0

pidfile

pidfile => '/var/run/myapp.pid',

The pid file

Default: /tmp/prefork.pid

See "pid_file" in Mojo::Server::Prefork

project_name

project_name => 'MyApp',

The project name. For example: MyApp

Default: current class name

private_key

private_key => '...'

Private RSA key

project_version

project_version => '0.01'

The project version. For example: 1.00

NOTE! This is required attribute!

public_key

public_key => '...',

Public RSA key

requests

requests => 0,

Maximum number of keep-alive requests per connection

Default: 100

See "max_requests" in Mojo::Server::Daemon

reload_sig

reload_sig => 'USR2',
reload_sig => 'HUP',

The signal name that will be used to receive reload commands from the system

Default: USR2

rsa_keysize

rsa_keysize => 2048

RSA key size

See RSA_KeySize configuration directive

Default: 2048

server_addr

server_addr => '*',

Main listener address (host)

Default: * (::0, 0:0:0:0)

server_port

server_port => 8080,

Main listener port

Default: 8080

server_url

server_url => 'http://127.0.0.1:8080',

Main real listener URL

See ListenAddr and ListenPort configuration directives

Default: http://127.0.0.1:8080

spare

spare => 0,

Temporarily spawn up to this number of additional workers if there is a need.

Default: 2

See "spare" in Mojo::Server::Prefork

tempdir

tempdir => '/tmp/myapp',

The temp directory

Default: /tmp/<MONIKER>

trustedproxies

List of trusted proxies

Default: none

uid

uid => 1000,
uid => getpwnam( 'anonymous' ),

This attribute pass UID to set the real user identifier and the effective user identifier for this process

workers

workers => 0,

Number of worker processes

Default: 4

See "workers" in Mojo::Server::Prefork

METHODS

This class inherits all methods from Mojolicious and implements the following new ones

init

$app->init;

This is your main hook into the Suffit application, it will be called at application startup immediately after calling the Mojolicious startup hook. Meant to be overloaded in a your subclass

listeners

This method returns server listeners as list of URLs

$prefork->listen( $app->listeners );

preforked_run

$app->preforked_run( COMMAND );
$app->preforked_run( COMMAND, ...OPTIONS... );
$app->preforked_run( COMMAND, { ...OPTIONS... } );
$app->preforked_run( 'start' );
$app->preforked_run( 'start', prerun => sub { ... } );
$app->preforked_run( 'stop' );
$app->preforked_run( 'restart', prerun => sub { ... } );
$app->preforked_run( 'status' );
$app->preforked_run( 'reload' );

This method runs your application using a command that is passed as the first argument

Options:

prerun
prerun => sub {
    my ($app, $prefork) = @_;

    $prefork->on(finish => sub { # Finish
        my $this = shift; # Prefork object
        my $graceful = shift;
        $this->app->log->debug($graceful
            ? 'Graceful server shutdown'
            : 'Server shutdown'
        );
    });
}

This option defines callback function that performs operations with prefork instance Mojo::Server::Prefork befor demonize and server running

raise

$app->raise("Mask %s", "val");
$app->raise("val");

Prints error message to STDERR and exit with errorlevel = 1

NOTE! For internal use only

reload

The reload hook

startup

Main "startup" in Mojolicious hook

HELPERS

This class implements the following helpers

authdb

This is access method to the AuthDB object (state object)

clientid

my $clientid = $app->clientid;

This helper returns client ID that calculates from User-Agent and Remote-Address headers:

md5(User-Agent . Remote-Address)

gen_cachekey

my $cachekey = $app->gen_cachekey;
my $cachekey = $app->gen_cachekey(16);

This helper helps generate the new CacheKey for caching user data that was got from authorization database

gen_rsakeys

my %keysdata = $app->gen_rsakeys;
my %keysdata = $app->gen_rsakeys( 2048 );

This helper generates RSA keys pair and returns structure as hash:

private_key => '...',
public_key  => '...',
key_size    => 2048,
error       => '...',

jwt

This helper makes JWT object with RSA keys and returns it

token

This helper performs get of current token from HTTP Request headers

CONFIGURATION

This class supports the following configuration directives

GENERAL DIRECTIVES

Log
Log         Syslog
Log         File

This directive defines the log provider. Supported providers: File, Syslog

Default: File

LogFile
LogFile     /var/log/myapp.log

This directive sets the path to logfile

Default: /var/log/<MONIKER>.log

LogLevel
LogLevel    warn

This directive defines log level.

Available log levels are trace, debug, info, warn, error and fatal, in that order.

Default: warn

SERVER DIRECTIVES

ListenURL
ListenURL http://127.0.0.1:8008
ListenURL http://127.0.0.1:8009
ListenURL 'https://*:3000?cert=/x/server.crt&key=/y/server.key&ca=/z/ca.crt'

Directives that specify additional listening addresses in URL form

NOTE! This is a multiple directive

Default: none

ListenAddr
ListenAddr  *
ListenAddr  0.0.0.0
ListenAddr  127.0.0.1

This directive sets the master listen address

Default: * (0.0.0.0)

ListenPort
ListenPort  8080
ListenPort  80
ListenPort  443

This directive sets the master listen port

Default: 8080

Accepts
Accepts     0

Maximum number of connections a worker is allowed to accept, before stopping gracefully and then getting replaced with a newly started worker, defaults to the value of "accepts" in Mojo::Server::Prefork. Setting the value to 0 will allow workers to accept new connections indefinitely

Default: 0

Clients
Clients     1000

Maximum number of accepted connections each worker process is allowed to handle concurrently, before stopping to accept new incoming connections, defaults to 100. Note that high concurrency works best with applications that perform mostly non-blocking operations, to optimize for blocking operations you can decrease this value and increase "workers" instead for better performance

Default: 1000

Requests
Requests    100

Maximum number of keep-alive requests per connection

Default: 100

Spare
Spare       2

Temporarily spawn up to this number of additional workers if there is a need, defaults to 2. This allows for new workers to be started while old ones are still shutting down gracefully, drastically reducing the performance cost of worker restarts

Default: 2

Workers
Workers     4

Number of worker processes, defaults to 4. A good rule of thumb is two worker processes per CPU core for applications that perform mostly non-blocking operations, blocking operations often require more and benefit from decreasing concurrency with "clients" (often as low as 1)

Default: 4

TrustedProxy
TrustedProxy  127.0.0.1
TrustedProxy  10.0.0.0/8
TrustedProxy  172.16.0.0/12
TrustedProxy  192.168.0.0/16
TrustedProxy  fc00::/7

Trusted reverse proxies, addresses or networks in CIDR form. The real IP address takes from X-Forwarded-For header

NOTE! This is a multiple directive

Default: All reverse proxies will be passed

Reload_Sig
Reload_Sig  USR2
Reload_Sig  HUP

This directive sets the dafault signal name that will be used to receive reload commands from the system

Default: USR2

SSL/TLS SERVER DIRECTIVES

TLS
TLS         enabled

This directive enables or disables the TLS (https) listening

Default: disabled

TLS_CA, TLS_Cert, TLS_Key
TLS_CA      certs/ca.crt
TLS_Cert    certs/server.crt
TLS_Key     certs/server.key

Paths to TLS files. Absolute or relative paths (started from /etc/<MONIKER>)

TLS_CA - Path to TLS certificate authority file used to verify the peer certificate. TLS_Cert - Path to the TLS cert file, defaults to a built-in test certificate. TLS_Key - Path to the TLS key file, defaults to a built-in test key

Default: none

TLS_Ciphers, TLS_Verify, TLS_Version
TLS_Version     TLSv1_2
TLS_Ciphers     AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH
TLS_Verify      0x00

Directives for setting TLS extra data

TLS cipher specification string. For more information about the format see https://www.openssl.org/docs/manmaster/man1/ciphers.html/CIPHER-STRINGS. TLS_Verify - TLS verification mode. TLS_Version - TLS protocol version.

Default: none

TLS_FD, TLS_Reuse, TLS_Single_Accept

TLS_FD - File descriptor with an already prepared listen socket. TLS_Reuse - Allow multiple servers to use the same port with the SO_REUSEPORT socket option. TLS_Single_Accept - Only accept one connection at a time.

SECURITY DIRECTIVES

PrivateKeyFile, PublicKeyFile
PrivateKeyFile /var/lib/myapp/rsa-private.key
PublicKeyFile  /var/lib/myapp/rsa-public.key

Private and Public RSA key files If not possible to read files by the specified paths, they will be created automatically

Defaults:

PrivateKeyFile /var/lib/E<lt>MONIKERE<gt>/rsa-private.key
PublicKeyFile  /var/lib/E<lt>MONIKERE<gt>/rsa-public.key
RSA_KeySize
RSA_KeySize     2048

RSA Key size. This is size (length) of the RSA Key. Allowed key sizes in bits: 512, 1024, 2048, 3072, 4096

Default: 2048

Secret
Secret      "My$ecretPhr@se!"

HMAC secret passphrase

Default: md5(rsa_private_file)

ATHORIZATION DIRECTIVES

AuthDBURL, AuthDBURI
AuthDBURI "mysql://user:pass@mysql.example.com/authdb \
       ?mysql_auto_reconnect=1&mysql_enable_utf8=1"
AuthDBURI "sqlite:///var/lib/myapp/auth.db?sqlite_unicode=1"

Authorization database connect string (Data Source URI) This directive written in the URI form

Default: "sqlite:///var/lib/<MONIKER>/auth.db?sqlite_unicode=1"

AuthDBCachedConnection
AuthDBCachedConnection  1
AuthDBCachedConnection  Yes
AuthDBCachedConnection  On
AuthDBCachedConnection  Enable

This directive defines status of caching while establishing of connection to database

See "cached" in WWW::Suffit::AuthDB

Default: false (no caching connection)

AuthDBCacheExpire, AuthDBCacheExpiration
AuthDBCacheExpiration    300

The expiration time

See "expiration" in WWW::Suffit::AuthDB

Default: 300 (5 min)

AuthDBCacheMaxKeys
AuthDBCacheMaxKeys  1024

The maximum keys number in cache

See "max_keys" in WWW::Suffit::AuthDB

Default: 1024*1024 (1`048`576 keys max)

AuthDBSourceFile
AuthDBSourceFile /var/lib/myapp/authdb.json

Authorization database source file path. This is simple JSON file that contains three blocks: users, groups and realms.

Default: /var/lib/<MONIKER>/authdb.json

Token
Token   ed23...3c0a

Development token directive This development directive allows authorization without getting real Authorization header from the client request

Default: none

EXAMPLE

Example of well-structured simplified web application

# mkdir lib
# touch lib/MyApp.pm
# chmod 644 lib/MyApp.pm

We will start by MyApp.pm that contains main application class and controller class

package MyApp;

use Mojo::Base 'WWW::Suffit::Server';

our $VERSION = '1.00';

sub init {
    my $self = shift;
    my $r = $self->routes;
    $r->any('/' => {text => 'Your test server is working!'})->name('index');
    $r->get('/test')->to('example#test')->name('test');
}

1;

package MyApp::Controller::Example;

use Mojo::Base 'Mojolicious::Controller';

sub test {
    my $self = shift;
    $self->render(text => 'Hello World!');
}

1;

The init method gets called right after instantiation and is the place where the whole your application gets set up

# mkdir bin
# touch bin/myapp.pl
# chmod 644 bin/myapp.pl

myapp.pl itself can now be created as simplified application script to allow running tests.

#!/usr/bin/perl -w
use strict;
use warnings;

use Mojo::File qw/ curfile path /;

use lib curfile->dirname->sibling('lib')->to_string;

use Mojo::Server;

my $root = curfile->dirname->child('test')->to_string;

Mojo::Server->new->build_app('MyApp',
    debugmode => 1,
    loglevel => 'debug',
    homedir => path($root)->child('www')->make_path->to_string,
    datadir => path($root)->child('var')->make_path->to_string,
    tempdir => path($root)->child('tmp')->make_path->to_string,
    config_opts     => {
        noload => 1, # force disable loading config from file
        defaults => {
            foo => 'bar',
        },
    },
)->start();

Now try to run it

# perl bin/myapp.pl daemon -l http://*:8080

Now let's get to simplified testing

# mkdir t
# touch t/myapp.t
# chmod 644 t/myapp.t

Full Mojolicious applications are easy to test, so t/myapp.t can be containts:

use strict;
use warnings;

use Test::More;
use Test::Mojo;

use Mojo::File qw/ path /;

use MyApp;

my $root = path()->child('test')->to_string;

my $t = Test::Mojo->new(MyApp->new(
    homedir => path($root)->child('www')->make_path->to_string,
    datadir => path($root)->child('var')->make_path->to_string,
    tempdir => path($root)->child('tmp')->make_path->to_string,
    config_opts     => {
        noload => 1, # force disable loading config from file
        defaults => {
            foo => 'bar',
        },
    },
));

subtest 'Test workflow' => sub {

    $t->get_ok('/')
      ->status_is(200)
      ->content_like(qr/working!/, 'right content by GET /');

    $t->post_ok('/' => form => {'_' => time})
      ->status_is(200)
      ->content_like(qr/working!/, 'right content by POST /');

    $t->get_ok('/test')
      ->status_is(200)
      ->content_like(qr/World/, 'right content by GET /test');

};

done_testing();

Now try to test

# prove -lv t/myapp.t

And our final directory structure should be looking like this

MyApp
+- bin
|  +- myapp.pl
+- lib
|  +- MyApp.pm
+- t
|  +- myapp.t
+- test
   +- tmp
   +- var
   +- www

Test-driven development takes a little getting used to, but can be a very powerful tool

HISTORY

See Changes file

TO DO

See TODO file

SEE ALSO

Mojolicious, WWW::Suffit, WWW::Suffit::RSA, WWW::Suffit::JWT, WWW::Suffit::API, WWW::Suffit::AuthDB

AUTHOR

Serż Minus (Sergey Lepenkov) https://www.serzik.com <abalama@cpan.org>

COPYRIGHT

Copyright (C) 1998-2024 D&D Corporation. All Rights Reserved

LICENSE

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

See LICENSE file and https://dev.perl.org/licenses/