File Coverage

bin/pagi-server
Criterion Covered Total %
statement 35 54 64.8
branch 12 32 37.5
condition 3 6 50.0
subroutine 5 5 100.0
pod n/a
total 55 97 56.7


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2              
3             # Handle compile-time options before loading any modules
4             # Future::XS must be loaded before Future, so check early
5             BEGIN {
6 9     9   37453 my @to_splice;
7 9         38 for my $i (0 .. $#ARGV) {
8 48 50       98 if ($ARGV[$i] eq '--future-xs') {
9 0         0 $ENV{PAGI_FUTURE_XS} = 1;
10 0         0 push @to_splice, $i;
11             }
12             }
13             # Remove processed flags in reverse order to preserve indices
14 9         12 for my $i (reverse @to_splice) {
15 0         0 splice @ARGV, $i, 1;
16             }
17              
18             # Capture original args for hot restart re-exec (USR2)
19 9 50       421 $ENV{PAGI_ARGV} = join("\0", @ARGV) unless exists $ENV{PAGI_ARGV};
20             }
21              
22 9     9   67 use strict;
  9         11  
  9         169  
23 9     9   25 use warnings;
  9         13  
  9         363  
24              
25 9     9   5552 use Getopt::Long qw(GetOptionsFromArray :config pass_through no_auto_abbrev);
  9         107718  
  9         51  
26 9     9   6198 use PAGI::Server::Runner;
  9         28  
  9         14763  
27              
28             # Parse PAGI::Server-specific options before passing to Runner
29             # This keeps Runner server-agnostic
30 9         1044653 my %server_opts;
31              
32             GetOptionsFromArray(
33             \@ARGV,
34             # Network (Unix socket / multi-listener)
35             'socket=s' => \$server_opts{socket},
36             'socket-mode=s' => \$server_opts{_socket_mode},
37             'listen=s@' => \$server_opts{_listen},
38              
39             # Workers/scaling
40             'w|workers=i' => \$server_opts{workers},
41             'reuseport' => \$server_opts{reuseport},
42             'max-requests=i' => \$server_opts{max_requests},
43             'max-connections=i' => \$server_opts{max_connections},
44              
45             # TLS
46             'ssl-cert=s' => \$server_opts{_ssl_cert},
47             'ssl-key=s' => \$server_opts{_ssl_key},
48              
49             # Timeouts
50             'timeout=i' => \$server_opts{timeout},
51             'shutdown-timeout=i' => \$server_opts{shutdown_timeout},
52             'request-timeout=i' => \$server_opts{request_timeout},
53             'ws-idle-timeout=i' => \$server_opts{ws_idle_timeout},
54             'sse-idle-timeout=i' => \$server_opts{sse_idle_timeout},
55             'heartbeat-timeout=i' => \$server_opts{heartbeat_timeout},
56              
57             # Lifespan
58             'lifespan=s' => \$server_opts{lifespan_mode},
59             'lifespan-startup-timeout=i' => \$server_opts{lifespan_startup_timeout},
60              
61             # Limits
62             'max-body-size=i' => \$server_opts{max_body_size},
63             'max-header-size=i' => \$server_opts{max_header_size},
64             'max-header-count=i' => \$server_opts{max_header_count},
65             'max-receive-queue=i' => \$server_opts{max_receive_queue},
66             'max-ws-frame-size=i' => \$server_opts{max_ws_frame_size},
67             'b|listener-backlog=i' => \$server_opts{listener_backlog},
68              
69             # Write backpressure watermarks
70             'write-high-watermark=i' => \$server_opts{write_high_watermark},
71             'write-low-watermark=i' => \$server_opts{write_low_watermark},
72              
73             # Misc
74             'http2' => \$server_opts{http2},
75             'log-level=s' => \$server_opts{log_level},
76             'sync-file-threshold=i' => \$server_opts{sync_file_threshold},
77             'access-log-format=s' => \$server_opts{access_log_format},
78 9         325 );
79              
80             # Build ssl hash if certs provided
81 9 100 66     16953 if ($server_opts{_ssl_cert} || $server_opts{_ssl_key}) {
82             die "--ssl-cert and --ssl-key must be specified together\n"
83 1 50 33     16 unless $server_opts{_ssl_cert} && $server_opts{_ssl_key};
84              
85             die "SSL cert not found: $server_opts{_ssl_cert}\n"
86 1 50       48 unless -f $server_opts{_ssl_cert};
87             die "SSL key not found: $server_opts{_ssl_key}\n"
88 1 50       9 unless -f $server_opts{_ssl_key};
89              
90             $server_opts{ssl} = {
91             cert_file => delete $server_opts{_ssl_cert},
92             key_file => delete $server_opts{_ssl_key},
93 1         5 };
94             }
95 9         83 delete $server_opts{_ssl_cert};
96 9         17 delete $server_opts{_ssl_key};
97              
98             # Handle --socket-mode (octal string to numeric)
99 9 50       25 if (defined $server_opts{_socket_mode}) {
100 0         0 my $mode_str = $server_opts{_socket_mode};
101 0 0       0 die "--socket-mode must be an octal value like 0660, got: $mode_str\n"
102             unless $mode_str =~ /^0[0-7]{3}$/;
103 0         0 $server_opts{socket_mode} = oct($mode_str);
104             }
105 9         25 delete $server_opts{_socket_mode};
106              
107             # Handle --listen (build listen array from repeated --listen flags)
108             # Detection: contains ':' -> TCP host:port, otherwise -> Unix socket path
109 9 50       36 if (my $listen_specs = delete $server_opts{_listen}) {
110 0         0 my @listeners;
111 0         0 for my $spec (@$listen_specs) {
112 0 0       0 if ($spec =~ m{^[./]}) {
    0          
    0          
113             # Starts with / or . — always a Unix socket path
114             # (even if it contains colons, e.g. /var/run/app:8080)
115 0         0 my %entry = (socket => $spec);
116             $entry{socket_mode} = $server_opts{socket_mode}
117 0 0       0 if defined $server_opts{socket_mode};
118 0         0 push @listeners, \%entry;
119             } elsif ($spec =~ /^\[([^\]]+)\]:(\d+)$/) {
120             # IPv6: [::1]:5000
121 0         0 push @listeners, { host => $1, port => int($2) };
122             } elsif ($spec =~ /^(.+):(\d+)$/) {
123             # IPv4/hostname: 127.0.0.1:5000
124 0         0 push @listeners, { host => $1, port => int($2) };
125             } else {
126             # Bare name without / or . prefix — treat as Unix socket
127 0         0 my %entry = (socket => $spec);
128             $entry{socket_mode} = $server_opts{socket_mode}
129 0 0       0 if defined $server_opts{socket_mode};
130 0         0 push @listeners, \%entry;
131             }
132             }
133 0         0 $server_opts{listen} = \@listeners;
134 0         0 delete $server_opts{socket_mode}; # Applied per-listener above
135             }
136              
137             # Handle workers (0 for single-process, >1 for multi-worker)
138 9 100       29 if (defined $server_opts{workers}) {
139 3 50       9 $server_opts{workers} = $server_opts{workers} > 1 ? $server_opts{workers} : 0;
140             }
141              
142             # Remove undefined options
143 9         49 %server_opts = map { $_ => $server_opts{$_} } grep { defined $server_opts{$_} } keys %server_opts;
  8         32  
  226         242  
144              
145             # Pass server options to Runner
146 9         111 PAGI::Server::Runner->run(@ARGV, server_options => \%server_opts);
147              
148             __END__