feat(contrib.tinyweb): serve over TLS with with_tls - #1900
Merged
Conversation
tinyweb wraps std.http, whose server has had `server_set_tls` all along --
tinyweb simply never surfaced it, so every tinyweb app was plaintext-only for
no deeper reason than a missing setter.
server = tinyweb.web_server_host("0.0.0.0", 443) { ... }
_ = tinyweb.with_tls(server, "/etc/ssl/cert.pem", "/etc/ssl/key.pem")
err = tinyweb.tw_start(server)
`with_tls` records the PEM pair on the existing config map; tw_start applies it
after bind and BEFORE registering routes, so a bad pair fails start-up rather
than after the server looks ready.
The design decision worth stating: a bad or HALF-configured pair is a start-up
ERROR, not a silent skip. A server that quietly downgrades to plaintext on a
port the caller believes is encrypted is worse than one that refuses to start,
so `with_tls(srv, cert, "")` returns "with_tls needs BOTH a cert and a key"
rather than serving HTTP. That is the same failure shape as the
`response_accept_tunnel` TLS gap found in #1899, where a silent plaintext
fallback had shipped unnoticed downstream.
New suite tests three things: a real TLS handshake serving the route body,
plain http to the TLS port NOT returning the body, and the half-configured
pair refusing to start. It is also the first tests/integration/tinyweb_* suite,
so contrib/tinyweb gains CI coverage it did not have.
Verified on Linux and macOS 15.7.7 (suite passes on both); Windows skips it by
design, as the other HTTP-server suites do, and still compiles the module.
The three existing in-tree tinyweb tests are unaffected. make test 409/409;
fmt gate and check-docs green.
NB the diff is deliberately additive-only (35 insertions, 0 deletions in
module.ae). `ae fmt contrib` reformats 23 unrelated contrib modules and most of
tinyweb's own examples; that noise is reverted here so the change is
reviewable, and the tree stays fmt-canonical either way.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The three Linux legs failed with integration_tinyweb_tls_half (TIMED OUT — hung; killed by per-test timeout) integration_tinyweb_tls_server (TIMED OUT — hung; killed by per-test timeout) test-ae runs every .ae under tests/ STANDALONE, so both fixtures were executed directly rather than only by their shell script -- and tw_start blocks forever, so each hung until the sweep killed it. The sibling http_server_sse/ is pruned for exactly this reason; tinyweb_tls/ needed the same entry and did not get it. Worth noting the difference from the SSE fixture added in #1899, which is NOT pruned and is nonetheless fine: it ends with sleep(60000); exit(0), so it self-terminates. Verified that directly rather than assuming it shared this bug -- it exits 0 when run standalone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The macOS x86_64 leg failed:
[FAIL] cache_subdir_entry_root_module: an unchanged re-run took 159ms;
the cache appears never to hit, so every run is a rebuild
That assertion was mine, from #1886, and it was wrong twice over.
The first version compared against an absolute "< 80ms", with a comment
claiming it "leaves generous headroom on a loaded CI runner". It did not: it
encoded the speed of the machine it was written on, and a slow macOS runner
needs 159ms for a genuine cache HIT.
The obvious repair -- time a forced rebuild and require the hit to be markedly
cheaper -- is also unsound, and I only found that by running it five times on a
real Mac rather than once: the "forced rebuild" can itself hit a warm entry
built earlier in the same test, so rebuild and hit measure 43ms and 43ms and
the ratio check fires on a perfectly good cache.
So the timing is gone entirely. A hit REUSES a cache entry and a miss CREATES
one, so counting entries answers the question exactly, on any machine, with no
clock involved. The test now asserts both directions -- an unchanged re-run
must not add an entry, and an edit must -- so it cannot pass by the cache being
broken the other way.
The greeter body is stamped unique per run ($$ + epoch), because a fixed body
already has an entry from an earlier invocation and "did the count grow" would
then answer no for the wrong reason. That was the third flake, found the same
way.
Verified: 3/3 locally and 5/5 on a real macOS 15.7.7 box (the platform that
failed), and it still CATCHES a broken cache -- sabotaging the key so it can
never hit reports "entry (20 -> 21)".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… path
The three Windows legs failed:
[FAIL] cache_subdir_entry_root_module: editing the module added no cache
entry (0 -> 0) — the key did not change
Zero on both sides is the tell: the script was counting files in a directory
that does not exist on that machine. It used "$HOME/.aether/cache", but `ae`
resolves the cache under USERPROFILE on Windows (get_home_dir in ae_cache.c
prefers it), and a shell under MSYS2 reports $HOME as a POSIX path. On a GitHub
runner those are genuinely different directories, so the count was 0 either
way and the assertion compared 0 to 0.
This is also why it passed on our own Windows box and failed in CI, which is
worth recording: there HOME=/c/Users/paul and USERPROFILE=C:\Users\paul are the
same directory in two spellings, so the guess happened to work. A developer
machine has a coherent environment; a hermetic runner does not. Same shape as
the -lfyaml finding in #1896, where our box's pkg-config could not see what
CI's could.
`ae cache` prints "Cache: N build(s)" from the same code that writes them, so
there is nothing left to guess. Verified 3/3 locally and 3/3 on the Windows
box, including with HOME deliberately repointed away from USERPROFILE.
Third revision of this assertion (absolute timing, then a rebuild-vs-hit ratio,
now this). The first two were wrong about the machine; this one asks the
program.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
tinyweb wraps
std.http, whose server has hadserver_set_tlsall along — tinyweb simply never surfaced it. So every tinyweb app was plaintext-only for no deeper reason than a missing setter.with_tlsrecords the PEM pair on the existing config map;tw_startapplies it after bind and before registering routes, so a bad pair fails start-up rather than after the server looks ready.The design decision worth reviewing
A bad or half-configured pair is a start-up error, not a silent skip.
A server that quietly downgrades to plaintext on a port the caller believes is encrypted is worse than one that refuses to start. That's the same failure shape as the
response_accept_tunnelTLS gap found in #1899 — where a silent plaintext fallback had already shipped downstream without anyone noticing, because the failure was a runtime string no test looked at.Testing
New suite asserts three things:
http://to the TLS port does not return the body;It's also the first
tests/integration/tinyweb_*suite, socontrib/tinywebgains CI coverage it didn't have.tinyweb_tlsmake test409/409; fmt gate and check-docs green.A note on the diff
It is deliberately additive-only — 35 insertions, 0 deletions in
module.ae.ae fmt contribreformats 23 unrelated contrib modules and most of tinyweb's own examples (collapsing alignedconstblocks,exports (→exports(). That noise is reverted here so the change is reviewable; the tree stays fmt-canonical either way, as the passing gate shows. Worth knowing that a contrib-wideae fmtis a large diff waiting to happen — probably better as its own deliberate commit than smuggled into a feature.🤖 Generated with Claude Code