File Coverage

blib/lib/WWW/Suffit/Server.pm
Criterion Covered Total %
statement 60 290 20.6
branch 0 176 0.0
condition 0 146 0.0
subroutine 20 37 54.0
pod 6 6 100.0
total 86 655 13.1


line stmt bran cond sub pod time code
1             package WWW::Suffit::Server;
2 1     1   134457 use strict;
  1         3  
  1         41  
3 1     1   5 use warnings;
  1         9  
  1         63  
4 1     1   734 use utf8;
  1         348  
  1         8  
5              
6             =encoding utf8
7              
8             =head1 NAME
9              
10             WWW::Suffit::Server - The Suffit API web-server class
11              
12             =head1 SYNOPSIS
13              
14             use Mojo::File qw/ path /;
15              
16             my $root = path()->child('test')->to_string;
17             my $app = MyApp->new(
18             project_name => 'MyApp',
19             project_version => '0.01',
20             moniker => 'myapp',
21             debugmode => 1,
22             loglevel => 'debug',
23             max_history_size => 25,
24              
25             # System
26             uid => 1000,
27             gid => 1000,
28              
29             # Dirs and files
30             homedir => path($root)->child('share')->make_path->to_string,
31             datadir => path($root)->child('var')->make_path->to_string,
32             tempdir => path($root)->child('tmp')->make_path->to_string,
33             documentroot => path($root)->child('www')->make_path->to_string,
34             logfile => path($root)->child('log')->make_path->child('myapp.log')->to_string,
35             pidfile => path($root)->child('run')->make_path->child('myapp.pid')->to_string,
36              
37             # Server
38             server_addr => '*',
39             server_port => 8080,
40             server_url => 'http://127.0.0.1:8080',
41             trustedproxies => ['127.0.0.1'],
42             accepts => 10000,
43             clients => 1000,
44             requests => 100,
45             workers => 4,
46             spare => 2,
47             reload_sig => 'USR2',
48             no_daemonize => 1,
49              
50             # Security
51             mysecret => 'Eph9Ce$quo.p2@oW3',
52             rsa_keysize => 2048,
53             private_key => undef, # Auto
54             public_key => undef, # Auto
55              
56             # Initialization options
57             all_features => 'no',
58             config_opts => {
59             file => path($root)->child('etc')->make_path->child('myapp.conf')->to_string,
60             defaults => {foo => 'bar'},
61             },
62             );
63              
64             # Run preforked application
65             $app->preforked_run( 'start' );
66              
67             1;
68              
69             package MyApp;
70              
71             use Mojo::Base 'WWW::Suffit::Server';
72              
73             sub init { shift->routes->any('/' => {text => 'Hello World!'}) }
74              
75             1;
76              
77             =head1 DESCRIPTION
78              
79             This module provides API web-server functionality
80              
81             =head1 OPTIONS
82              
83             sub startup {
84             my $self = shift->SUPER::startup( OPTION_NAME => VALUE, ... );
85              
86             # ...
87             }
88              
89             Options passed as arguments to the startup function allow you to customize
90             the initialization of plugins at the level of your descendant class, and
91             options are considered to have higher priority than attributes of the same name.
92              
93             List of allowed options (pairs of name-value):
94              
95             =head2 admin_routes_opts
96              
97             admin_routes_opts => {
98             prefix_path => "/admin",
99             prefix_name => "admin",
100             }
101              
102             =over 8
103              
104             =item prefix_name
105              
106             prefix_name => "admin"
107              
108             This option defines prefix of admin api route name
109              
110             Default: 'admin'
111              
112             =item prefix_path
113              
114             prefix_path => "/admin"
115              
116             This option defines prefix of admin api route
117              
118             Default: '/admin'
119              
120             =back
121              
122             =head2 all_features
123              
124             all_features => 'on'
125              
126             This option enables all of the init_* options, which are described bellow
127              
128             Default: off
129              
130             =head2 api_routes_opts
131              
132             api_routes_opts => {
133             prefix_path => "/api",
134             prefix_name => "api",
135             }
136              
137             =over 8
138              
139             =item prefix_name
140              
141             prefix_name => "api"
142              
143             This option defines prefix of api route name
144              
145             Default: 'api'
146              
147             =item prefix_path
148              
149             prefix_path => "/api"
150              
151             This option defines prefix of api route
152              
153             Default: '/api'
154              
155             =back
156              
157             =head2 authdb_opts
158              
159             authdb_opts => {
160             uri => "sqlite://?sqlite_unicode=1",
161             cachedconnection => 'on',
162             cacheexpiration => 300,
163             cachemaxkeys => 1024*1024,
164             sourcefile => '/tmp/authdb.json',
165             }
166              
167             =over 8
168              
169             =item uri, url
170              
171             uri => "sqlite:///tmp/auth.db?sqlite_unicode=1",
172              
173             Default: See config C or C directive
174              
175             =item cachedconnection
176              
177             cachedconnection => 'on',
178              
179             Default: See config C directive. Default: on
180              
181             =item cacheexpire, cacheexpiration
182              
183             cacheexpiration => 300,
184              
185             Default: See config C or C directive. Default: 300
186              
187             =item cachemaxkeys
188              
189             cachemaxkeys => 1024*1024,
190              
191             Default: See config C directive. Default: 1024*1024
192              
193             =item sourcefile
194              
195             sourcefile => '/tmp/authdb.json',
196              
197             Default: See config C directive
198              
199             =back
200              
201             =head2 config_opts
202              
203             config_opts => { ... }
204              
205             This option sets L plugin options
206              
207             Default:
208              
209             `noload => 1` if $self->configobj exists
210             `defaults => $self->config` if $self->config is not void
211              
212             =head2 init_admin_routes
213              
214             init_admin_routes => 'on'
215              
216             This option enables Admin Suffit API routes
217              
218             Default: off
219              
220             =head2 init_authdb
221              
222             init_authdb => 'on'
223              
224             This option enables AuthDB initialize
225              
226             Default: off
227              
228             =head2 init_api_routes
229              
230             init_api_routes => 'on'
231              
232             This option enables Suffit API routes
233              
234             Default: off
235              
236             =head2 init_rsa_keys
237              
238             init_rsa_keys => 'on'
239              
240             This option enables RSA keys initialize
241              
242             Default: off
243              
244             =head2 init_user_routes
245              
246             init_user_routes => 'on'
247              
248             This option enables User Suffit API routes
249              
250             Default: off
251              
252             =head2 syslog_opts
253              
254             syslog_opts => { ... }
255              
256             This option sets L plugin options
257              
258             Default:
259              
260             `enable => 1` if the `Log` config directive is "syslog"
261              
262             =head2 user_routes_opts
263              
264             user_routes_opts => {
265             prefix_path => "/user",
266             prefix_name => "user",
267             }
268              
269             =over 8
270              
271             =item prefix_name
272              
273             prefix_name => "user"
274              
275             This option defines prefix of user api route name
276              
277             Default: 'user'
278              
279             =item prefix_path
280              
281             prefix_path => "/user"
282              
283             This option defines prefix of user api route
284              
285             Default: '/user'
286              
287             =back
288              
289             =head1 ATTRIBUTES
290              
291             This class implements the following attributes
292              
293             =head2 accepts
294              
295             accepts => 0,
296              
297             Maximum number of connections a worker is allowed to accept, before stopping
298             gracefully and then getting replaced with a newly started worker,
299             passed along to L
300              
301             Default: 10000
302              
303             See L
304              
305             =head2 cache
306              
307             The WWW::Suffit::Cache object
308              
309             =head2 clients
310              
311             clients => 0,
312              
313             Maximum number of accepted connections this server is allowed to handle concurrently,
314             before stopping to accept new incoming connections, passed along to L
315              
316             Default: 1000
317              
318             See L
319              
320             =head2 configobj
321              
322             The Config::General object or undef
323              
324             =head2 acruxconfig
325              
326             The L object or undef
327              
328             =head2 datadir
329              
330             datadir => '/var/lib/myapp',
331              
332             The sharedstate data directory (data dir)
333              
334             Default: /var/lib/
335              
336             =head2 debugmode
337              
338             debugmode => 0,
339              
340             If this attribute is enabled then this server is no daemonize performs
341              
342             =head2 documentroot
343              
344             documentroot => '/var/www/myapp',
345              
346             Document root directory
347              
348             Default: /var/www/
349              
350             =head2 gid
351              
352             gid => 1000,
353             gid => getgrnam( 'anonymous' ),
354              
355             This attribute pass GID to set the real group identifier and the effective group identifier for this process
356              
357             =head2 homedir
358              
359             homedir => '/usr/share/myapp',
360              
361             The Project home directory
362              
363             Default: /usr/share/
364              
365             =head2 logfile
366              
367             logfile => '/var/log/myapp.log',
368              
369             The log file
370              
371             Default: /var/log/.log
372              
373             =head2 loglevel
374              
375             loglevel => 'warn',
376              
377             This attribute performs set the log level
378              
379             Default: warn
380              
381             =head2 max_history_size
382              
383             max_history_size => 25,
384              
385             Maximum number of logged messages to store in "history"
386              
387             Default: 25
388              
389             =head2 moniker
390              
391             moniker => 'myapp',
392              
393             Project name in lowercase notation, project nickname, moniker.
394             This value often used as default filename for configuration files and the like
395              
396             Default: decamelizing the application class
397              
398             See L
399              
400             =head2 mysecret
401              
402             mysecret => 'dgdfg',
403              
404             Default secret string
405              
406             Default:
407              
408             =head2 no_daemonize
409              
410             no_daemonize => 1,
411              
412             This attribute disables the daemonize process
413              
414             Default: 0
415              
416             =head2 pidfile
417              
418             pidfile => '/var/run/myapp.pid',
419              
420             The pid file
421              
422             Default: /tmp/prefork.pid
423              
424             See L
425              
426             =head2 project_name
427              
428             project_name => 'MyApp',
429              
430             The project name. For example: MyApp
431              
432             Default: current class name
433              
434             =head2 private_key
435              
436             private_key => '...'
437              
438             Private RSA key
439              
440             =head2 project_version
441              
442             project_version => '0.01'
443              
444             The project version. For example: 1.00
445              
446             B This is required attribute!
447              
448             =head2 public_key
449              
450             public_key => '...',
451              
452             Public RSA key
453              
454             =head2 requests
455              
456             requests => 0,
457              
458             Maximum number of keep-alive requests per connection
459              
460             Default: 100
461              
462             See L
463              
464             =head2 reload_sig
465              
466             reload_sig => 'USR2',
467             reload_sig => 'HUP',
468              
469             The signal name that will be used to receive reload commands from the system
470              
471             Default: USR2
472              
473             =head2 rsa_keysize
474              
475             rsa_keysize => 2048
476              
477             RSA key size
478              
479             See C configuration directive
480              
481             Default: 2048
482              
483             =head2 server_addr
484              
485             server_addr => '*',
486              
487             Main listener address (host)
488              
489             Default: * (::0, 0:0:0:0)
490              
491             =head2 server_port
492              
493             server_port => 8080,
494              
495             Main listener port
496              
497             Default: 8080
498              
499             =head2 server_url
500              
501             server_url => 'http://127.0.0.1:8080',
502              
503             Main real listener URL
504              
505             See C and C configuration directives
506              
507             Default: http://127.0.0.1:8080
508              
509             =head2 spare
510              
511             spare => 0,
512              
513             Temporarily spawn up to this number of additional workers if there is a need.
514              
515             Default: 2
516              
517             See L
518              
519             =head2 tempdir
520              
521             tempdir => '/tmp/myapp',
522              
523             The temp directory
524              
525             Default: /tmp/
526              
527             =head2 trustedproxies
528              
529             List of trusted proxies
530              
531             Default: none
532              
533             =head2 uid
534              
535             uid => 1000,
536             uid => getpwnam( 'anonymous' ),
537              
538             This attribute pass UID to set the real user identifier and the effective user identifier for this process
539              
540             =head2 workers
541              
542             workers => 0,
543              
544             Number of worker processes
545              
546             Default: 4
547              
548             See L
549              
550             =head1 METHODS
551              
552             This class inherits all methods from L and implements the following new ones
553              
554             =head2 init
555              
556             $app->init;
557              
558             This is your main hook into the Suffit application, it will be called at application startup
559             immediately after calling the Mojolicious startup hook. Meant to be overloaded in a your subclass
560              
561             =head2 listeners
562              
563             This method returns server listeners as list of URLs
564              
565             $prefork->listen( $app->listeners );
566              
567             =head2 preforked_run
568              
569             $app->preforked_run( COMMAND );
570             $app->preforked_run( COMMAND, ...OPTIONS... );
571             $app->preforked_run( COMMAND, { ...OPTIONS... } );
572             $app->preforked_run( 'start' );
573             $app->preforked_run( 'start', prerun => sub { ... } );
574             $app->preforked_run( 'stop' );
575             $app->preforked_run( 'restart', prerun => sub { ... } );
576             $app->preforked_run( 'status' );
577             $app->preforked_run( 'reload' );
578              
579             This method runs your application using a command that is passed as the first argument
580              
581             B
582              
583             =over 8
584              
585             =item prerun
586              
587             prerun => sub {
588             my ($app, $prefork) = @_;
589              
590             $prefork->on(finish => sub { # Finish
591             my $this = shift; # Prefork object
592             my $graceful = shift;
593             $this->app->log->debug($graceful
594             ? 'Graceful server shutdown'
595             : 'Server shutdown'
596             );
597             });
598             }
599              
600             This option defines callback function that performs operations with prefork
601             instance L befor demonize and server running
602              
603             =back
604              
605             =head2 raise
606              
607             $app->raise("Mask %s", "val");
608             $app->raise("val");
609              
610             Prints error message to STDERR and exit with errorlevel = 1
611              
612             B For internal use only
613              
614             =head2 reload
615              
616             The reload hook
617              
618             =head2 startup
619              
620             Main L hook
621              
622             =head1 HELPERS
623              
624             This class implements the following helpers
625              
626             =head2 authdb
627              
628             This is access method to the AuthDB object (state object)
629              
630             =head2 clientid
631              
632             my $clientid = $app->clientid;
633              
634             This helper returns client ID that calculates from C
635             and C headers:
636              
637             md5(User-Agent . Remote-Address)
638              
639             =head2 gen_cachekey
640              
641             my $cachekey = $app->gen_cachekey;
642             my $cachekey = $app->gen_cachekey(16);
643              
644             This helper helps generate the new CacheKey for caching user data
645             that was got from authorization database
646              
647             =head2 gen_rsakeys
648              
649             my %keysdata = $app->gen_rsakeys;
650             my %keysdata = $app->gen_rsakeys( 2048 );
651              
652             This helper generates RSA keys pair and returns structure as hash:
653              
654             private_key => '...',
655             public_key => '...',
656             key_size => 2048,
657             error => '...',
658              
659             =head2 jwt
660              
661             This helper makes JWT object with RSA keys and returns it
662              
663             =head2 token
664              
665             This helper performs get of current token from HTTP Request headers
666              
667             =head1 CONFIGURATION
668              
669             This class supports the following configuration directives
670              
671             =head2 GENERAL DIRECTIVES
672              
673             =over 8
674              
675             =item Log
676              
677             Log Syslog
678             Log File
679              
680             This directive defines the log provider. Supported providers: C, C
681              
682             Default: File
683              
684             =item LogFile
685              
686             LogFile /var/log/myapp.log
687              
688             This directive sets the path to logfile
689              
690             Default: /var/log/EMONIKERE.log
691              
692             =item LogLevel
693              
694             LogLevel warn
695              
696             This directive defines log level.
697              
698             Available log levels are C, C, C, C, C and C, in that order.
699              
700             Default: warn
701              
702             =back
703              
704             =head2 SERVER DIRECTIVES
705              
706             =over 8
707              
708             =item ListenURL
709              
710             ListenURL http://127.0.0.1:8008
711             ListenURL http://127.0.0.1:8009
712             ListenURL 'https://*:3000?cert=/x/server.crt&key=/y/server.key&ca=/z/ca.crt'
713              
714             Directives that specify additional listening addresses in URL form
715              
716             B This is a multiple directive
717              
718             Default: none
719              
720             =item ListenAddr
721              
722             ListenAddr *
723             ListenAddr 0.0.0.0
724             ListenAddr 127.0.0.1
725              
726             This directive sets the master listen address
727              
728             Default: * (0.0.0.0)
729              
730             =item ListenPort
731              
732             ListenPort 8080
733             ListenPort 80
734             ListenPort 443
735              
736             This directive sets the master listen port
737              
738             Default: 8080
739              
740             =item Accepts
741              
742             Accepts 0
743              
744             Maximum number of connections a worker is allowed to accept, before
745             stopping gracefully and then getting replaced with a newly started worker,
746             defaults to the value of "accepts" in L.
747             Setting the value to 0 will allow workers to accept new connections
748             indefinitely
749              
750             Default: 0
751              
752             =item Clients
753              
754             Clients 1000
755              
756             Maximum number of accepted connections each worker process is allowed to
757             handle concurrently, before stopping to accept new incoming connections,
758             defaults to 100. Note that high concurrency works best with applications
759             that perform mostly non-blocking operations, to optimize for blocking
760             operations you can decrease this value and increase "workers" instead
761             for better performance
762              
763             Default: 1000
764              
765             =item Requests
766              
767             Requests 100
768              
769             Maximum number of keep-alive requests per connection
770              
771             Default: 100
772              
773             =item Spare
774              
775             Spare 2
776              
777             Temporarily spawn up to this number of additional workers if there
778             is a need, defaults to 2. This allows for new workers to be started while
779             old ones are still shutting down gracefully, drastically reducing the
780             performance cost of worker restarts
781              
782             Default: 2
783              
784             =item Workers
785              
786             Workers 4
787              
788             Number of worker processes, defaults to 4. A good rule of thumb is two
789             worker processes per CPU core for applications that perform mostly
790             non-blocking operations, blocking operations often require more and
791             benefit from decreasing concurrency with "clients" (often as low as 1)
792              
793             Default: 4
794              
795             =item TrustedProxy
796              
797             TrustedProxy 127.0.0.1
798             TrustedProxy 10.0.0.0/8
799             TrustedProxy 172.16.0.0/12
800             TrustedProxy 192.168.0.0/16
801             TrustedProxy fc00::/7
802              
803             Trusted reverse proxies, addresses or networks in C form.
804             The real IP address takes from C header
805              
806             B This is a multiple directive
807              
808             Default: All reverse proxies will be passed
809              
810             =item Reload_Sig
811              
812             Reload_Sig USR2
813             Reload_Sig HUP
814              
815             This directive sets the dafault signal name that will be used to receive reload commands from the system
816              
817             Default: USR2
818              
819             =back
820              
821             =head2 SSL/TLS SERVER DIRECTIVES
822              
823             =over 8
824              
825             =item TLS
826              
827             TLS enabled
828              
829             This directive enables or disables the TLS (https) listening
830              
831             Default: disabled
832              
833             =item TLS_CA, TLS_Cert, TLS_Key
834              
835             TLS_CA certs/ca.crt
836             TLS_Cert certs/server.crt
837             TLS_Key certs/server.key
838              
839             Paths to TLS files. Absolute or relative paths (started from /etc/EMONIKERE)
840              
841             B - Path to TLS certificate authority file used to verify the peer certificate.
842             B - Path to the TLS cert file, defaults to a built-in test certificate.
843             B - Path to the TLS key file, defaults to a built-in test key
844              
845             Default: none
846              
847             =item TLS_Ciphers, TLS_Verify, TLS_Version
848              
849             TLS_Version TLSv1_2
850             TLS_Ciphers AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH
851             TLS_Verify 0x00
852              
853             Directives for setting TLS extra data
854              
855             TLS cipher specification string. For more information about the format see
856             L.
857             B - TLS verification mode. B - TLS protocol version.
858              
859             Default: none
860              
861             =item TLS_FD, TLS_Reuse, TLS_Single_Accept
862              
863             B - File descriptor with an already prepared listen socket.
864             B - Allow multiple servers to use the same port with the C socket option.
865             B - Only accept one connection at a time.
866              
867             =back
868              
869             =head2 SECURITY DIRECTIVES
870              
871             =over 8
872              
873             =item PrivateKeyFile, PublicKeyFile
874              
875             PrivateKeyFile /var/lib/myapp/rsa-private.key
876             PublicKeyFile /var/lib/myapp/rsa-public.key
877              
878             Private and Public RSA key files
879             If not possible to read files by the specified paths, they will
880             be created automatically
881              
882             Defaults:
883              
884             PrivateKeyFile /var/lib/EMONIKERE/rsa-private.key
885             PublicKeyFile /var/lib/EMONIKERE/rsa-public.key
886              
887             =item RSA_KeySize
888              
889             RSA_KeySize 2048
890              
891             RSA Key size. This is size (length) of the RSA Key.
892             Allowed key sizes in bits: C<512>, C<1024>, C<2048>, C<3072>, C<4096>
893              
894             Default: 2048
895              
896             =item Secret
897              
898             Secret "My$ecretPhr@se!"
899              
900             HMAC secret passphrase
901              
902             Default: md5(rsa_private_file)
903              
904             =back
905              
906             =head2 ATHORIZATION DIRECTIVES
907              
908             =over 8
909              
910             =item AuthDBURL, AuthDBURI
911              
912             AuthDBURI "mysql://user:pass@mysql.example.com/authdb \
913             ?mysql_auto_reconnect=1&mysql_enable_utf8=1"
914             AuthDBURI "sqlite:///var/lib/myapp/auth.db?sqlite_unicode=1"
915              
916             Authorization database connect string (Data Source URI)
917             This directive written in the URI form
918              
919             Default: "sqlite:///var/lib/EMONIKERE/auth.db?sqlite_unicode=1"
920              
921             =item AuthDBCachedConnection
922              
923             AuthDBCachedConnection 1
924             AuthDBCachedConnection Yes
925             AuthDBCachedConnection On
926             AuthDBCachedConnection Enable
927              
928             This directive defines status of caching while establishing of connection to database
929              
930             See L
931              
932             Default: false (no caching connection)
933              
934             =item AuthDBCacheExpire, AuthDBCacheExpiration
935              
936             AuthDBCacheExpiration 300
937              
938             The expiration time
939              
940             See L
941              
942             Default: 300 (5 min)
943              
944             =item AuthDBCacheMaxKeys
945              
946             AuthDBCacheMaxKeys 1024
947              
948             The maximum keys number in cache
949              
950             See L
951              
952             Default: 1024*1024 (1`048`576 keys max)
953              
954             =item AuthDBSourceFile
955              
956             AuthDBSourceFile /var/lib/myapp/authdb.json
957              
958             Authorization database source file path.
959             This is simple JSON file that contains three blocks: users, groups and realms.
960              
961             Default: /var/lib/EMONIKERE/authdb.json
962              
963             =item Token
964              
965             Token ed23...3c0a
966              
967             Development token directive
968             This development directive allows authorization without getting real C
969             header from the client request
970              
971             Default: none
972              
973             =back
974              
975             =head1 EXAMPLE
976              
977             Example of well-structured simplified web application
978              
979             # mkdir lib
980             # touch lib/MyApp.pm
981             # chmod 644 lib/MyApp.pm
982              
983             We will start by C that contains main application class and controller class
984              
985             package MyApp;
986              
987             use Mojo::Base 'WWW::Suffit::Server';
988              
989             our $VERSION = '1.00';
990              
991             sub init {
992             my $self = shift;
993             my $r = $self->routes;
994             $r->any('/' => {text => 'Your test server is working!'})->name('index');
995             $r->get('/test')->to('example#test')->name('test');
996             }
997              
998             1;
999              
1000             package MyApp::Controller::Example;
1001              
1002             use Mojo::Base 'Mojolicious::Controller';
1003              
1004             sub test {
1005             my $self = shift;
1006             $self->render(text => 'Hello World!');
1007             }
1008              
1009             1;
1010              
1011             The C method gets called right after instantiation and is the place where the whole your application gets set up
1012              
1013             # mkdir bin
1014             # touch bin/myapp.pl
1015             # chmod 644 bin/myapp.pl
1016              
1017             C itself can now be created as simplified application script to allow running tests.
1018              
1019             #!/usr/bin/perl -w
1020             use strict;
1021             use warnings;
1022              
1023             use Mojo::File qw/ curfile path /;
1024              
1025             use lib curfile->dirname->sibling('lib')->to_string;
1026              
1027             use Mojo::Server;
1028              
1029             my $root = curfile->dirname->child('test')->to_string;
1030              
1031             Mojo::Server->new->build_app('MyApp',
1032             debugmode => 1,
1033             loglevel => 'debug',
1034             homedir => path($root)->child('www')->make_path->to_string,
1035             datadir => path($root)->child('var')->make_path->to_string,
1036             tempdir => path($root)->child('tmp')->make_path->to_string,
1037             config_opts => {
1038             noload => 1, # force disable loading config from file
1039             defaults => {
1040             foo => 'bar',
1041             },
1042             },
1043             )->start();
1044              
1045             Now try to run it
1046              
1047             # perl bin/myapp.pl daemon -l http://*:8080
1048              
1049             Now let's get to simplified testing
1050              
1051             # mkdir t
1052             # touch t/myapp.t
1053             # chmod 644 t/myapp.t
1054              
1055             Full L applications are easy to test, so C can be containts:
1056              
1057             use strict;
1058             use warnings;
1059              
1060             use Test::More;
1061             use Test::Mojo;
1062              
1063             use Mojo::File qw/ path /;
1064              
1065             use MyApp;
1066              
1067             my $root = path()->child('test')->to_string;
1068              
1069             my $t = Test::Mojo->new(MyApp->new(
1070             homedir => path($root)->child('www')->make_path->to_string,
1071             datadir => path($root)->child('var')->make_path->to_string,
1072             tempdir => path($root)->child('tmp')->make_path->to_string,
1073             config_opts => {
1074             noload => 1, # force disable loading config from file
1075             defaults => {
1076             foo => 'bar',
1077             },
1078             },
1079             ));
1080              
1081             subtest 'Test workflow' => sub {
1082              
1083             $t->get_ok('/')
1084             ->status_is(200)
1085             ->content_like(qr/working!/, 'right content by GET /');
1086              
1087             $t->post_ok('/' => form => {'_' => time})
1088             ->status_is(200)
1089             ->content_like(qr/working!/, 'right content by POST /');
1090              
1091             $t->get_ok('/test')
1092             ->status_is(200)
1093             ->content_like(qr/World/, 'right content by GET /test');
1094              
1095             };
1096              
1097             done_testing();
1098              
1099             Now try to test
1100              
1101             # prove -lv t/myapp.t
1102              
1103             And our final directory structure should be looking like this
1104              
1105             MyApp
1106             +- bin
1107             | +- myapp.pl
1108             +- lib
1109             | +- MyApp.pm
1110             +- t
1111             | +- myapp.t
1112             +- test
1113             +- tmp
1114             +- var
1115             +- www
1116              
1117             Test-driven development takes a little getting used to, but can be a very powerful tool
1118              
1119             =head1 HISTORY
1120              
1121             See C file
1122              
1123             =head1 TO DO
1124              
1125             See C file
1126              
1127             =head1 SEE ALSO
1128              
1129             L, L, L, L, L, L
1130              
1131             =head1 AUTHOR
1132              
1133             Serż Minus (Sergey Lepenkov) L Eabalama@cpan.orgE
1134              
1135             =head1 COPYRIGHT
1136              
1137             Copyright (C) 1998-2026 D&D Corporation
1138              
1139             =head1 LICENSE
1140              
1141             This program is distributed under the terms of the Artistic License Version 2.0
1142              
1143             See the C file or L for details
1144              
1145             =cut
1146              
1147             our $VERSION = '1.13';
1148              
1149 1     1   1098 use Mojo::Base 'Mojolicious';
  1         13850  
  1         6  
