Commit eaf68b43 authored by jan.koester's avatar jan.koester
Browse files

test

parent ddc744f0
Loading
Loading
Loading
Loading
+1 −0
Changes for src/httpd.cpp: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -2238,6 +2238,7 @@ bool libhttppp::HttpEvent::_startH3BodyStream(netplus::quic &q, uint64_t stream_
        // Whatever of the body RequestEvent didn't read (it answered first) is unwanted:
        // STOP_SENDING(H3_NO_ERROR) after the response, RFC 9114 §4.1.2.
        const bool unread = !reader->finished();
        std::cerr << "[DBG-H3] finish sid=" << stream_id << " unread=" << unread << " resvalid=" << (r->getHeaderData(":res-valid") != nullptr) << std::endl;
        reader->abort();
        r->_bodyStream.reset();
        try {
+86 −2
Changes for test/httpd_request_body_stream_test.cpp: 86 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -38,6 +38,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//     consumer (backpressure instead of overrun), and a handler answering before reading
//     the body, followed by more uploads on the same connection than one connection window
//     holds (credit must still be returned for DATA of the abandoned stream).
//   - H3 (real QUIC, HttpClient::RequestStream over HTTP/3): known and unknown length up to
//     20MB (> the 16MB QUIC stream window, so stream credit must follow consumption), a slow
//     consumer, an early answer followed by more uploads on the same connection; plus the
//     non-streamed H3 paths on top of QUIC pull dispatch: a 20MB buffered upload (credit
//     must still be returned as the server pulls) and onH3StreamHeaders/onH3DataChunk.
//   - A buffered POST still works when shouldStreamRequestBody() says no.

#include <chrono>
@@ -58,6 +63,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "checksum_common.h"
#include "hpack.h"
#include "qpack.h"
#include "https_certs.h"
#include "https_ca_cert.h"
#include "raw_tcp_client.h"
@@ -94,7 +100,34 @@ public:
        return true;
    }
    bool shouldStreamRequestBody(libhttppp::HttpRequest &req) const override {
        return req.getRequestURL() != "/buffered";
        return req.getRequestURL() != "/buffered" && req.getRequestURL() != "/h3cb";
    }

    // "/h3cb" over H3: the pre-existing callback ingestion API, answered with "<size>:<sha256>".
    bool onH3StreamHeaders(netplus::socket *sock, uint64_t sid,
                           const std::vector<libhttppp::qpack::HeaderField> &headers) override {
        for (auto &h : headers) {
            if (h.name == ":path" && h.value == "/h3cb") {
                std::lock_guard<std::mutex> lk(_cbMtx);
                _cbBodies[{sock, sid}].clear();
                return true;
            }
        }
        return false;
    }
    void onH3DataChunk(netplus::socket *sock, uint64_t sid, const char *data, size_t len,
                       bool fin) override {
        std::vector<char> done;
        {
            std::lock_guard<std::mutex> lk(_cbMtx);
            auto &b = _cbBodies[{sock, sid}];
            if (len) b.insert(b.end(), data, data + len);
            if (!fin) return;
            done = std::move(b);
            _cbBodies.erase({sock, sid});
        }
        sendH3StreamResponse(sock, sid, 200, "text/plain",
                             std::to_string(done.size()) + ":" + sha256Hex(done));
    }

    void RequestEvent(libhttppp::HttpRequest &req, const int, ULONG_PTR) override {
@@ -127,6 +160,10 @@ public:
        std::string n = std::to_string(body.size());
        res.send(req, n, static_cast<int>(n.size()));
    }

private:
    std::mutex _cbMtx;
    std::map<std::pair<netplus::socket *, uint64_t>, std::vector<char>> _cbBodies;
};

netplus::ssl::CertificateBundle buildTestCertBundle() {
@@ -370,6 +407,7 @@ std::string recvSome(netplus::tcp &sock) {
int main() {
    std::cout << "=== Streamed request body test ===" << std::endl;
    const int kTlsPort = 19651;
    const int kQuicPort = 19652;
    const size_t kBig = 20u * 1024 * 1024; // > the 16MB H2 stream window

    try {
@@ -384,8 +422,10 @@ int main() {
        certs["127.0.0.1"] = bundle;
        auto sslSock = std::make_unique<netplus::ssl>(certs, "127.0.0.1", kTlsPort, 16, -1);

        auto quicSock = std::make_unique<netplus::quic>(certs, "127.0.0.1", kQuicPort, 64, -1);

        auto server = std::make_unique<StreamEvent>(
            std::vector<netplus::socket *>{tcpSock.get(), sslSock.get()});
            std::vector<netplus::socket *>{tcpSock.get(), sslSock.get(), quicSock.get()});
        std::thread serverThread([s = server.get()] { s->runEventloop(); });
        ServerThreadGuard guard{&serverThread};
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
@@ -441,6 +481,50 @@ int main() {
                           "TLS buffered fallback");
        }

        // ---------------- HTTP/3 ----------------
        std::cout << "-- HTTP/3 --" << std::endl;
        {
            libhttppp::HttpUrl url("https://127.0.0.1:" + std::to_string(kQuicPort) + "/",
                                   /*http3=*/true);
            libhttppp::HttpClient client(url, /*vers=*/3, /*timeoutSec=*/20);
            for (size_t size : {size_t(1), size_t(65536), size_t(1048576), kBig}) {
                std::vector<char> data = randomBuffer(size);
                std::string s = std::to_string(size);
                streamedUpload(client, "/sum", data, true, true, "H3 known length size=" + s);
                streamedUpload(client, "/sum", data, false, true, "H3 unknown length size=" + s);
            }
            streamedUpload(client, "/slowsum", randomBuffer(kBig), true, true,
                           "H3 slow consumer (backpressure) 20MB");
            try {
                std::vector<char> data = randomBuffer(1048576);
                libhttppp::HttpRequest req;
                req.setRequestURL("/early");
                client.RequestStream(POSTREQUEST, req, sourceOf(data, 50000),
                                     static_cast<int64_t>(data.size()));
                check(client.lastStatusCode() == 413, "H3 early response before body read");
            } catch (std::exception &e) {
                check(false, std::string("H3 early response: threw ") + e.what());
            }
            for (int i = 0; i < 2; ++i)
                streamedUpload(client, "/sum", randomBuffer(8u * 1024 * 1024), true, true,
                               "H3 same connection after early response #" + std::to_string(i));
            streamedUpload(client, "/buffered", randomBuffer(kBig), true, false,
                           "H3 buffered 20MB (credit returned as pulled)");
            try {
                std::vector<char> data = randomBuffer(kBig);
                libhttppp::HttpRequest req;
                req.setRequestURL("/h3cb");
                std::vector<char> resp = client.RequestStream(POSTREQUEST, req,
                                                              sourceOf(data, 50000),
                                                              static_cast<int64_t>(data.size()));
                check(std::string(resp.begin(), resp.end()) ==
                          std::to_string(data.size()) + ":" + sha256Hex(data),
                      "H3 onH3DataChunk callback ingestion 20MB");
            } catch (std::exception &e) {
                check(false, std::string("H3 onH3DataChunk: threw ") + e.what());
            }
        }

        // ---------------- HTTP/2 (h2c) ----------------
        std::cout << "-- HTTP/2 (h2c) --" << std::endl;
        {