Skip to main content

ESGI integration

ESGI (Event-based Server Gateway Interface) is a Python gateway specification for HTTP and WebSocket: one application invocation per logical connection, a scope (metadata), and a protocol object with blocking read/write methods — without an ASGI-style receive / send loop. The normative draft lives in PROTOCOL.md (version 0.1-draft).

Gevent-only worker

module_esgi always runs in a gevent worker. There is no asyncio on the Python side for the connection to Caddy: the subprocess uses gevent.server.StreamServer, plain blocking sockets (cooperatively scheduled by gevent), and greenlets — e.g. one greenlet for the client connection and one to read WebSocket frames into a queue. Your app remains a synchronous def application(scope, protocol): ... (or __esgi__); it is never async def and is never driven by asyncio.

Install gevent in the same environment as your app (e.g. venv referenced from the Caddyfile).

Configuration

Use module_esgi with the usual module:variable import path. Exactly one of module_wsgi, module_asgi, and module_esgi may be set.

runtime must be gevent for ESGI (this is also the default when runtime is omitted).

python {
module_esgi "main:application"
runtime gevent
venv "./venv"
workers 4
}

Optional hooks from the spec: if the loaded app defines __esgi_init__ / __esgi_del__, the worker calls them once at process startup and once at orderly shutdown.

Dispatch prefers __esgi__(self, scope, protocol) when present over a plain callable.

Runtime semantics

DirectiveValuesDefault (when runtime is omitted)
module_wsgisync, geventsync
module_esgigevent onlygevent
module_asginative, uvloopuvloop
  • native (ASGI): standard asyncio event loop (WSGI/ASGI worker subprocess).
  • uvloop (ASGI): prefer uvloop when installed; otherwise warn and fall back to native asyncio. (Legacy configs may say libuv; it is normalized to uvloop.)

Protocol coverage

The current worker targets 0.1-draft behavior used by real apps:

  • HTTP: read_body, iter_body, response_empty / response_str / response_bytes, response_file, response_file_range, wait_disconnect (no-op until disconnect tracking lands).
  • WebSocket: acceptWsTransport with receive / send_bytes / send_str; root close with RFC 6455 semantics.
  • response_stream / full streaming refinements are not implemented yet (NotImplementedError).

Integration tests

The repository includes tests/simple_esgi: a tiny ESGI app with HTTP and WebSocket echo checks (depends on gevent). Run it like other Docker integration targets:

./tests/integration.sh simple_esgi 3.13

Roadmap

  • Narrow named exceptions for oversized bodies and disconnects (spec open questions).
  • response_stream and chunked/streaming parity with the draft.
  • Optional true gevent-based WSGI mode (today runtime gevent for WSGI is still documented as thread-pool parity).

For ASGI/WSGI usage, see the main configuration reference.