DOCUMENTATION / 05
Architecture
Tez is a standalone HTTP/1.x origin server. The network path uses Boost.Asio and Boost.Beast; routing and file handling return a small application Response that the session serializes to HTTP.
Session ownership#
Each accepted connection has a session containing the TCP stream, HTTP parser, persistent input buffer, response object, request count, and connection accounting. Shared ownership keeps the session alive while asynchronous callbacks are outstanding. The response object remains alive until its write completes.
The input buffer belongs to the connection, not to one request. A read can receive headers, body bytes, and part of the next pipelined request together. Only bytes consumed by the parser are removed; remaining bytes are used by the next request parser.
One response write finishes before the next request is dispatched on that connection. This maintains order without a separate unbounded response queue. A session's strand prevents its handlers from running concurrently across I/O workers.
Execution model#
The listener accepts asynchronously. A configured number of threads call io_context::run(). Different sessions may execute on different workers; a session is not assigned a permanent operating-system thread. Socket waits are asynchronous, so a quiet keep-alive client does not occupy a worker in a blocking read.
Route dispatch, file opens and reads, cache access, JSON serialization, and request logging are synchronous handler work. The file cache uses locking; logging serializes complete lines. The design does not claim asynchronous disk I/O, zero-copy responses, lock-free execution, or absence of contention.
The connection limit is admission control. It bounds active sessions, not the operating system's listen backlog or total clients trying to connect. Excess accepted connections are closed without dispatching application work.
Routing and files#
Configured routes are parsed and validated once at startup, then published as an immutable snapshot. Built-in routes provide a health response and method-dispatch examples. The configured response map is already in memory; looking it up does not require an additional LRU response-cache layer.
Static file requests use a separate path:
- Strip the query component, decode the path once, and reject invalid or unsafe components.
- Walk from the configured root directory using descriptor-relative opens that reject symlinks.
- Check that the opened target is a regular file within the size limit.
- Compare file identity and metadata with any cache entry.
- On a miss, read a bounded body and recheck metadata before caching it.
The cache maintains recency, insertion age, logical bytes, and entry count. TTL expiration and changed metadata cause a miss. The cache is an optimization after access validation, not a shortcut around the document-root boundary.
Deadlines and shutdown#
Each asynchronous header read, body read, and response write has a deadline. A completed operation does not grant the next phase an unlimited lifetime. The request-count limit also terminates long-lived keep-alive sessions with a correctly advertised final close.
SIGINT and SIGTERM stop network processing. Pending socket work is released during teardown and worker threads are joined. This avoids a worker pool stuck in synchronous socket reads, but it is not graceful response draining: an in-flight response may be interrupted. Synchronous filesystem or log operations are not preempted by the socket deadline.
Source map#
| File | Responsibility |
|---|---|
src/main.cpp |
CLI, listener, session lifecycle, HTTP I/O, deadlines, worker startup and shutdown |
src/request.cpp |
Request parsing and validation helpers |
src/router.cpp |
Validated route configuration and method dispatch |
src/file_server.cpp |
Confined static file access and MIME selection |
src/middleware.cpp |
Cache support and request logging |
include/response.hpp |
Application response representation |
tests/ |
Isolated component tests and socket integration coverage |
For design rationale, alternatives, and remaining gaps, see engineering notes. For exact operational defaults, use configuration.