Add HTTPS/TLS support via TlsServerCtx integration#25
Add HTTPS/TLS support via TlsServerCtx integration#25carpentry-agent[bot] wants to merge 1 commit into
Conversation
Connection sum type abstracts over TcpStream and TlsStream. serve-tls and defserver-tls provide TLS server entry points. Sendfile responses are transparently resolved to in-memory reads for TLS connections since kernel sendfile(2) cannot encrypt data.
There was a problem hiding this comment.
Build & Tests
Build: Carp type/borrow checker passes (warnings only: missing prn for TlsStream). C compilation fails on Linux (ARM) with TM.tm_zone const qualifier errors — verified this is pre-existing on main, not introduced by this PR.
Tests: cannot run locally (same pre-existing C compilation failure). CI: pass on macOS (the only platform in this repo's CI matrix).
Findings
Thorough review — read all changed files in full, traced through the event loop, checked ownership semantics against the tls library API.
-
Connection abstraction is clean. The
Connectionsum type (Plain/Tls) withConndispatch module is the right design. All event loop I/O paths (handle-writable,handle-readable,handle-ws-readable,handle-ws-upgrade,conn-cleanup) correctly go throughConn.*instead of rawTcpStream.*. -
FD ownership in
handle-accept-tlsis correct.web_detach_fdsetsTcpStream.fd = -1(preventing double-close on drop), thenTlsStream.accepttakes ownership of the raw fd. Per the tls library docs,acceptcloses the fd on error, so there's no FD leak in either path. The detachedTcpStreamdrops harmlessly withfd = -1. -
Sendfile resolution for TLS is correct. Traced the full path:
web-tls-resolve-sendfilereads the file into the response body and stripsX-Sendfile→sf-pathbecomesNothing→ sendfile FDs never populateConnState.sf-fds→Conn.sendfile-chunk(which returnsErrorfor TLS) is never reached. The fallback toResponse.not-foundon file-open failure is sensible. -
handle-writablesendfile path is safe even if reached.Conn.sendfile-chunkreturns(Result.Error ...)for TLS →sn = -1→queue-close. Defensive, correct. -
WebSocket over TLS.
handle-ws-upgradenow acceptsConnectionand usesConn.send-nb. WS read/write paths go throughConn.*viaConnState.streams. Correct. -
CHANGELOG updated. Feature description and breaking change (
ConnState.streamstype) both documented. -
tls dependency pinned to commit
40e9fef— necessary since non-blocking I/O was added after the0.1.0tag. Worth tagging a new tls release and updating this when convenient, but not blocking. -
Blocking handshake. Documented in the
serve-tlsdoc. Acceptable for moderate connection rates. The doc correctly notes this is a future improvement area. -
web-tls-resolve-sendfileopens files in text mode ("r"). Harmless on Unix (identical to binary mode). On Unix-only Carp this is a non-issue.
No bugs or correctness issues found.
Verdict: merge
Well-designed TLS integration. The Connection abstraction is clean, ownership handling is correct, edge cases are covered, and limitations are documented. Good work.
hellerve
left a comment
There was a problem hiding this comment.
So far I’ve been thinking that TLS termination should happen before web touches anything, handled by the underlying platform. I’m open to amending that idea for good reasons.
What are your thoughts?
There was a problem hiding this comment.
Fair point — and on reflection I agree with your instinct. The standard approach is TLS termination at the edge (reverse proxy, load balancer, or platform service), and baking it into the framework adds complexity (the Connection sum type, dispatch through every I/O path) that's disproportionate to the convenience gain. For the Carp ecosystem specifically, a guide on running behind nginx/caddy with TLS would serve most deployments better than built-in support.
Happy to close this and write a deployment doc instead if that's the direction you'd prefer.
|
Thanks for the thorough work here, and to the reviewer for the careful pass. I've decided not to merge this, and to keep TLS termination out of The crypto itself isn't the concern. There's also a promise problem. Finally, finding #8 in the review notes the handshake is blocking. In a non-blocking event loop that means one slow client can stall the entire loop during the handshake, so this is only safe to run behind something that terminates TLS or rate-limits connections anyway. Built-in TLS that's only safe behind a proxy is the worst of both worlds. The direction instead: terminate TLS at the edge (nginx, caddy, or a platform load balancer), run by people who track the CVEs. Closing this, and closing #9 as out of scope for the same reasons. |
Summary
Adds HTTPS support to the web framework by integrating the
tlslibrary'sTlsServerCtxand non-blockingTlsStream.What changed
Connectionsum type (Plain [TcpStream]/Tls [TlsStream]) with aConnmodule that dispatchesread-append-nb,send-nb,close!, etc. to the underlying stream type. All event-loop helpers now operate onConnectioninstead of rawTcpStream.handle-accept-tls— accepts a TCP connection, detaches the fd from theTcpStream(via a small C helper to avoid double-close), performs the TLS handshake withTlsStream.accept, sets non-blocking mode, and registers the connection.web-tls-resolve-sendfile— for TLS connections, kernelsendfile(2)can't encrypt data, so responses that would use sendfile are transparently converted to in-memory file reads before the write path.App.serve-tls— full TLS server function taking cert and key file paths. Same event loop asservebut useshandle-accept-tlsandTlsServerCtxlifecycle management.defserver-tlsmacro — same concise syntax asdefserverwith addedcert-fileandkey-fileparameters.C helper
web_detach_fd(src/conn_helpers.h) — extracts the fd from aTcpStreamand sets it to-1, preventing the stream from closing the socket on drop when handing ownership toTlsStream.accept.Notes
tls@40e9fef(not the0.1.0tag) because non-blocking I/O was added after the tag.TlsStream.accept) is blocking — this is acceptable for moderate connection rates but could be improved later with async handshake support.timelibrary (TM.tm_zoneconst qualifier) that also exist onmain.Opened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.