The Perl Advent Calendar needs more articles for 2022. Submit your idea today!
package Mojo::Server::Daemon;
use Mojo::Base 'Mojo::Server';

use Carp qw(croak);
use Mojo::IOLoop;
use Mojo::Transaction::WebSocket;
use Mojo::URL;
use Mojo::Util qw(term_escape);
use Mojo::WebSocket qw(server_handshake);
use Scalar::Util qw(weaken);

use constant DEBUG => $ENV{MOJO_SERVER_DEBUG} || 0;

has acceptors => sub { [] };
has [qw(backlog max_clients silent)];
has inactivity_timeout => sub { $ENV{MOJO_INACTIVITY_TIMEOUT} // 30 };
has ioloop             => sub { Mojo::IOLoop->singleton };
has keep_alive_timeout => sub { $ENV{MOJO_KEEP_ALIVE_TIMEOUT} // 5 };
has listen             => sub { [split /,/, $ENV{MOJO_LISTEN} || 'http://*:3000'] };
has max_requests       => 100;

  my $self = shift;
  return if ${^GLOBAL_PHASE} eq 'DESTRUCT';
  my $loop = $self->ioloop;
  $loop->remove($_) for keys %{$self->{connections} // {}}, @{$self->acceptors};

sub ports { [map { $_[0]->ioloop->acceptor($_)->port } @{$_[0]->acceptors}] }

sub run {
  my $self = shift;

  # Make sure the event loop can be stopped in regular intervals
  my $loop = $self->ioloop;
  my $int  = $loop->recurring(1 => sub { });
  local $SIG{INT} = local $SIG{TERM} = sub { $loop->stop };

sub start {
  my $self = shift;

  my $loop = $self->ioloop;
  if (my $max = $self->max_clients) { $loop->max_connections($max) }

  # Resume accepting connections
  if (my $servers = $self->{servers}) {
    push @{$self->acceptors}, $loop->acceptor(delete $servers->{$_}) for keys %$servers;

  # Start listening
  elsif (!@{$self->acceptors}) {
    $self->_listen($_) for @{$self->listen};

  return $self;

sub stop {
  my $self = shift;

  # Suspend accepting connections but keep listen sockets open
  my $loop = $self->ioloop;
  while (my $id = shift @{$self->acceptors}) {
    my $server = $self->{servers}{$id} = $loop->acceptor($id);

  return $self;

sub _build_tx {
  my ($self, $id, $c) = @_;

  my $tx = $self->build_tx->connection($id);
  $tx->res->headers->server('Mojolicious (Perl)');
  my $handle = $self->ioloop->stream($id)->timeout($self->inactivity_timeout)->handle;
  unless ($handle->isa('IO::Socket::UNIX')) {
  $tx->req->url->base->scheme('https') if $c->{tls};

  weaken $self;
    request => sub {
      my $tx = shift;

      my $req = $tx->req;
      if (my $error = $req->error) { $self->_trace($id, $error->{message}) }

      # WebSocket
      if ($req->is_handshake) {
        my $ws = $self->{connections}{$id}{next} = Mojo::Transaction::WebSocket->new(handshake => $tx);
        $self->emit(request => server_handshake $ws);

      # HTTP
      else { $self->emit(request => $tx) }

      # Last keep-alive request or corrupted connection
      my $c = $self->{connections}{$id};
      $tx->res->headers->connection('close') if ($c->{requests} || 1) >= $self->max_requests || $req->error;

      $tx->on(resume => sub { $self->_write($id) });

  # Kept alive if we have more than one request on the connection
  return ++$c->{requests} > 1 ? $tx->kept_alive(1) : $tx;

sub _close {
  my ($self, $id) = @_;
  if (my $tx = $self->{connections}{$id}{tx}) { $tx->closed }
  delete $self->{connections}{$id};

sub _trace { $_[0]->app->log->trace($_[2]) if $_[0]{connections}{$_[1]}{tx} }

sub _finish {
  my ($self, $id) = @_;

  # Always remove connection for WebSockets
  my $c = $self->{connections}{$id};
  return unless my $tx = $c->{tx};
  return $self->_remove($id) if $tx->is_websocket;

  # Finish transaction

  # Upgrade connection to WebSocket
  if (my $ws = delete $c->{next}) {

    # Successful upgrade
    if ($ws->handshake->res->code == 101) {
      $c->{tx} = $ws->established(1);
      weaken $self;
      $ws->on(resume => sub { $self->_write($id) });

    # Failed upgrade
    else { $ws->closed }

  # Close connection if necessary
  return $self->_remove($id) if $tx->error || !$tx->keep_alive;

  # Build new transaction for leftovers
  if (length(my $leftovers = $tx->req->content->leftovers)) {
    $tx = $c->{tx} = $self->_build_tx($id, $c);

  # Keep-alive connection
  $self->ioloop->stream($id)->timeout($self->keep_alive_timeout) unless $c->{tx};

sub _listen {
  my ($self, $listen) = @_;

  my $url   = Mojo::URL->new($listen);
  my $proto = $url->protocol;
  croak qq{Invalid listen location "$listen"} unless $proto eq 'http' || $proto eq 'https' || $proto eq 'http+unix';

  my $query   = $url->query;
  my $options = {backlog => $self->backlog};
  $options->{$_} = $query->param($_) for qw(fd single_accept reuse);
  if ($proto eq 'http+unix') { $options->{path} = $url->host }
  else {
    if ((my $host = $url->host) ne '*') { $options->{address} = $host }
    if (my $port = $url->port) { $options->{port} = $port }

  $options->{tls_ca} = $query->param('ca');
  /^(.*)_(cert|key)$/ and $options->{"tls_$2"}{$1} = $query->param($_) for @{$query->names};
  if (my $cert = $query->param('cert')) { $options->{tls_cert}{''} = $cert }
  if (my $key  = $query->param('key'))  { $options->{tls_key}{''}  = $key }
  my ($ciphers, $verify, $version) = ($query->param('ciphers'), $query->param('verify'), $query->param('version'));
  $options->{tls_options}{SSL_cipher_list} = $ciphers    if defined $ciphers;
  $options->{tls_options}{SSL_verify_mode} = hex $verify if defined $verify;
  $options->{tls_options}{SSL_version}     = $version    if defined $version;
  my $tls = $options->{tls} = $proto eq 'https';

  weaken $self;
  push @{$self->acceptors}, $self->ioloop->server(
    $options => sub {
      my ($loop, $stream, $id) = @_;

      $self->{connections}{$id} = {tls => $tls};
      warn "-- Accept $id (@{[$stream->handle->peerhost]})\n" if DEBUG;

      $stream->on(close   => sub { $self && $self->_close($id) });
      $stream->on(error   => sub { $self && $self->app->log->error(pop) && $self->_close($id) });
      $stream->on(read    => sub { $self->_read($id => pop) });
      $stream->on(timeout => sub { $self->_trace($id, 'Inactivity timeout') });

  return if $self->silent;
  $self->app->log->info(qq{Listening at "$url"});
  $url->host('')        if $url->host eq '*';
  $url->port($self->ports->[-1]) if !$options->{path} && !$url->port;
  say 'Web application available at ', $options->{path} // $url;

sub _read {
  my ($self, $id, $chunk) = @_;

  # Make sure we have a transaction
  my $c  = $self->{connections}{$id};
  my $tx = $c->{tx} ||= $self->_build_tx($id, $c);
  warn term_escape "-- Server <<< Client (@{[_url($tx)]})\n$chunk\n" if DEBUG;

sub _remove {
  my ($self, $id) = @_;

sub _url { shift->req->url->to_abs }

sub _write {
  my ($self, $id) = @_;

  # Protect from resume event recursion
  my $c = $self->{connections}{$id};
  return if !(my $tx = $c->{tx}) || $c->{writing};
  local $c->{writing} = 1;
  my $chunk = $tx->server_write;
  warn term_escape "-- Server >>> Client (@{[_url($tx)]})\n$chunk\n" if DEBUG;
  my $next = $tx->is_finished ? '_finish' : length $chunk ? '_write' : undef;
  return $self->ioloop->stream($id)->write($chunk) unless $next;
  weaken $self;
  $self->ioloop->stream($id)->write($chunk => sub { $self->$next($id) });


=encoding utf8

=head1 NAME

Mojo::Server::Daemon - Non-blocking I/O HTTP and WebSocket server


  use Mojo::Server::Daemon;

  my $daemon = Mojo::Server::Daemon->new(listen => ['http://*:8080']);
  $daemon->unsubscribe('request')->on(request => sub ($daemon, $tx) {

    # Request
    my $method = $tx->req->method;
    my $path   = $tx->req->url->path;

    # Response
    $tx->res->body("$method request for $path!");

    # Resume transaction


L<Mojo::Server::Daemon> is a full featured, highly portable non-blocking I/O HTTP and WebSocket server, with IPv6, TLS,
SNI, Comet (long polling), keep-alive and multiple event loop support.

For better scalability (epoll, kqueue) and to provide non-blocking name resolution, SOCKS5 as well as TLS support, the
optional modules L<EV> (4.32+), L<Net::DNS::Native> (0.15+), L<IO::Socket::Socks> (0.64+) and L<IO::Socket::SSL>
(2.009+) will be used automatically if possible. Individual features can also be disabled with the C<MOJO_NO_NNR>,
C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment variables.

See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.

=head1 SIGNALS

The L<Mojo::Server::Daemon> process can be controlled at runtime with the following signals.

=head2 INT, TERM

Shut down server immediately.

=head1 EVENTS

L<Mojo::Server::Daemon> inherits all events from L<Mojo::Server>.


L<Mojo::Server::Daemon> inherits all attributes from L<Mojo::Server> and implements the following new ones.

=head2 acceptors

  my $acceptors = $daemon->acceptors;
  $daemon       = $daemon->acceptors(['6be0c140ef00a389c5d039536b56d139']);

Active acceptor ids.

  # Check port
  mu $port = $daemon->ioloop->acceptor($daemon->acceptors->[0])->port;

=head2 backlog

  my $backlog = $daemon->backlog;
  $daemon     = $daemon->backlog(128);

Listen backlog size, defaults to C<SOMAXCONN>.

=head2 inactivity_timeout

  my $timeout = $daemon->inactivity_timeout;
  $daemon     = $daemon->inactivity_timeout(5);

Maximum amount of time in seconds a connection with an active request can be inactive before getting closed, defaults
to the value of the C<MOJO_INACTIVITY_TIMEOUT> environment variable or C<30>. Setting the value to C<0> will allow
connections to be inactive indefinitely.

=head2 ioloop

  my $loop = $daemon->ioloop;
  $daemon  = $daemon->ioloop(Mojo::IOLoop->new);

Event loop object to use for I/O operations, defaults to the global L<Mojo::IOLoop> singleton.

=head2 keep_alive_timeout

  my $timeout = $daemon->keep_alive_timeout;
  $daemon     = $daemon->keep_alive_timeout(10);

Maximum amount of time in seconds a connection without an active request can be inactive before getting closed,
defaults to the value of the C<MOJO_KEEP_ALIVE_TIMEOUT> environment variable or C<5>. Setting the value to C<0> will
allow connections to be inactive indefinitely.

=head2 listen

  my $listen = $daemon->listen;
  $daemon    = $daemon->listen(['']);

Array reference with one or more locations to listen on, defaults to the value of the C<MOJO_LISTEN> environment
variable or C<http://*:3000> (shortcut for C<>).

  # Listen on all IPv4 interfaces

  # Listen on all IPv4 and IPv6 interfaces

  # Listen on IPv6 interface

  # Listen on IPv4 and IPv6 interfaces
  $daemon->listen(['', 'http://[::1]:3000']);

  # Listen on UNIX domain socket "/tmp/myapp.sock" (percent encoded slash)

  # File descriptor, as used by systemd

  # Allow multiple servers to use the same port (SO_REUSEPORT)

  # Listen on two ports with HTTP and HTTPS at the same time
  $daemon->listen(['http://*:3000', 'https://*:4000']);

  # Use a custom certificate and key

  # Domain specific certificates and keys (SNI)

  # Or even a custom certificate authority

These parameters are currently available:

=over 2

=item ca


Path to TLS certificate authority file used to verify the peer certificate.

=item cert


Path to the TLS cert file, defaults to a built-in test certificate.

=item ciphers


TLS cipher specification string. For more information about the format see

=item fd


File descriptor with an already prepared listen socket.

=item key


Path to the TLS key file, defaults to a built-in test key.

=item reuse


Allow multiple servers to use the same port with the C<SO_REUSEPORT> socket option.

=item single_accept


Only accept one connection at a time.

=item verify


TLS verification mode.

=item version


TLS protocol version.


=head2 max_clients

  my $max = $daemon->max_clients;
  $daemon = $daemon->max_clients(100);

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

=head2 max_requests

  my $max = $daemon->max_requests;
  $daemon = $daemon->max_requests(250);

Maximum number of keep-alive requests per connection, defaults to C<100>.

=head2 silent

  my $bool = $daemon->silent;
  $daemon  = $daemon->silent($bool);

Disable console messages.

=head1 METHODS

L<Mojo::Server::Daemon> inherits all methods from L<Mojo::Server> and implements the following new ones.

=head2 ports

  my $ports = $daemon->ports;

Get all ports this server is currently listening on.

  # All ports
  say for @{$daemon->ports};

=head2 run


Run server and wait for L</"SIGNALS">.

=head2 start

  $daemon = $daemon->start;

Start or resume accepting connections through L</"ioloop">.

  # Listen on random port
  my $port = $daemon->listen([''])->start->ports->[0];

  # Run multiple web servers concurrently
  my $daemon1 = Mojo::Server::Daemon->new(listen => ['http://*:3000'])->start;
  my $daemon2 = Mojo::Server::Daemon->new(listen => ['http://*:4000'])->start;
  Mojo::IOLoop->start unless Mojo::IOLoop->is_running;

=head2 stop

  $daemon = $daemon->stop;

Stop accepting connections through L</"ioloop">.


You can set the C<MOJO_SERVER_DEBUG> environment variable to get some advanced diagnostics information printed to


=head1 SEE ALSO

L<Mojolicious>, L<Mojolicious::Guides>, L<>.