1150              
1151 1     1   620734 use Carp qw/ carp croak /;
  1         3  
  1         120  
1152 1     1   8 use POSIX qw//;
  1         3  
  1         27  
1153 1     1   7 use File::Spec;
  1         2  
  1         28  
1154              
1155 1     1   5 use Mojo::URL;
  1         5  
  1         9  
1156 1     1   42 use Mojo::File qw/ path /;
  1         2  
  1         61  
1157 1     1   7 use Mojo::Home qw//;
  1         26  
  1         33  
1158 1     1   6 use Mojo::Util qw/ decamelize steady_time md5_sum /; # decamelize(ref($self))
  1         2  
  1         100  
1159 1     1   7 use Mojo::Loader qw/ load_class /;
  1         13  
  1         59  
1160 1     1   917 use Mojo::Server::Prefork;
  1         4891  
  1         14  
1161              
1162 1     1   1051 use Acrux::Util qw/ color parse_time_offset randchars /;
  1         12167  
  1         151  
1163 1     1   731 use Acrux::RefUtil qw/ as_array_ref as_hash_ref isnt_void is_true_flag /;
  1         2915  
  1         141  
1164              
1165 1         467 use WWW::Suffit::Const qw/
1166             :general :security :session :dir :server
1167             AUTHDBFILE JWT_REGEXP
1168 1     1   949 /;
  1         3544  
