Pyjo.Server.Daemon - Non-blocking I/O HTTP and WebSocket server

from Pyjo.Server.Daemon import Daemon

daemon = Daemon(listen=['http://*:8080'])


@daemon.unsubscribe('request').on('request')
def request(daemon, tx):
    # Request
    method = tx.req.method
    path = tx.req.url.path

    # Response
    tx.res.code = 200
    tx.res.headers.content_type = 'text/plain'
    tx.res.body = f'{method} request for {path}!'.encode()

    # Resume transaction
    tx.resume()


daemon.run()

# Or import the module, as in Google Python Style Guide

import Pyjo.Server.Daemon

daemon = Pyjo.Server.Daemon.new(listen=['http://*:8080'])


@daemon.unsubscribe('request').on('request')
def request(daemon, tx):
    # Request
    method = tx.req.method
    path = tx.req.url.path

    # Response
    tx.res.code = 200
    tx.res.headers.content_type = 'text/plain'
    tx.res.body = f'{method} request for {path}!'.encode()

    # Resume transaction
    tx.resume()


daemon.run()

Pyjo.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. It also speaks FastCGI to a web server in front of it, on the listen locations fcgi:// and fcgi+unix://, with Pyjo.Server.FastCGI.

TLS support comes from ssl of the standard library, and can be disabled with the PYJO_NO_TLS environment variable, as name resolution and SOCKS5 support of Pyjo.IOLoop with PYJO_NO_NNR and PYJO_NO_SOCKS.

A daemon is also a context manager, which stops listening and closes its connections at its end.

The end of a with block stops the daemon and closes its sockets.

Signals

The Pyjo.Server.Daemon process can be controlled at runtime with the following signals.

INT, TERM

Shut down server gracefully: stop accepting new connections and wait for the accepted ones to be closed. A second INT, as of Ctrl-C pressed twice, shuts it down immediately, a second TERM does nothing.

QUIT

Shut down server immediately. On Windows, which has no QUIT, BREAK does it, as of Ctrl-Break.

HUP

Ignored, as there is nothing to reload. Windows has no HUP.

Events

Pyjo.Server.Daemon inherits all events from Pyjo.Server.

Debugging

You can set the PYJO_SERVER_DEBUG environment variable to get some advanced diagnostics information printed to sys.stderr.

PYJO_SERVER_DEBUG=1

See also

Pyjoyment, https://mojolicious.org.

Ported from Mojo::Server::Daemon of Mojolicious 9.49.

Classes

class Pyjo.Server.Daemon.Pyjo_Server_Daemon(*, acceptors: list[str] | None = None, backlog: int | None = None, inactivity_timeout: float | None = None, ioloop: Pyjo_IOLoop | None = None, keep_alive_timeout: float | None = None, listen: Sequence[Location] | None = None, max_clients: int | None = None, max_requests: int = 100, silent: bool | None = None, **kwargs: Any)

Pyjo.Server.Daemon inherits all methods from Pyjo.Server and implements the following new ones.

acceptors: list[str]
acceptors = daemon.acceptors
daemon.acceptors = ['6be0c140ef00a389c5d039536b56d139']

Active acceptor ids.

# Check port
port = daemon.ioloop.acceptor(daemon.acceptors[0]).port
backlog
backlog = daemon.backlog
daemon.backlog = 128

Listen backlog size, defaults to socket.SOMAXCONN.

close() → None
daemon.close()

Close the listen sockets and the connections of the server, as the end of a with block does.

property inactivity_timeout: float
timeout = daemon.inactivity_timeout
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 PYJO_INACTIVITY_TIMEOUT environment variable or 30. Setting the value to 0 will allow connections to be inactive indefinitely.

ioloop: Pyjo_IOLoop
loop = daemon.ioloop
daemon.ioloop = Pyjo.IOLoop.new()

Event loop object to use for I/O operations, defaults to the global Pyjo.IOLoop singleton.

property keep_alive_timeout: float
timeout = daemon.keep_alive_timeout
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 PYJO_KEEP_ALIVE_TIMEOUT environment variable or 5. Setting the value to 0 will allow connections to be inactive indefinitely.

