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:
- The interpreter is
!Send: it drivesListener::accept+Conn::read_request/Conn::write_response_connas owned futures on its own thread (no thread hop), calling back into the LOGOS handler between.awaitpoints. It never usesserve/spawn. - AOT compiles the handler to a plain
Fn(HttpRequest) -> HttpResponse(owned library types,Send), so it usesserve(block) orspawn.
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.
- Http
Server - A running server spawned onto the tokio runtime. Dropping the handle aborts
the accept loop (mirrors
relay::RelayServer). The handler must beSend; the interpreter’s!Sendhandler usesListener/Conndirectly instead. - Listener
- A bound TCP listener. Resolve
127.0.0.1:0to its real port viaListener::local_addrbefore dialing.
Enums§
- Read
Error - 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
Servestatement 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 port0. It is flushed because a server never returns: anything left in a buffer would never be seen. - bad_
request - The canned
400a 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
Servestatement binds — host AND port, composed HERE so no tier can build one of its own. - env_
port_ override - The port a
Servestatement actually binds: theLOGOS_PORTenvironment variable when set (falling back toPORT, 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
500a 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, bodyinternal error. - page_
with_ visitor_ token - A page document composed for one visitor of a server that answers CSRF tokens:
pagewith the visitor’s token in its head (tokenis the server’s answer at its reserved token path), that answer’sSet-Cookieset on it, and the whole never stored by a cache — the document is this visitor’s alone.Nonewhentokenis no token (a server with no## Sessiondoes not serve the path), and the page goes out as it was. - payload_
too_ large - The canned
413a request gets when its declared body or header block is larger than the server’s cap — a DEFINED “too large” refusal (distinct from the malformed400) so a client can tell the two apart. Shared by both tiers.text/plain, bodyPayload Too Large. - read_
error_ response - Map a request read error to the response the server writes: an oversized
BODY (a declared
Content-Lengthpast the cap) is a defined413(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 malformed400(the shape the cross-tierhttp_differentiallock pins). THE shared read-error law both the tokio and io_uring engines apply so their refusals are identical. - serve
- Bind
addrand serve forever — THE AOT entry, and the engine-selection seam:LOGOS_HTTP_ENGINE=uring|tokiopicks 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 (theFnMutcontract);serve_with_staticsis the shared-handler entry that also carries the static-response fast path. - serve_
here servefor a handler that cannot leave the thread it was made on — one that holds the program’s own bindings, whose collections are notSend. The handler runs on the thread that awaits this future, one request at a time, exactly asserveserializes 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’ shared500.- serve_
here_ with_ upgrades serve_herewith a table of protocol-upgrade paths, asserve_with_upgrades.- serve_
on - Serve on an already-bound
Listener, looping forever — the TOKIO engine. Connections run CONCURRENTLY (one task each, capped atMAX_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 sharedinternal_error500for that request and the server keeps serving. A parse error answers400and closes; a closed/errored socket just ends that connection. - serve_
on_ shared serve_onwith a SHARED handler (Fn + Send + Sync) — calls run CONCURRENTLY across connection tasks instead of queueing on one mutex.- serve_
on_ shared_ with serve_on_sharedwith a table of protocol-upgrade paths (seeUpgradeHandler) — aGETto one of them, asking to upgrade, is answered101and handed off instead of reachinghandler. Every other request goes through the ordinary request/response path unchanged, soserve_on_shared(l, h)is exactlyserve_on_shared_with(l, h, []).- serve_
on_ with serve_onwith a table of protocol-upgrade paths (seeUpgradeHandler) — aGETto one of them, asking to upgrade, is answered101and handed off instead of reachinghandler. Every other request is unaffected, soserve_on(l, h)is exactlyserve_on_with(l, h, []).- serve_
with_ statics - Bind
addrand 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-serializedStaticResponsestable both engines consult before dispatching. Engine selection as inserve. - serve_
with_ upgrades servewith a table of protocol-upgrade paths (seeUpgradeHandler) — aGETto one of them carryingUpgrade: websocketis handed to its handler once the handshake is answered, on either engine. A compiled backend whose pages talk to the relay registers/_relayhere (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 isSend). Always the TOKIO engine: the handle’s drop-aborts contract needs a task, not OS threads — engine selection belongs to the blockingserveentry (the io_uring engine’s own handle isengine_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 — becauseconfig.stamp_correlation_iddefaults on — anX-Correlation-Idon every response. THE entry an embedder (and the AOTServelowering, 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
reqagainstupgrades, in ONE place so every tier that drives its own connection loop (the tokioserve_connectionbelow; the interpreter, which is!Sendand never reachesserve_connectionat all) reaches the exact same answer rather than hand-copying the accept-key derivation and the101response line a second time.
Type Aliases§
- Upgrade
Handler - 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 carryingUpgrade: websocket, answered101 Switching Protocols) — the socket, plus any bytes already read into the connection’s buffer past that request (almost always empty; a bodylessGETends 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.