74 lines
2.7 KiB
C++
74 lines
2.7 KiB
C++
|
|
//SPDX-License-Identifier: LGPL-3.0-only
|
||
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||
|
|
|
||
|
|
// A body far larger than any socket buffer, in both directions. This is
|
||
|
|
// where short writes, partial reads and off-by-one framing show up: the
|
||
|
|
// request has to survive being split across dozens of send() calls and the
|
||
|
|
// response across dozens of recv() calls.
|
||
|
|
|
||
|
|
import Crafter.Network;
|
||
|
|
import std;
|
||
|
|
using namespace Crafter;
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
int failures = 0;
|
||
|
|
|
||
|
|
void Check(bool condition, std::string_view what) {
|
||
|
|
if (!condition) {
|
||
|
|
std::println("FAIL: {}", what);
|
||
|
|
++failures;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Non-uniform payload, so a mangled offset can't accidentally compare
|
||
|
|
// equal the way a run of identical bytes would.
|
||
|
|
std::string MakePayload(std::size_t size) {
|
||
|
|
std::string payload(size, '\0');
|
||
|
|
for (std::size_t i = 0; i < size; ++i) {
|
||
|
|
payload[i] = static_cast<char>('A' + (i * 7 + i / 251) % 26);
|
||
|
|
}
|
||
|
|
return payload;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
int main() {
|
||
|
|
constexpr std::size_t payloadSize = 10 * 1024 * 1024;
|
||
|
|
const std::string payload = MakePayload(payloadSize);
|
||
|
|
|
||
|
|
std::unordered_map<std::string, std::function<HTTPResponse(const HTTPRequest&)>> routes;
|
||
|
|
routes["/echo"] = [](const HTTPRequest& request) {
|
||
|
|
return CreateResponseHTTP("200", request.body);
|
||
|
|
};
|
||
|
|
routes["/size"] = [](const HTTPRequest& request) {
|
||
|
|
return CreateResponseHTTP("200", std::to_string(request.body.size()));
|
||
|
|
};
|
||
|
|
|
||
|
|
try {
|
||
|
|
ListenerAsyncHTTP1 listener(8092, std::move(routes));
|
||
|
|
ClientHTTP1 client("localhost", 8092);
|
||
|
|
|
||
|
|
HTTPResponse size = client.Send(CreateRequestHTTP("POST", "/size", "localhost", payload));
|
||
|
|
Check(size.status == "200", "large POST status");
|
||
|
|
Check(size.body == std::to_string(payloadSize), "the server received every byte");
|
||
|
|
|
||
|
|
// Same again in both directions, and on the same connection, so a
|
||
|
|
// leftover byte from the previous exchange would desynchronise the
|
||
|
|
// framing and show up here.
|
||
|
|
HTTPResponse echoed = client.Send(CreateRequestHTTP("POST", "/echo", "localhost", payload));
|
||
|
|
Check(echoed.status == "200", "large echo status");
|
||
|
|
Check(echoed.body.size() == payloadSize, "the response body is the right length");
|
||
|
|
Check(echoed.body == payload, "the response body is byte-for-byte the request");
|
||
|
|
Check(listener.listener.AcceptedCount() == 1, "both transfers shared one connection");
|
||
|
|
|
||
|
|
listener.Stop();
|
||
|
|
} catch (const std::exception& error) {
|
||
|
|
std::println("threw: {}", error.what());
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (failures != 0) {
|
||
|
|
std::println("{} check(s) failed", failures);
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|