1169 1     1   894 use WWW::Suffit::Cache;
  1         2042  
  1         7  
1170 1     1   852 use WWW::Suffit::RSA;
  1         6326  
  1         11  
1171 1     1   1021 use WWW::Suffit::JWT;
  1         4638  
  1         10  
1172              
1173             use constant {
1174 1         7501 MAX_HISTORY_SIZE => 25,
1175             DEFAULT_SERVER_URL => 'http://127.0.0.1:8080',
1176             DEFAULT_SERVER_ADDR => '*',
1177             DEFAULT_SERVER_PORT => 8080,
1178 1     1   79 };
  1         3  
1179              
1180             # Common attributes
1181             has 'project_name'; # Anonymous
1182             has 'project_version'; # 1.00
1183             has 'server_url'; # http://127.0.0.1:8080
1184             has 'server_addr'; # * (0.0.0.0)
1185             has 'server_port'; # 8080
1186             has 'debugmode'; # 0
1187             has 'configobj'; # Config::General object
1188             has 'acruxconfig'; # Acrux::Config object
1189             has 'cache' => sub { WWW::Suffit::Cache->new };
1190              
1191             # Files and directories
1192             has 'documentroot'; # /var/www/
1193             has 'homedir'; # /usr/share/
1194             has 'datadir'; # /var/lib/
1195             has 'tempdir'; # /tmp/
1196             has 'logfile'; # /var/log/.log
1197             has 'pidfile'; # /run/.pid
1198              
1199             # Logging
1200             has 'loglevel' => 'warn'; # warn
1201             has 'max_history_size' => MAX_HISTORY_SIZE;
1202              
1203             # Security
1204             has 'mysecret' => DEFAULT_SECRET; # Secret
1205             has 'private_key' => ''; # Private RSA key
1206             has 'public_key' => ''; # Public RSA key
1207             has 'rsa_keysize' => sub { shift->conf->latest("/rsa_keysize") };
1208             has 'trustedproxies' => sub { [grep {length} @{(shift->conf->list("/trustedproxy"))}] };
1209              
1210             # Prefork
1211             has 'clients' => sub { shift->conf->latest("/clients") || SERVER_MAX_CLIENTS }; # 10000
1212             has 'requests' => sub { shift->conf->latest("/requests") || SERVER_MAX_REQUESTS}; # 100
1213             has 'accepts' => sub { shift->conf->latest("/accepts") }; # SERVER_ACCEPTS is 0 -- by default not specified
1214             has 'spare' => sub { shift->conf->latest("/spare") || SERVER_SPARE }; # 2
1215             has 'workers' => sub { shift->conf->latest("/workers") || SERVER_WORKERS }; # 4
1216             has 'reload_sig' => sub { shift->conf->latest("/reload_sig") // 'USR2' };
1217             has 'no_daemonize';
1218             has 'uid';
1219             has 'gid';
1220              
1221             # Startup options as attributes
1222             has [qw/all_features init_rsa_keys init_authdb init_api_routes init_user_routes init_admin_routes/];
1223             has 'config_opts' => sub { {} };
1224             has 'syslog_opts' => sub { {} };
1225             has 'authdb_opts' => sub { {} };
1226             has 'api_routes_opts' => sub { {} };
1227             has 'user_routes_opts' => sub { {} };
1228             has 'admin_routes_opts' => sub { {} };
1229              
1230             sub raise {
1231 0     0 1   my $self = shift;
1232 0           say STDERR color "bright_red" => @_;
1233 0 0         $self->log->error((scalar(@_) == 1) ? shift : sprintf(shift, @_));
1234 0           exit 1;
1235             }
1236             sub startup {
1237 0     0 1   my $self = shift;
1238 0 0         my $opts = @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {};
  0 0          
1239 0 0         $self->project_name(ref($self)) unless defined $self->project_name;
1240 0 0         $self->project_version($self->VERSION) unless defined $self->project_version;
1241 0 0         $self->raise("Incorrect `project_name`") unless $self->project_name;
1242 0 0         $self->raise("Incorrect `project_version`") unless $self->project_version;
1243 0           unshift @{$self->plugins->namespaces}, 'WWW::Suffit::Plugin'; # Add another namespace to load plugins from
  0            
1244 0           push @{$self->routes->namespaces}, 'WWW::Suffit::Server'; # Add Server routes namespace
  0            
1245 0   0       my $all_features = is_true_flag($opts->{all_features} // $self->all_features); # on/off
1246              
1247             # Get all ConfigGeneral configuration attributes
1248 0   0       my $config_opts = as_hash_ref($opts->{config_opts} || $self->config_opts) || {};
1249 0 0         if (my $configobj = $self->configobj) {
1250 0 0         $self->raise("The `configobj` must be Config::General object")
1251             unless ref($configobj) eq 'Config::General';
1252 0           $self->config($configobj->getall); # Set config hash
1253 0 0         $config_opts->{noload} = 1 unless exists $config_opts->{noload};
1254             }
1255              
1256             # Get all Acrux configuration attributes
1257 0 0         if (my $acruxconfig = $self->acruxconfig) {
1258 0 0         $self->raise("The `acruxconfig` must be Acrux::Config object")
1259             unless ref($acruxconfig) eq 'Acrux::Config';
1260 0           $self->config($acruxconfig->config); # Set config hash
1261 0 0         $config_opts->{noload} = 1 unless exists $config_opts->{noload};
1262             }
1263              
1264             # Init ConfigGeneral plugin
1265 0 0         unless (exists($config_opts->{noload})) { $config_opts->{noload} = 0 }
  0            
1266 0 0         unless (exists($config_opts->{defaults})) { $config_opts->{defaults} = as_hash_ref($self->config) }
  0            
1267 0           $self->plugin('ConfigGeneral' => $config_opts);
1268              
1269             # Syslog
1270 0   0       my $syslog_opts = as_hash_ref($opts->{syslog_opts} || $self->syslog_opts) || {};
1271 0 0 0       my $syslogen = ($self->conf->latest('/log') && $self->conf->latest('/log') =~ /syslog/i) ? 1 : 0;
1272 0 0         unless (exists($syslog_opts->{enable})) { $syslog_opts->{enable} = $syslogen };
  0            
1273 0           $self->plugin('Syslog' => $syslog_opts);
1274              
1275             # PRE REQUIRED Plugins
1276 0           $self->plugin('CommonHelpers');
1277              
1278             # Logging
1279 0   0       $self->log->level($self->loglevel || ($self->debugmode ? "debug" : "warn"))
      0        
1280             ->max_history_size($self->max_history_size || MAX_HISTORY_SIZE);
1281 0 0         $self->log->path($self->logfile) if $self->logfile;
1282              
1283             # Helpers
1284 0           $self->helper('token' => \&_getToken);
1285 0           $self->helper('jwt' => \&_getJWT);
1286 0           $self->helper('clientid' => \&_genClientId);
1287 0           $self->helper('gen_cachekey'=> \&_genCacheKey);
1288 0           $self->helper('gen_rsakeys' => \&_genRSAKeys);
1289              
1290             # DataDir (variable data, caches, temp files and etc.) -- /var/lib/
1291 0 0         $self->datadir(path(SHAREDSTATEDIR, $self->moniker)->to_string()) unless defined $self->datadir;
1292 0 0         $self->raise("Startup error! Data directory %s not exists", $self->datadir) unless -e $self->datadir;
1293              
1294             # HomeDir (shared static files, default templates and etc.) -- /usr/share/
1295 0 0         $self->homedir(path(DATADIR, $self->moniker)->to_string()) unless defined $self->homedir;
1296 0           $self->home(Mojo::Home->new($self->homedir)); # Switch to installable home directory
1297              
1298             # DocumentRoot (user's static data) -- /var/www/
1299 0           my $documentroot = path(WEBDIR, $self->moniker)->to_string();
1300 0 0         $self->documentroot(-e $documentroot ? $documentroot : $self->homedir) unless defined $self->documentroot;
    0          
1301              
1302             # Reset static dirs
1303 0           $self->static->paths()->[0] = $self->documentroot; #unshift @{$static->paths}, '/home/sri/themes/blue/public';
1304 0 0         $self->static->paths()->[1] = $self->homedir if $self->documentroot ne $self->homedir;
1305              
1306             # Add renderer path (templates)
1307 0           push @{$self->renderer->paths}, $self->documentroot, $self->homedir;
  0            
1308              
1309             # Remove system favicon file
1310 0           delete $self->static->extra->{'favicon.ico'};
1311              
1312             # Set secret
1313 0 0         $self->mysecret($self->conf->latest("/secret")) if $self->conf->latest("/secret");
1314 0           $self->secrets([$self->mysecret]);
1315              
1316             # Init RSA keys (optional)
1317 0 0 0       if ($all_features || is_true_flag($opts->{init_rsa_keys} // $self->init_rsa_keys)) {
      0        
1318 0   0       my $private_key_file = $self->conf->latest("/privatekeyfile") || path($self->datadir, PRIVATEKEYFILE)->to_string;
1319 0   0       my $public_key_file = $self->conf->latest("/publickeyfile") || path($self->datadir, PUBLICKEYFILE)->to_string;
1320 0 0 0       if ((!-r $private_key_file) and (!-r $public_key_file)) {
    0          
    0          
1321 0           my $rsa = WWW::Suffit::RSA->new();
1322 0 0         $rsa->key_size($self->rsa_keysize) if $self->rsa_keysize;
1323 0           $rsa->keygen;
1324 0           path($private_key_file)->spew($rsa->private_key)->chmod(0600);
1325 0           $self->private_key($rsa->private_key);
1326 0           path($public_key_file)->spew($rsa->public_key)->chmod(0644);
1327 0           $self->public_key($rsa->public_key);
1328             } elsif (!-r $private_key_file) {
1329 0           $self->raise("Can't read RSA private key file: \"%s\"", $private_key_file);
1330             } elsif (!-r $public_key_file) {
1331 0           $self->raise("Can't read RSA public key file: \"%s\"", $public_key_file);
1332             } else {
1333 0           $self->private_key(path($private_key_file)->slurp);
1334 0           $self->public_key(path($public_key_file)->slurp)
1335             }
1336             }
1337              
1338             # Init AuthDB plugin (optional)
1339 0 0 0       if ($all_features || is_true_flag($opts->{init_authdb} // $self->init_authdb)) {
      0        
1340             #_load_module("WWW::Suffit::AuthDB");
1341 0   0       my $authdb_opts = as_hash_ref($opts->{authdb_opts} || $self->authdb_opts) || {};
1342 0           my $authdb_file = path($self->datadir, AUTHDBFILE)->to_string;
1343             my $authdb_uri = $authdb_opts->{uri} || $authdb_opts->{url}
1344 0   0       || $self->conf->latest("/authdburl") || $self->conf->latest("/authdburi")
1345             || qq{sqlite://$authdb_file?sqlite_unicode=1};
1346 0   0       my $cacheexpiration = $self->conf->latest("/authdbcacheexpire") || $self->conf->latest("/authdbcacheexpiration");
1347             $self->plugin('AuthDB' => {
1348             ds => $authdb_uri,
1349             cached => $authdb_opts->{cachedconnection} // $self->conf->latest("/authdbcachedconnection") // 'on',
1350             expiration => $authdb_opts->{cacheexpire} || $authdb_opts->{cacheexpiration} ||
1351             (defined($cacheexpiration) ? parse_time_offset($cacheexpiration) : undef),
1352             max_keys => $authdb_opts->{cachemaxkeys} || $self->conf->latest("/authdbcachemaxkeys"),
1353 0   0       sourcefile => $authdb_opts->{sourcefile} || $self->conf->latest("/authdbsourcefile"),
      0        
      0        
      0        
      0        
1354             });
1355 0           $self->authdb->with_roles(qw/+CRUD +AAA/);
1356             #$self->log->info(sprintf("AuthDB URI: \"%s\"", $authdb_uri));
1357             }
1358              
1359             # Set API routes plugin (optional)
1360             $self->plugin('API' => as_hash_ref($opts->{api_routes_opts} || $self->api_routes_opts) || {})
1361 0 0 0       if $all_features || is_true_flag($opts->{init_api_routes} // $self->init_api_routes);
      0        
      0        
1362             $self->plugin('API::User' => as_hash_ref($opts->{user_routes_opts} || $self->user_routes_opts) || {})
1363 0 0 0       if $all_features || is_true_flag($opts->{init_user_routes} // $self->init_user_routes);
      0        
      0        
1364             $self->plugin('API::Admin' => as_hash_ref($opts->{admin_routes_opts} || $self->admin_routes_opts) || {})
1365 0 0 0       if $all_features || is_true_flag($opts->{init_admin_routes} // $self->init_admin_routes);
      0        
      0        
1366              
1367             # Hooks
1368             $self->hook(before_dispatch => sub {
1369 0     0     my $c = shift;
1370 0           $c->res->headers->server(sprintf("%s/%s", $self->project_name, $self->project_version)); # Set Server header
1371 0           });
1372              
1373             # Init hook
1374 0           $self->init;
1375              
1376 0           return $self;
1377             }
1378       0 1   sub init { } # Overload it
1379             sub reload { # Reload hook
1380 0     0 1   my $self = shift;
1381 0           $self->log->warn("Request for reload $$");
1382 0           return 1; # 1 - ok; 0 - error :(
1383             }
1384             sub listeners {
1385 0     0 1   my $self = shift;
1386              
1387             # Resilver cert file
1388             my $_resolve_cert_file = sub {
1389 0     0     my $f = shift;
1390 0 0         return $f if File::Spec->file_name_is_absolute($f);
1391 0           return File::Spec->catfile(SYSCONFDIR, $self->moniker, $f);
1392 0           };
1393              
1394             # Master URL
1395 0   0       my $url = Mojo::URL->new($self->server_url || DEFAULT_SERVER_URL);
1396 0 0 0       my $host = $self->server_url ? $url->host : ($self->server_addr || DEFAULT_SERVER_ADDR);
1397 0 0 0       my $port = $self->server_url ? $url->port : ($self->server_port || DEFAULT_SERVER_PORT);
1398 0   0       $url->host($self->conf->latest("/listenaddr") || $host);
1399 0   0       $url->port($self->conf->latest("/listenport") || $port);
1400              
1401             # Added TLS parameters
1402 0 0         if (is_true_flag($self->conf->latest("/tls"))) {
1403 0           $url->scheme('https');
1404 0           foreach my $k (qw/ciphers version verify fd reuse single_accept/) {
1405 0   0       my $v = $self->conf->latest("/tls_$k") // '';
1406 0 0         next unless length $v;
1407 0 0 0       $v ||= '0x00' if $k eq 'verify';
1408 0           $url->query->merge($k, $v);
1409             }
1410 0           foreach my $k (qw/ca cert key/) {
1411 0   0       my $v = $self->conf->latest("/tls_$k") // '';
1412 0 0         next unless length $v;
1413 0           my $file = $_resolve_cert_file->($v);
1414 0 0 0       $self->raise("Can't load file \"%s\"", $file) unless -e $file and -r $file;
1415 0           $url->query->merge($k, $file);
1416             }
1417             }
1418              
1419             # Make master listener
1420 0           my $listener = $url->to_unsafe_string;
1421              
1422             # Make additional (slave) listeners
1423 0           my @listeners = ();
1424 0           push @listeners, $listener; # Ass master listener
1425 0   0       my $slaves = as_array_ref($self->conf->list("/listenurl")) // [];
1426 0 0         push @listeners, @$slaves if isnt_void($slaves);
1427              
1428             # Return listeners
1429 0           return [@listeners];
1430             }
1431             sub preforked_run {
1432 0     0 1   my $self = shift; # app
1433 0   0       my $dash_k = shift || '';
1434 0 0         my $opts = @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {};
  0 0          
1435              
1436             # Dash k
1437             $self->raise("Incorrect LSB command! Please use start, status, stop, restart or reload")
1438 0 0         unless (grep {$_ eq $dash_k} qw/start status stop restart reload/);
  0            
1439              
1440             # Mojolicious Prefork server
1441 0           my $prefork = Mojo::Server::Prefork->new( app => $self );
1442 0 0         $prefork->pid_file($self->pidfile) if length $self->pidfile;
1443              
1444             # Hypnotoad Pre-fork settings
1445 0 0         $prefork->max_clients($self->clients) if defined $self->clients;
1446 0 0         $prefork->max_requests($self->requests) if defined $self->requests;
1447 0 0         $prefork->accepts($self->accepts) if defined $self->accepts;
1448 0 0         $prefork->spare($self->spare) if defined $self->spare;
1449 0 0         $prefork->workers($self->workers) if defined $self->workers;
1450              
1451             # Listener
1452 0           $prefork->listen($self->listeners);
1453              
1454             # Working with Dash k
1455 0           my $upgrade = 0;
1456 0           my $reload = 0;
1457 0           my $upgrade_timeout = SERVER_UPGRADE_TIMEOUT; # 30
1458 0 0         if ($dash_k eq 'start') {
    0          
    0          
    0          
1459 0 0         if (my $pid = $prefork->check_pid()) {
1460 0           print "Already running $pid\n";
1461 0           exit 0;
1462             }
1463             } elsif ($dash_k eq 'stop') {
1464 0 0         if (my $pid = $prefork->check_pid()) {
1465 0           kill 'QUIT', $pid;
1466 0           print "Stopping $pid\n";
1467             } else {
1468 0           print "Not running\n";
1469             }
1470 0           exit 0;
1471             } elsif ($dash_k eq 'restart') {
1472 0 0         if (my $pid = $prefork->check_pid()) {
1473 0   0       $upgrade ||= steady_time;
1474 0           kill 'QUIT', $pid;
1475 0           my $up = $upgrade_timeout;
1476 0           while (kill 0, $pid) {
1477 0           $up--;
1478 0           sleep 1;
1479             }
1480 0 0         die("Can't stop $pid") if $up <= 0;
1481 0           print "Stopping $pid\n";
1482 0           $upgrade = 0;
1483             }
1484             } elsif ($dash_k eq 'reload') {
1485 0           my $pid = $prefork->check_pid();
1486 0 0         if ($pid) {
1487 0 0         if (my $s = $self->reload_sig) {
1488             # Start hot deployment
1489 0           kill $s, $pid; # 'USR2'
1490 0           print "Reloading $pid\n";
1491 0           exit 0;
1492             }
1493             }
1494 0           print "Not running\n";
1495             } else { # status
1496 0 0         if (my $pid = $prefork->check_pid()) {
1497 0           print "Running $pid\n";
1498             } else {
1499 0           print "Not running\n";
1500             }
1501 0           exit 0;
1502             }
1503              
1504             # Listen USR2 (reload)
1505 0 0         if (my $s = $self->reload_sig) {
1506 0   0 0     $SIG{$s} = sub { $upgrade ||= steady_time };
  0            
1507             }
1508              
1509             # Set system hooks
1510             $prefork->on(wait => sub { # Manage (every 1 sec)
1511 0     0     my $this = shift; # Prefork object
1512              
1513             # Upgrade
1514 0 0         if ($upgrade) {
1515             #$this->app->log->debug(">>> " . $this->healthy() || '?');
1516 0 0         unless ($reload) {
1517 0           $reload = 1; # Off next reloading
1518 0 0         if ($this->app->reload()) {
1519 0           $reload = 0;
1520 0           $upgrade = 0;
1521 0           return 1;
1522             }
1523             }
1524              
1525             # Timeout
1526 0 0         if (($upgrade + $upgrade_timeout) <= steady_time()) {
1527 0           kill 'KILL', $$;
1528 0           $upgrade = 0;
1529             }
1530             }
1531 0           });
1532             $prefork->on(finish => sub { # Finish
1533 0     0     my $this = shift; # Prefork object
1534 0           my $graceful = shift;
1535 0 0         $this->app->log->debug($graceful ? 'Graceful server shutdown' : 'Server shutdown');
1536 0           });
1537              
1538             # Set GID and UID
1539 0           if (IS_ROOT) {
1540 0 0         if (my $gid = $self->gid) {
1541 0 0         POSIX::setgid($gid) or $self->raise("setgid %s failed - %s", $gid, $!);
1542 0           $) = "$gid $gid"; # this calls setgroups
1543 0 0 0       $self->raise("detected strange gid") if !($( eq "$gid $gid" && $) eq "$gid $gid"); # just to be sure
1544             }
1545 0 0         if (my $uid = $self->uid) {
1546 0 0         POSIX::setuid($uid) or $self->raise("setuid %s failed - %s", $uid, $!);
1547 0 0 0       $self->raise("detected strange uid") if !($< == $uid && $> == $uid); # just to be sure
1548             }
1549             }
1550              
1551             # PreRun callback
1552 0 0         if (my $prerun = $opts->{prerun}) {
1553 0 0         $prerun->($self, $prefork) if ref($prerun) eq 'CODE';
1554             }
1555              
1556             # Daemonize
1557 0 0         $prefork->daemonize() unless $self->no_daemonize;
1558              
1559             # Running
1560 0           print "Running\n";
1561 0           $prefork->run();
1562             }
1563              
1564             sub _load_module {
1565 0     0     my $module = shift;
1566 0 0         if (my $e = load_class($module)) {
1567 0 0         croak ref($e) ? "Exception: $e" : "The module $module not found!";
1568             }
1569 0           return 1;
1570             }
1571             sub _getToken {
1572 0     0     my $self = shift;
1573              
1574             # Get authorization string from request header
1575 0   0       my $token = $self->req->headers->header(TOKEN_HEADER_NAME) // '';
1576 0 0         if (length($token)) {
1577 0 0         return '' unless $token =~ JWT_REGEXP;
1578 0           return $token;
1579             }
1580              
1581             # Get authorization string from request authorization header
1582             my $auth_string = $self->req->headers->authorization
1583             || $self->req->env->{'X_HTTP_AUTHORIZATION'}
1584 0   0       || $self->req->env->{'HTTP_AUTHORIZATION'}
1585             || '';
1586 0 0         if ($auth_string =~ /(Bearer|Token)\s+(.*)/) {
1587 0           $token = $2;
1588 0 0 0       return '' unless length($token) && $token =~ JWT_REGEXP;
1589 0           return $token;
1590             }
1591              
1592             # In debug mode see "Token" config directive
1593 0 0 0       if ($self->app->debugmode and $token = $self->conf->latest("/token")) {
1594 0 0         return '' unless $token =~ JWT_REGEXP;
1595             }
1596              
1597 0   0       return $token // '';
1598             }
1599             sub _getJWT {
1600 0     0     my $self = shift;
1601 0           return WWW::Suffit::JWT->new(
1602             secret => $self->app->mysecret,
1603             private_key => $self->app->private_key,
1604             public_key => $self->app->public_key,
1605             );
1606             }
1607             sub _genCacheKey {
1608 0     0     my $self = shift;
1609 0   0       my $len = shift || 12;
1610 0           return randchars($len);
1611             }
1612             sub _genRSAKeys {
1613 0     0     my $self = shift;
1614 0   0       my $key_size = shift || $self->app->rsa_keysize;
1615 0           my $rsa = WWW::Suffit::RSA->new();
1616 0 0         $rsa->key_size($key_size) if $key_size;
1617 0           $rsa->keygen;
1618 0   0       my ($private_key, $public_key) = ($rsa->private_key // '', $rsa->public_key // '');
      0        
1619             return (
1620 0 0 0       private_key => $private_key,
1621             public_key => $public_key,
1622             key_size => $rsa->key_size,
1623             error => $rsa->error
1624             ? sprintf("Error occurred while generation %s bit RSA keys: %s", $rsa->key_size // '?', $rsa->error)
1625             : '',
1626             );
1627             }
1628             sub _genClientId {
1629 0     0     my $self = shift;
1630 0   0       my $user_agent = $self->req->headers->header('User-Agent') // 'unknown';
1631 0   0       my $remote_address = $self->remote_ip($self->app->trustedproxies)
1632             || $self->tx->remote_address || '::1';
1633             # md5(User-Agent . Remote-Address)
1634 0           return md5_sum(sprintf("%s%s", $user_agent, $remote_address));
1635             }
1636              
1637             1;
1638              
1639             __END__