property listen: list[Location]
listen = daemon.listen
daemon.listen = ['https://127.0.0.1:8080']

List of one or more locations to listen on, as strings or Pyjo.URL objects, defaults to the value of the PYJO_LISTEN environment variable, to http://*: with the port of the PORT environment variable, which platforms as Knative, Cloud Run and Heroku set, or to http://*:3000 (shortcut for http://0.0.0.0:3000).

# Listen on all IPv4 interfaces
daemon.listen = ['http://*:3000']

# Listen on all IPv4 and IPv6 interfaces
daemon.listen = ['http://[::]:3000']

# Listen on IPv6 interface
daemon.listen = ['http://[::1]:4000']

# Listen on IPv4 and IPv6 interfaces
daemon.listen = ['http://127.0.0.1:3000', 'http://[::1]:3000']

# Listen on UNIX domain socket "/tmp/myapp.sock" (percent encoded slash)
daemon.listen = ['http+unix://%2Ftmp%2Fmyapp.sock']

# FastCGI for a web server in front of the application
daemon.listen = ['fcgi://127.0.0.1:9000']

# FastCGI on UNIX domain socket "/tmp/myapp.sock"
daemon.listen = ['fcgi+unix://%2Ftmp%2Fmyapp.sock']

# File descriptor, as used by systemd
daemon.listen = ['http://127.0.0.1?fd=3']

# Allow multiple servers to use the same port (SO_REUSEPORT)
daemon.listen = ['http://*:8080?reuse=1']

# Listen on two ports with HTTP and HTTPS at the same time
daemon.listen = ['http://*:3000', 'https://*:4000']

# Location of a URL object
daemon.listen = [Pyjo.URL.new().set(scheme='http', host='127.0.0.1', port=3000)]

# Use a custom certificate and key
daemon.listen = ['https://*:3000?cert=/x/server.crt&key=/y/server.key']

# Domain specific certificates and keys (SNI)
daemon.listen = ['https://*:3000?example.com_cert=/x/my.crt&example.com_key=/y/my.key']

# Or even a custom certificate authority
daemon.listen = ['https://*:3000?cert=/x/server.crt&key=/y/server.key&ca=/z/ca.crt']

These parameters are currently available:

ca
ca=/etc/tls/ca.crt

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

cert
cert=/etc/tls/server.crt
mojolicious.org_cert=/etc/tls/mojo.crt

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

ciphers
ciphers=AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH

TLS cipher specification string. For more information about the format see https://www.openssl.org/docs/manmaster/man1/ciphers.html#CIPHER-STRINGS.

fd
fd=3

File descriptor with an already prepared listen socket.

key
key=/etc/tls/server.key
mojolicious.org_key=/etc/tls/mojo.key

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

reuse
reuse=1

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

single_accept
single_accept=1

Only accept one connection at a time.

verify
verify=0x00

TLS verification mode: 0x00 verifies no peer, 0x01 verifies a peer certificate if there is one, and 0x03 requires it.

version
version=TLSv1_2

TLS protocol version.

max_clients
max = daemon.max_clients
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 Pyjo.IOLoop.Pyjo_IOLoop.max_connections.

max_requests
max = daemon.max_requests
daemon.max_requests = 250

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

ports() → list[int]
ports = daemon.ports()

Get all ports this server is currently listening on.

# All ports
for port in daemon.ports():
    print(port)
run() → None
daemon.run()

Run server and wait for the signals INT, TERM and QUIT.

silent
boolean = daemon.silent
daemon.silent = boolean

Disable console messages.

start() → Self
daemon = daemon.start()

Start or resume accepting connections through ioloop.

# Listen on random port
daemon.listen = ['http://127.0.0.1']
port = daemon.start().ports()[0]

# Run multiple web servers concurrently
daemon1 = Pyjo.Server.Daemon.new(listen=['http://*:3000']).start()
daemon2 = Pyjo.Server.Daemon.new(listen=['http://*:4000']).start()
if not Pyjo.IOLoop.is_running():
    Pyjo.IOLoop.start()
stop() → Self
daemon = daemon.stop()

Stop accepting connections through ioloop.