feat(tls): add a libssl TLS transport and an https:// HTTP/1.1 stack
HTTP/1.1 was plaintext-only, which left `https://` to either an HTTP/3
listener or a terminating proxy in front. Neither helps the callers this
stack exists for — curl scripts, CI tooling, old proxies — so wrap the
transport in libssl instead.
Two new partitions:
:Stream a ByteStream with per-call deadlines on both directions, plus
the plaintext socket implementation. The HTTP/1.1 client and
listener now hold a ByteStream& and never learn which
transport they have, which is what lets one code path serve
both schemes.
:TLS TLSContext/TLSStream over OpenSSL 3, with credentials for both
roles: chain and hostname verification on by default, private
trust anchors, client certificates, mutual TLS, ALPN, and an
in-process self-signed certificate for development.
Both descriptors go non-blocking and every read and write is driven by
poll() against a deadline. That is required for TLS — a blocking
descriptor cannot express a handshake timeout — and it means a plaintext
write can now time out too, instead of parking forever against a peer
that stopped reading.
ClientHTTP1 and ListenerHTTP1 gain credential-taking constructors; the
existing ones still speak http://. The listener handshakes on the
connection's own thread, so a peer that stalls mid-handshake costs one
thread rather than the accept loop, and a failed handshake is counted
rather than logged — on a public port it is ordinary traffic.
MessageParser gains SetDefaultScheme so origin-form targets report the
scheme the transport actually used; handlers shared with ListenerHTTP now
see the same "https" they would over HTTP/3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e1bb116b2d
commit
9c22cbe09e
11 changed files with 1299 additions and 99 deletions
|
|
@ -299,6 +299,14 @@ namespace Crafter::HTTP1 {
|
|||
headRequest = EqualsIgnoreCase(requestMethod, "HEAD");
|
||||
}
|
||||
|
||||
// Scheme reported for origin-form request targets, which carry none of
|
||||
// their own. Only the transport knows — "https" once TLS is
|
||||
// terminating the connection. An absolute-form target still wins, as
|
||||
// it names its own scheme. Survives Reset().
|
||||
void SetDefaultScheme(std::string scheme) {
|
||||
defaultScheme = std::move(scheme);
|
||||
}
|
||||
|
||||
void Feed(const char* data, std::size_t size) {
|
||||
if (size != 0) buffer.append(data, size);
|
||||
Advance();
|
||||
|
|
@ -340,7 +348,7 @@ namespace Crafter::HTTP1 {
|
|||
HTTPRequest request;
|
||||
request.method = std::move(method);
|
||||
request.path = std::move(path);
|
||||
request.scheme = scheme.empty() ? std::string("http") : std::move(scheme);
|
||||
request.scheme = scheme.empty() ? defaultScheme : std::move(scheme);
|
||||
// Absolute-form targets carry their own authority and win over
|
||||
// Host (RFC 9112 §3.2.2); otherwise Host supplies it. Either way
|
||||
// it lives in the named field, matching the HTTP/3 shape, so it
|
||||
|
|
@ -759,6 +767,10 @@ namespace Crafter::HTTP1 {
|
|||
std::string method;
|
||||
std::string path;
|
||||
std::string scheme;
|
||||
// Not cleared by Reset(): the transport does not change under a
|
||||
// connection, so it is told to the parser once and applies to every
|
||||
// request on it.
|
||||
std::string defaultScheme = "http";
|
||||
std::string authority;
|
||||
std::string version;
|
||||
std::string status;
|
||||
|
|
|
|||
Loading…
Reference in a new issue