The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use Mojo::Base qw{ -strict };

use Mojolicious;
use Mojo::IOLoop;
use Mojo::URL;
use Mojo::IOLoop::Server;

local $SIG{KILL} = $SIG{INT} = $SIG{TERM} = sub {
    stop_server();
    exit;
};

my $_servers = {};

sub get_server { $_servers->{$_[0]} || +{} }

sub generate_port {
    ( $Mojolicious::VERSION >= 5.0 )
      ? Mojo::IOLoop::Server->generate_port
      : Mojo::IOLoop->generate_port;
}

sub start_server {
    my $app  = shift;
    my $args = { @_ };
    my $port = delete( $args->{port} ) || generate_port();

    my $pid = open my $fh, '|-'; # fork
    $fh->autoflush;

    # parent
    if ($pid) {
        my $url = Mojo::URL->new("http://127.0.0.1:$port");
        $_servers->{$pid} = { url => $url, fh => $fh };
        # check started
        sleep 1 while !IO::Socket::INET->new(
            Proto    => 'tcp',
            PeerAddr => '127.0.0.1',
            PeerPort => $port,
        );
        return (wantarray) ? ( $url, $pid ) : $url;
    }
    # child
    else {
        local $SIG{KILL} = $SIG{INT} = $SIG{TERM} = 'DEFAULT';
        setpgrp or die "$!";
        my @args = ( @{ $args->{options} || [] }, '-l', "http://127.0.0.1:$port" );
        # start server daemon
        open my $server, '|-', $^X, $app, @args;
        $server->autoflush;
        while (<>) { chomp; print $server $_ }
    }
}

sub stop_server {
    my @list_pid = ( @_ ) ? @_ : keys %$_servers;
    for my $pid ( @list_pid ) {
        unless ( kill 0, $pid ) {
            warn "already stopped: $pid";
            next;
        }
        kill -15, getpgrp $pid; # send SIGTERM to process-group of server daemon
        waitpid $pid, 0;
        delete $_servers->{$pid};
    }
}

1;