Skip to main content

Module server

Module server 

Source
Expand description

The tokio accept loop — the runtime under a LOGOS Serve.

This module is the swappable transport boundary. Its public surface — Listener / Conn (low-level, for the interpreter’s single-threaded re-entry) and serve / spawn (a convenience for a plain Fn(HttpRequest) -> HttpResponse) — is what a hyper-backed transport would re-provide identically, so swapping to hyper never touches the LOGOS handler bridge, http.lg, codegen, or the tests.

Two entry shapes, because the two tiers re-enter LOGOS differently:

Connection semantics (both tiers, so the cross-tier differential stays byte-identical): HTTP/1.1 persistent connections — requests are read in a loop per connection, the shared request_wants_keep_alive law decides the disposition, a parse error answers 400 and closes, EOF ends the connection, and at most MAX_INFLIGHT connections are in flight at once. A handler failure (a panic here; a runtime error on the interpreter tier) answers the shared internal_error 500 for THAT request and the server keeps serving.

Re-exports§

pub use super::lifecycle::DrainOutcome;
pub use super::lifecycle::LifecycleConfig;

Structs§

Conn
One accepted connection: the socket plus its receive buffer, which persists across requests — keep-alive drains exactly one request’s frame per parse and keeps the remainder (a pipelined next request) buffered, and the allocation is reused for every request on the connection.
HttpServer
A running server spawned onto the tokio runtime. Dropping the handle aborts the accept loop (mirrors relay::RelayServer). The handler must be Send; the interpreter’s !Send handler uses Listener/Conn directly instead.
Listener
A bound TCP listener. Resolve 127.0.0.1:0 to its real port via Listener::local_addr before dialing.

Enums§

ReadError
Reading a request off a connection failed.

Constants§

MAX_INFLIGHT
The most connections either tier holds open at once. Accepting pauses at the cap (further connections wait in the OS backlog), so a flood of held-open sockets cannot exhaust memory.

Functions§

