# Copyright (c) 2025 Yuki Kimoto
# MIT License
class Mojolicious::Command::Prefork extends Mojolicious::Command {
version_from Mojolicious;
use Mojo::Server::Prefork;
use Mojo::Util;
use Fn;
has description : rw string
get {
unless (exists $self->{description}) {
$self->{description} = "Start application with pre-forking HTTP and WebSocket server";
}
return $self->{description};
}
;
has usage : rw string
get {
unless (exists $self->{usage}) {
$self->{usage} = <<'EOS';
Usage: APPLICATION prefork [OPTIONS]
./myapp.spvm prefork
./myapp.spvm prefork -m production -p -l http://*:8080
./myapp.spvm prefork -l http://127.0.0.1:8080 -l https://[::]:8081
./myapp.spvm prefork -l 'https://*:443?cert=./server.crt&key=./server.key'
./myapp.spvm prefork -l http+unix://%2Ftmp%2Fmyapp.sock -w 12
./myapp.spvm prefork -l http://127.0.0.1:8080 -p 127.0.0.0/8 -p fc00::/7
Options:
-a, --accepts <number> Number of connections for workers to
accept, defaults to 10000
-b, --backlog <size> Listen backlog size, defaults to
SOMAXCONN
-c, --clients <number> Maximum number of concurrent
connections, defaults to 1000
-G, --graceful-timeout <seconds> Graceful timeout, defaults to 120.
-I, --heartbeat-interval <seconds> Heartbeat interval, defaults to 5
-H, --heartbeat-timeout <seconds> Heartbeat timeout, defaults to 50
-h, --help Show this summary of available options
--home <path> Path to home directory of your
application, defaults to the value of
MOJO_HOME or auto-detection
-i, --inactivity-timeout <seconds> Inactivity timeout, defaults to the
value of MOJO_INACTIVITY_TIMEOUT or 30
-k, --keep-alive-timeout <seconds> Keep-alive timeout, defaults to the
value of MOJO_KEEP_ALIVE_TIMEOUT or 5
-l, --listen <location> One or more locations you want to
listen on, defaults to the value of
MOJO_LISTEN or "http://*:3000"
-m, --mode <name> Operating mode for your application,
defaults to the value of
MOJO_MODE/PLACK_ENV or "development"
-P, --pid-file <path> Path to process id file, defaults to
"prefork.pid" in a temporary directory
-p, --proxy [<network>] Activate reverse proxy support,
defaults to the value of
MOJO_REVERSE_PROXY, optionally takes
one or more trusted proxy addresses or
networks
-r, --requests <number> Maximum number of requests per
keep-alive connection, defaults to 100
-s, --spare <number> Temporarily spawn up to this number of
additional workers, defaults to 2
-w, --workers <number> Number of workers, defaults to 4
EOS
}
return $self->{usage};
}
;
method build_server : Mojo::Server::Prefork ($args : string[] = undef) {
my $values_h = Hash->new({
listen => new string[0],
proxy => new string[0],
});
my $specs = [
"b|backlog=i",
"c|clients=i",
"i|inactivity-timeout=i",
"k|keep-alive-timeout=i",
"l|listen=s",
"p|proxy=s",
"r|requests=i",
"a|accepts=i",
"G|graceful-timeout=i",
"I|heartbeat-interval=i",
"H|heartbeat-timeout=i",
"P|pid-file=s",
"s|spare=i",
"w|workers=i",
];
eval { Mojo::Util->getopt(my $_ = [$args], $values_h, $specs); }
if ($@) {
die $self->usage;
}
my $server = Mojo::Server::Prefork->new;
$server->set_app($self->app);
if ($values_h->exists("backlog")) {
my $backlog = $values_h->{"backlog"}->(int);
$server->set_backlog($backlog);
}
if ($values_h->exists("clients")) {
my $max_clients = $values_h->{"clients"}->(int);
$server->set_max_clients($max_clients);
}
if ($values_h->exists("inactivity-timeout")) {
my $inactivity_timeout = $values_h->{"inactivity-timeout"}->(int);
$server->set_inactivity_timeout($inactivity_timeout);
}
if ($values_h->exists("keep-alive-timeout")) {
my $keep_alive_timeout = $values_h->{"keep-alive-timeout"}->(int);
$server->set_keep_alive_timeout($keep_alive_timeout);
}
if ($values_h->exists("requests")) {
my $max_requests = $values_h->{"requests"}->(int);
$server->set_max_requests($max_requests);
}
my $listen = (string[])$values_h->get("listen");
if (@$listen) {
$server->set_listen($listen);
}
my $proxy = (string[])$values_h->get("proxy");
if (@$proxy) {
$server->set_reverse_proxy(1);
}
my $trusted = (string[])Fn->grep(method : int ($_ : string) { return length $_; }, $proxy);
if (@$trusted) {
$server->set_trusted_proxies($trusted);
}
if ($values_h->exists("accepts")) {
my $accepts = $values_h->{"accepts"}->(int);
$server->set_accepts($accepts);
}
if ($values_h->exists("graceful-timeout")) {
my $graceful_timeout = $values_h->{"graceful-timeout"}->(int);
$server->set_graceful_timeout($graceful_timeout);
}
if ($values_h->exists("heartbeat-interval")) {
my $heartbeat_interval = $values_h->{"heartbeat-interval"}->(int);
$server->set_heartbeat_interval($heartbeat_interval);
}
if ($values_h->exists("heartbeat-timeout")) {
my $heartbeat_timeout = $values_h->{"heartbeat-timeout"}->(int);
$server->set_heartbeat_timeout($heartbeat_timeout);
}
if ($values_h->exists("pid-file")) {
my $pid_file = $values_h->{"pid-file"}->(string);
$server->set_pid_file($pid_file);
}
if ($values_h->exists("spare")) {
my $spare = $values_h->{"spare"}->(int);
$server->set_spare($spare);
}
if ($values_h->exists("workers")) {
my $workers = $values_h->{"workers"}->(int);
$server->set_workers($workers);
}
return $server;
}
method run : void ($args : string[]) {
$self->build_server($args)->run;
}
}