//SPDX-License-Identifier: LGPL-3.0-only //SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // A server is only as good as its worst peer. This drives ListenerHTTP1 // with many concurrent clients and then with peers that behave badly — // vanishing mid-request, sending garbage, opening and dropping connections, // stalling forever — and requires that it keeps serving correctly // throughout and still shuts down promptly at the end. import Crafter.Network; import std; using namespace Crafter; namespace { std::atomic failures{0}; void Check(bool condition, std::string_view what) { if (!condition) { std::println("FAIL: {}", what); failures.fetch_add(1); } } constexpr std::uint16_t port = 8095; // Raw socket, no HTTP client in the way, so the test can send things a // well-behaved client never would. void SendRaw(std::string_view bytes, bool waitForReply) { ClientTCP socket("localhost", port); if (!bytes.empty()) socket.Send(bytes.data(), static_cast(bytes.size())); if (waitForReply) { try { (void)socket.RecieveSync(); } catch (const std::exception&) { // A dropped connection is an acceptable answer to nonsense. } } } } int main() { std::unordered_map> routes; routes["/work"] = [](const HTTPRequest& request) { return CreateResponseHTTP("200", request.body.empty() ? std::string("empty") : request.body); }; try { ListenerAsyncHTTP1 listener(port, std::move(routes)); // Keep the stalled-peer case quick; the defaults are measured in // tens of seconds, which is right for a real server and wrong for a // test. listener.listener.requestTimeout = std::chrono::milliseconds(300); listener.listener.keepAliveTimeout = std::chrono::milliseconds(500); // ── Many clients at once, each reusing its own connection ────── constexpr int clientCount = 24; constexpr int requestsPerClient = 8; std::atomic completed{0}; std::vector clients; for (int i = 0; i < clientCount; ++i) { clients.emplace_back([i, &completed] { try { ClientHTTP1 client("localhost", port); for (int request = 0; request < requestsPerClient; ++request) { const std::string payload = std::format("client-{}-request-{}", i, request); HTTPResponse response = client.Send( CreateRequestHTTP("POST", "/work", "localhost", payload)); Check(response.status == "200", "concurrent request status"); Check(response.body == payload, "concurrent request body matches its sender"); completed.fetch_add(1); } } catch (const std::exception& error) { std::println("client threw: {}", error.what()); failures.fetch_add(1); } }); } for (auto& client : clients) client.join(); Check(completed.load() == clientCount * requestsPerClient, "every concurrent request finished"); Check(listener.listener.AcceptedCount() == clientCount, "each client used exactly one connection"); // ── Peers that misbehave ────────────────────────────────────── SendRaw("", false); // connect, say nothing, leave SendRaw("GET /work HTTP/1.1\r\nHost: x\r\n", false); // headers cut short SendRaw("POST /work HTTP/1.1\r\nHost: x\r\nContent-Length: 100\r\n\r\nshort", false); SendRaw("not http at all\r\n\r\n", true); // garbage start line SendRaw("GET / HTTP/1.1\r\nHost : x\r\n\r\n", true); // smuggling-shaped header SendRaw(std::string("GET /") + std::string(16 * 1024, 'z') + " HTTP/1.1\r\n\r\n", true); // A peer that opens a request and then just sits there must be cut // loose by the request timeout rather than holding its thread. { ClientTCP stalled("localhost", port); const std::string partial = "POST /work HTTP/1.1\r\nHost: x\r\nContent-Length: 10\r\n\r\nab"; stalled.Send(partial.data(), static_cast(partial.size())); const auto start = std::chrono::steady_clock::now(); try { (void)stalled.RecieveUntilCloseSync(); } catch (const std::exception&) {} const auto elapsed = std::chrono::steady_clock::now() - start; Check(elapsed < std::chrono::seconds(5), "a stalled peer is timed out, not waited on"); } // ── Still healthy afterwards ────────────────────────────────── ClientHTTP1 client("localhost", port); HTTPResponse response = client.Send(CreateRequestHTTP("POST", "/work", "localhost", std::string("still here"))); Check(response.status == "200", "the server survived the abuse"); Check(response.body == "still here", "and still answers correctly"); // Shutdown has to finish quickly even with connections open. const auto start = std::chrono::steady_clock::now(); listener.Stop(); const auto elapsed = std::chrono::steady_clock::now() - start; Check(elapsed < std::chrono::seconds(5), "Stop() returns promptly with a connection still open"); } catch (const std::exception& error) { std::println("threw: {}", error.what()); return 1; } if (failures.load() != 0) { std::println("{} check(s) failed", failures.load()); return 1; } return 0; }