announce_serving
Announce the address a Serve statement bound, on stdout and at once: Serving on http://<bound>. Every tier prints it from here, after the listener is bound and before the first request is read, so whoever started the server (the full-stack dev server, a test, an orchestrator) learns the real address, including the port the system chose for a requested port 0. It is flushed because a server never returns: anything left in a buffer would never be seen.
bad_request
The canned 400 a connection gets when its bytes don’t parse — shared by both tiers so the differential stays byte-identical.
env_bind_address
The whole address a Serve statement binds — host AND port, composed HERE so no tier can build one of its own.
env_port_override
The port a Serve statement actually binds: the LOGOS_PORT environment variable when set (falling back to PORT, the Fly/Heroku/Render convention), else the program’s own port expression. ONE seam serving both the largo full-stack dev proxy (which runs the program as a child on a port it chose) and PaaS deployment — implemented here so the interpreter and AOT tiers cannot drift.
internal_error
The canned 500 a request gets when its handler fails (a runtime error on the interpreter tier, a panic on the AOT tier) — shared by both tiers. The failure detail is deliberately NOT on the wire (it goes to stderr): status 500, text/plain, body internal error.
page_with_visitor_token
A page document composed for one visitor of a server that answers CSRF tokens: page with the visitor’s token in its head (token is the server’s answer at its reserved token path), that answer’s Set-Cookie set on it, and the whole never stored by a cache — the document is this visitor’s alone. None when token is no token (a server with no ## Session does not serve the path), and the page goes out as it was.
payload_too_large
The canned 413 a request gets when its declared body or header block is larger than the server’s cap — a DEFINED “too large” refusal (distinct from the malformed 400) so a client can tell the two apart. Shared by both tiers. text/plain, body Payload Too Large.
read_error_response
Map a request read error to the response the server writes: an oversized BODY (a declared Content-Length past the cap) is a defined 413 (never buffered / never dispatched) so a client can tell “too big” from “malformed”. Everything else — including an oversized HEADER block, whose head the server cannot even parse — is the malformed 400 (the shape the cross-tier http_differential lock pins). THE shared read-error law both the tokio and io_uring engines apply so their refusals are identical.
serve
Bind addr and serve forever — THE AOT entry, and the engine-selection seam: LOGOS_HTTP_ENGINE=uring|tokio picks the engine, defaulting to the Linux thread-per-core io_uring engine wherever a probe ring initializes (else tokio), always announced with one stderr line. Handler calls are serialized either way (the FnMut contract); serve_with_statics is the shared-handler entry that also carries the static-response fast path.
serve_here
serve for a handler that cannot leave the thread it was made on — one that holds the program’s own bindings, whose collections are not Send. The handler runs on the thread that awaits this future, one request at a time, exactly as serve serializes its calls; the engine runs on a thread and runtime of its own and hands each request across, so the caller’s runtime may be of any flavor. A panic inside a call answers the engines’ shared 500.
serve_here_with_upgrades
serve_here with a table of protocol-upgrade paths, as serve_with_upgrades.
serve_on
Serve on an already-bound Listener, looping forever — the TOKIO engine. Connections run CONCURRENTLY (one task each, capped at MAX_INFLIGHT) with HTTP/1.1 keep-alive, so a stalled or held-open connection never blocks the rest; handler calls are serialized on a mutex (one at a time, matching the single-threaded interpreter tier). A handler panic answers the shared internal_error 500 for that request and the server keeps serving. A parse error answers 400 and closes; a closed/errored socket just ends that connection.
serve_on_shared
serve_on with a SHARED handler (Fn + Send + Sync) — calls run CONCURRENTLY across connection tasks instead of queueing on one mutex.
serve_on_shared_with
serve_on_shared with a table of protocol-upgrade paths (see UpgradeHandler) — a GET to one of them, asking to upgrade, is answered 101 and handed off instead of reaching handler. Every other request goes through the ordinary request/response path unchanged, so serve_on_shared(l, h) is exactly serve_on_shared_with(l, h, []).
serve_on_with
serve_on with a table of protocol-upgrade paths (see UpgradeHandler) — a GET to one of them, asking to upgrade, is answered 101 and handed off instead of reaching handler. Every other request is unaffected, so serve_on(l, h) is exactly serve_on_with(l, h, []).
serve_with_statics
Bind addr and serve forever with a SHARED handler (Fn + Send + Sync — the compiled-handler contract; calls may run concurrently across shards or connection tasks) and an optional pre-serialized StaticResponses table both engines consult before dispatching. Engine selection as in serve.
serve_with_upgrades
serve with a table of protocol-upgrade paths (see UpgradeHandler) — a GET to one of them carrying Upgrade: websocket is handed to its handler once the handshake is answered, on either engine. A compiled backend whose pages talk to the relay registers /_relay here (relay::RelayHub::upgrades).
spawn
Bind addr, spawn the accept loop onto the tokio runtime, and return a handle carrying the bound port. For tests and embedders (the handler is Send). Always the TOKIO engine: the handle’s drop-aborts contract needs a task, not OS threads — engine selection belongs to the blocking serve entry (the io_uring engine’s own handle is engine_uring::UringServer).
spawn_with_lifecycle
Bind addr, spawn the LIFECYCLE-AWARE accept loop, and return a handle that exposes the full production lifecycle: HttpServer::set_ready, HttpServer::drain, and — because config.stamp_correlation_id defaults on — an X-Correlation-Id on every response. THE entry an embedder (and the AOT Serve lowering, via a config seam) uses for a server that “stays serving”: graceful drain, readiness gating, bounded backpressure, per-request time bounds, malformed-flood survival, and correlation ids.
try_upgrade
The RFC 6455 handshake decision for req against upgrades, in ONE place so every tier that drives its own connection loop (the tokio serve_connection below; the interpreter, which is !Send and never reaches serve_connection at all) reaches the exact same answer rather than hand-copying the accept-key derivation and the 101 response line a second time.

Type Aliases§

UpgradeHandler
A protocol-upgrade handler: takes over a connection’s raw socket once the shared loop has completed the HTTP-level handshake (a GET <path> request carrying Upgrade: websocket, answered 101 Switching Protocols) — the socket, plus any bytes already read into the connection’s buffer past that request (almost always empty; a bodyless GET ends its own frame at the header block, but a pipelined first WS frame is possible and must not be dropped). Spawned as its own task by the loop, which then moves on to the next connection — the handler owns the socket for as long as it likes.