2026-07-22 17:55:45 +02:00
|
|
|
//SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
2025-11-02 15:00:53 +01:00
|
|
|
|
|
|
|
|
export module Crafter.Network:HTTP;
|
|
|
|
|
import std;
|
|
|
|
|
|
|
|
|
|
namespace Crafter {
|
2026-05-07 00:06:44 +02:00
|
|
|
// HTTP/3 request as carried over a QUIC bidirectional stream. The four
|
|
|
|
|
// pseudo-headers (method/scheme/authority/path) are split out as named
|
|
|
|
|
// fields rather than living in the headers map, because RFC 9114 forbids
|
|
|
|
|
// them from appearing in the regular header section and this shape makes
|
|
|
|
|
// route dispatch and request construction cleaner. `headers` keys are
|
|
|
|
|
// expected lowercase; HTTP/3 forbids uppercase characters in field names.
|
2025-11-02 15:00:53 +01:00
|
|
|
export struct HTTPRequest {
|
|
|
|
|
std::string method;
|
2026-05-07 00:06:44 +02:00
|
|
|
std::string scheme = "https";
|
|
|
|
|
std::string authority;
|
|
|
|
|
std::string path = "/";
|
2025-11-02 15:00:53 +01:00
|
|
|
std::unordered_map<std::string, std::string> headers;
|
|
|
|
|
std::string body;
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-07 00:06:44 +02:00
|
|
|
// HTTP/3 response. `status` is the numeric three-digit code as a string
|
|
|
|
|
// (e.g. "200") — HTTP/3 has no reason phrase. `headers` keys are expected
|
|
|
|
|
// lowercase.
|
2025-11-02 15:00:53 +01:00
|
|
|
export struct HTTPResponse {
|
2026-05-07 00:06:44 +02:00
|
|
|
std::string status = "200";
|
2025-11-02 15:00:53 +01:00
|
|
|
std::unordered_map<std::string, std::string> headers;
|
|
|
|
|
std::string body;
|
|
|
|
|
};
|
|
|
|
|
|
feat(http1): dispatch unmatched paths to an optional fallback handler
The route map only answers paths that are known when the listener is
built. A route with an unbounded segment — /shop/<slug>, /order/<token>,
/posts/<id> — cannot be pre-registered: the token space is unbounded and
the product set changes while the server runs. Every such request became
a synthetic 404 that the application never got to see.
Add one optional member, called for anything `routes` missed, with the
full target still in request.path. Precedence is exact path, then the
query-stripped path, then fallback, then the 404 as before, so a
default-constructed listener behaves byte-identically and no call site
changes.
A hook rather than a pattern syntax: consumers that already have a
router — one shared between a wasm frontend and the server, so a URL
cannot mean different things to a crawler and to the app — keep using
it, and there is no second route table to disagree with the first.
PathWithoutQuery moves out of this file into :HTTP as an exported
PathWithoutQueryHTTP, since ListenerHTTP now needs the same split and a
fallback handler almost always does too.
ListenerAsyncHTTP1 starts accepting inside its constructor, so assigning
`listener.fallback` afterwards would race the accept loop; it gets a
constructor overload that installs the fallback before the thread
starts.
2026-07-28 19:41:57 +00:00
|
|
|
// Everything in a request target up to '?'. Both listeners dispatch on
|
|
|
|
|
// this when the full target is not a registered route, so `/thing?x=1`
|
|
|
|
|
// reaches the handler registered for `/thing`: browsers append query
|
|
|
|
|
// strings freely and registering every variant is not a workable API.
|
|
|
|
|
// Exported because a fallback handler generally needs the same split.
|
|
|
|
|
export inline std::string_view PathWithoutQueryHTTP(std::string_view target) {
|
|
|
|
|
return target.substr(0, target.find('?'));
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-07 00:06:44 +02:00
|
|
|
export inline HTTPRequest CreateRequestHTTP(std::string method, std::string path, std::string authority) {
|
|
|
|
|
HTTPRequest r;
|
|
|
|
|
r.method = std::move(method);
|
|
|
|
|
r.path = std::move(path);
|
|
|
|
|
r.authority = std::move(authority);
|
|
|
|
|
return r;
|
2025-11-02 15:00:53 +01:00
|
|
|
}
|
2026-05-07 00:06:44 +02:00
|
|
|
|
|
|
|
|
export inline HTTPRequest CreateRequestHTTP(std::string method, std::string path, std::string authority,
|
|
|
|
|
std::unordered_map<std::string, std::string> headers) {
|
|
|
|
|
HTTPRequest r;
|
|
|
|
|
r.method = std::move(method);
|
|
|
|
|
r.path = std::move(path);
|
|
|
|
|
r.authority = std::move(authority);
|
|
|
|
|
r.headers = std::move(headers);
|
|
|
|
|
return r;
|
2025-11-02 15:00:53 +01:00
|
|
|
}
|
2026-05-07 00:06:44 +02:00
|
|
|
|
|
|
|
|
export inline HTTPRequest CreateRequestHTTP(std::string method, std::string path, std::string authority,
|
|
|
|
|
std::string body) {
|
|
|
|
|
HTTPRequest r;
|
|
|
|
|
r.method = std::move(method);
|
|
|
|
|
r.path = std::move(path);
|
|
|
|
|
r.authority = std::move(authority);
|
|
|
|
|
r.body = std::move(body);
|
|
|
|
|
return r;
|
2025-11-02 15:00:53 +01:00
|
|
|
}
|
2026-05-07 00:06:44 +02:00
|
|
|
|
|
|
|
|
export inline HTTPRequest CreateRequestHTTP(std::string method, std::string path, std::string authority,
|
|
|
|
|
std::unordered_map<std::string, std::string> headers,
|
|
|
|
|
std::string body) {
|
|
|
|
|
HTTPRequest r;
|
|
|
|
|
r.method = std::move(method);
|
|
|
|
|
r.path = std::move(path);
|
|
|
|
|
r.authority = std::move(authority);
|
|
|
|
|
r.headers = std::move(headers);
|
|
|
|
|
r.body = std::move(body);
|
|
|
|
|
return r;
|
2025-11-02 15:00:53 +01:00
|
|
|
}
|
|
|
|
|
|
2026-05-07 00:06:44 +02:00
|
|
|
export inline HTTPResponse CreateResponseHTTP(std::string status) {
|
|
|
|
|
HTTPResponse r;
|
|
|
|
|
r.status = std::move(status);
|
|
|
|
|
return r;
|
|
|
|
|
}
|
2025-11-02 15:00:53 +01:00
|
|
|
|
2026-05-07 00:06:44 +02:00
|
|
|
export inline HTTPResponse CreateResponseHTTP(std::string status,
|
|
|
|
|
std::unordered_map<std::string, std::string> headers) {
|
|
|
|
|
HTTPResponse r;
|
|
|
|
|
r.status = std::move(status);
|
|
|
|
|
r.headers = std::move(headers);
|
|
|
|
|
return r;
|
|
|
|
|
}
|
2025-11-02 15:00:53 +01:00
|
|
|
|
2026-05-07 00:06:44 +02:00
|
|
|
export inline HTTPResponse CreateResponseHTTP(std::string status, std::string body) {
|
|
|
|
|
HTTPResponse r;
|
|
|
|
|
r.status = std::move(status);
|
|
|
|
|
r.body = std::move(body);
|
|
|
|
|
return r;
|
|
|
|
|
}
|
2025-11-02 15:00:53 +01:00
|
|
|
|
2026-05-07 00:06:44 +02:00
|
|
|
export inline HTTPResponse CreateResponseHTTP(std::string status,
|
|
|
|
|
std::unordered_map<std::string, std::string> headers,
|
|
|
|
|
std::string body) {
|
|
|
|
|
HTTPResponse r;
|
|
|
|
|
r.status = std::move(status);
|
|
|
|
|
r.headers = std::move(headers);
|
|
|
|
|
r.body = std::move(body);
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
}
|