feat(httpserver): add TLS support and socket tuning parameters - #165
Conversation
There was a problem hiding this comment.
CI green across lint + 3.10–3.13. Clean API design — passing a pre-built ssl.SSLContext is the right pattern (matches aiohttp/uvicorn). Version bump to 0.5.0 is appropriate for new public parameters. Tests are thorough: TLS handshake, plaintext rejection, POST over TLS, socket tuning, and the combined case. Module-scoped cert fixtures keep the expensive openssl calls from running per-test.
Two actionable items:
1. backlog silently dropped for Unix sockets
_serve() only forwards ssl_context to _start_unix_socket(), but asyncio.start_unix_server() also accepts backlog. A caller passing run(socket="/tmp/app.sock", backlog=2048) gets silent parameter loss. Should forward backlog to the Unix path too. (reuse_address/reuse_port are TCP-only so dropping those is correct.)
2. backlog docstring/type mismatch
Docstring says "None leaves the OS default (typically 128)" but the type annotation is int = 100, so None isn't accepted. Either change the type to int | None = None (and conditionally pass it to asyncio), or fix the docstring to say "Defaults to 100" (which matches asyncio's own default). The current text describes behavior the caller can't reach.
No other issues. Scheme-aware log messages (https://, https+unix:) are a nice touch.
There was a problem hiding this comment.
Clean API — following the aiohttp pattern of accepting a pre-built SSLContext is the right call for a zero-dep server. TLS forwarding to both TCP and Unix socket paths is correct, socket tuning params are well-documented, and the 7 new tests cover the key combinations. CI green.
Actionable (non-blocking)
1. backlog docstring/type mismatch
The docstring says:
Noneleaves the OS default (typically 128).
But the type is int = 100 — callers can't actually pass None. Either widen the type to int | None = None (and let asyncio pick its own default), or update the docstring to describe the current 100 default. Applies to both run() and _serve().
2. Socket tuning params silently ignored on Unix socket path
backlog, reuse_address, reuse_port are forwarded to asyncio.start_server() for TCP, but the Unix socket path only receives ssl_context:
server = await self._start_unix_socket(socket, ssl_context=ssl_context)
# ← backlog not forwardedasyncio.start_unix_server() also accepts backlog, so a caller doing run(socket="/tmp/app.sock", backlog=2048) silently drops the tuning. Either forward backlog to _start_unix_socket(), or document that socket tuning params are TCP-only. (reuse_address/reuse_port don't apply to Unix sockets, so those are fine to skip.)
3. No test for TLS over Unix socket
The code correctly forwards ssl_context to start_unix_server(), but there's no test exercising that path. A single TLS-over-UDS test would close the coverage gap — the _start_unix_socket method already handles socket cleanup and permissions, and TLS adds another layer worth verifying end-to-end.
Minor
_generate_self_signed_certdepends onopensslCLI with no skip guard — if CI or a contributor's machine doesn't have it, all 7 tests fail at fixture time. Apytest.importorskip-style check orshutil.which("openssl")skip would be safer.- Test cleanup (
app.shutdown()+await serve_task) runs after assertions — if an assertion fails, the server leaks. Atry/finallywould be more robust, though this matches the existing test style.
There was a problem hiding this comment.
Review — PR #165: feat(httpserver): add TLS support and socket tuning parameters
CI: ✅ lint + test 3.10–3.13 all pass
Scope: +314/−14, 3 files — TLS via ssl.SSLContext pass-through, plus backlog/reuse_address/reuse_port
Design is clean — the pass-through SSLContext pattern (aiohttp/uvicorn style) keeps the module zero-dependency while giving callers full control over certs, mTLS, ciphers, and ALPN. The kwargs-dict pattern for optional params avoids passing None to asyncio, which is the right approach. reuse_address/reuse_port correctly omitted from the Unix socket path. Version bump to 0.5.0 is appropriate. https+unix: scheme in log output is a nice touch.
Actionable
- No test for TLS over Unix socket
_start_unix_socket()now accepts and forwardsssl_context, but all 7 TLS tests use the TCP path. A test exercising_serve("127.0.0.1", 0, socket="/tmp/test.sock", ssl_context=ctx)would close the gap — especially since this is new code path that can't be verified by the TCP tests.
Non-blocking
-
asyncio.sleep(0.3)startup race — All 7 tests use a fixed 300ms delay for server readiness. On a slow CI runner or under load, this races. Anasyncio.wait_forpolling loop on the port would be more robust, though the existing httpserver tests likely use the same pattern, so it's at least consistent. -
opensslCLI dependency in tests —_generate_self_signed_cert()shells out toopenssl. If it's missing (unlikely on CI, but possible in minimal containers), all TLS tests fail with a subprocess error instead of a clean skip. Apytest.importorskiporpytest.mark.skipif(shutil.which("openssl") is None, ...)would degrade gracefully. -
Manifest rebase noise — The diff includes
content_hash/last_updatedchanges forratelimitandprofilermodules, unrelated to this PR. Not blocking, but a clean rebase onto current master before merge would trim the diff.
LGTM with item 1 addressed.
|
Thanks @elena-oaklight and @milo-oaklight — all three items addressed:
|
Expose ssl_context, backlog, reuse_address, and reuse_port on run() and _serve(), forwarded to asyncio.start_server() / start_unix_server(). TLS is opt-in: pass a pre-built ssl.SSLContext to enable HTTPS. Startup log messages now include the scheme (http:// vs https://). Closes #162
- Forward `backlog` to `_start_unix_socket()` so callers using unix sockets don't get silent parameter loss. - Change `backlog` type from `int = 100` to `int | None = None` so `None` actually works as the docstring describes. - Conditionally pass `backlog`/`reuse_address`/`reuse_port` only when not None, letting asyncio use its own defaults.
Cover the ssl_context forwarding path in _start_unix_socket(), as suggested in review.
- Replace asyncio.sleep(0.3) with readiness polling (_wait_ready helper) for deterministic startup detection instead of fixed delays. - Wrap all test bodies in try/finally to ensure server cleanup on assertion failure. - Add ASYNC240 ignore for TLS test file (os.path.exists in readiness polling, same as unix socket tests).
d4f1b90 to
df81edc
Compare
Summary
ssl_contextparameter torun()/_serve()/_start_unix_socket()for TLS termination, forwarded asssl=toasyncio.start_server()/start_unix_server()backlog,reuse_address,reuse_portsocket tuning parameters, also forwarded to asynciohttp://vshttps://,unix:vshttps+unix:)API follows the aiohttp pattern — caller passes a pre-built
ssl.SSLContext, giving full control over certs, mTLS, ciphers, and ALPN without the server needing to know about any of it.Closes #162
Test plan
pytest httpserver/test_httpserver_tls.py)