Commit 367bb9f3 authored by jan.koester's avatar jan.koester
Browse files

test

parent 8f9e9caf
Loading
Loading
Loading
Loading
+93 −21
Changes for test/test_http_request_body_streaming.cpp: 93 added lines, 21 removed lines.
Original line number Diff line number Diff line
// End-to-end test for streamed request-body relaying through a real HttpProxyController:
// client -> proxyplus -> fake upstream, checking the upstream receives exactly the bytes the
// client sent (sha256) for HTTP/1.1 (Content-Length and chunked) and HTTP/2 (h2c, declared and
// undeclared length), and that process.max_request_body_mb answers 413 both up front (declared
// Content-Length) and mid-upload (no declared length).
// client sent (sha256) over every protocol the proxy listens on -- HTTP/1.1 (Content-Length and
// chunked), HTTP/2 via TLS+ALPN (HttpClient) and cleartext h2c (raw client), and HTTP/3 over
// QUIC -- and that process.max_request_body_mb answers 413 both up front (declared
// Content-Length) and mid-upload (no declared length), after which the client still works.
//
// Bare assert() is compiled out here (RelWithDebInfo passes -DNDEBUG), hence check().

@@ -21,6 +22,7 @@
#include <netplus/exception.h>
#include <netplus/random.h>
#include <netplus/socket.h>
#include <netplus/utils/certgen.h>

#include <httppp/hpack.h>
#include <httppp/http.h>
@@ -284,6 +286,52 @@ std::string headerOf(const libhttppp::HttpResponse *res, const std::string &key)
    return hd && hd->getfirstValue() ? hd->getfirstValue()->getvalue() : std::string();
}

libhttppp::HttpClient::BodySource sourceOf(const std::vector<char> &data);

// The same checks over whichever protocol `client` speaks: 6MB with and without a declared
// length reaches the upstream intact; 20MB without a declared length is cut off at the limit
// with 413 mid-upload; and the client can carry on afterwards.
void clientUploads(libhttppp::HttpClient &client, const std::string &proto, size_t okSize,
                   size_t tooBig) {
    for (bool known : {true, false}) {
        std::string label = proto + (known ? " known length 6MB" : " unknown length 6MB");
        try {
            std::vector<char> data = randomBuffer(okSize);
            libhttppp::HttpRequest req;
            req.setRequestURL("/upload");
            std::vector<char> resp = client.RequestStream(
                POSTREQUEST, req, sourceOf(data), known ? int64_t(data.size()) : -1);
            check(client.lastStatusCode() == 200, label + ": 200");
            check(std::string(resp.begin(), resp.end()) == std::to_string(data.size()),
                  label + ": upstream got every byte");
            check(headerOf(client.lastResponse(), "x-sha256") == sha256Hex(data),
                  label + ": sha256 matches at upstream");
        } catch (std::exception &e) {
            check(false, label + ": threw " + e.what());
        }
    }
    try {
        std::vector<char> data = randomBuffer(tooBig);
        libhttppp::HttpRequest req;
        req.setRequestURL("/upload");
        client.RequestStream(POSTREQUEST, req, sourceOf(data), -1);
        check(client.lastStatusCode() == 413, proto + " undeclared length over limit: 413 mid-upload");
    } catch (std::exception &e) {
        check(false, proto + " over limit: threw " + std::string(e.what()));
    }
    try {
        std::vector<char> data = randomBuffer(okSize);
        libhttppp::HttpRequest req;
        req.setRequestURL("/upload");
        client.RequestStream(POSTREQUEST, req, sourceOf(data), int64_t(data.size()));
        check(client.lastStatusCode() == 200 &&
                  headerOf(client.lastResponse(), "x-sha256") == sha256Hex(data),
              proto + " same client still works after the 413");
    } catch (std::exception &e) {
        check(false, proto + " after 413: threw " + std::string(e.what()));
    }
}

libhttppp::HttpClient::BodySource sourceOf(const std::vector<char> &data) {
    auto off = std::make_shared<size_t>(0);
    return [&data, off](char *buf, size_t len) -> size_t {
@@ -315,6 +363,29 @@ int main() {
    const int proxyPort = boundPort(*proxySock);
    proxySock->listen();

    // TLS (h2 via ALPN) and QUIC (h3) listeners on the same proxy, with a throwaway
    // self-signed certificate (the clients here don't verify).
    netplus::CertGenConfig certCfg;
    certCfg.commonName = "127.0.0.1";
    certCfg.subjectAltNames = {"127.0.0.1", "localhost"};
    netplus::CertKeyPair kp;
    if (!netplus::generateSelfSignedCert(certCfg, kp) ||
        !netplus::writePemFile(std::string(tmpl) + "/cert.pem", kp.certPem) ||
        !netplus::writePemFile(std::string(tmpl) + "/key.pem", kp.keyPem)) {
        std::cerr << "certificate generation failed" << std::endl;
        return 1;
    }
    netplus::ssl::CertificateBundle bundle;
    if (!bundle.loadFromFile(std::string(tmpl) + "/cert.pem", std::string(tmpl) + "/key.pem")) {
        std::cerr << "loading the generated certificate failed" << std::endl;
        return 1;
    }
    std::map<std::string, netplus::ssl::CertificateBundle> certs{{"127.0.0.1", bundle},
                                                                 {"localhost", bundle}};
    constexpr int kTlsPort = 19661, kQuicPort = 19662;
    auto tlsSock = 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);

    store::ConfigStore configStore(tmpl);
    store::HttpRouteRecord route;
    route.id = "body-stream-test";
@@ -330,7 +401,8 @@ int main() {
    http::UpstreamConnectionPool pool(8, 60);

    Upstream upstream({upSock.get()});
    httpproxy::HttpProxyController proxy({proxySock.get()}, "test", /*tlsActive=*/false,
    httpproxy::HttpProxyController proxy({proxySock.get(), tlsSock.get(), quicSock.get()},
                                         "test", /*tlsActive=*/false,
                                         configStore, authService, pluginManager, loadBalancer,
                                         pool, nullptr, 0, 30, 256, 256, kLimitMb);
    netplus::event::Running = true;
@@ -343,23 +415,7 @@ int main() {
        {
            libhttppp::HttpClient client(
                libhttppp::HttpUrl("http://127.0.0.1:" + std::to_string(proxyPort) + "/"), 1, 30);
            for (bool known : {true, false}) {
                std::string label = known ? "H1 content-length 6MB" : "H1 chunked 6MB";
                try {
                    std::vector<char> data = randomBuffer(kOk);
                    libhttppp::HttpRequest req;
                    req.setRequestURL("/upload");
                    std::vector<char> resp = client.RequestStream(
                        POSTREQUEST, req, sourceOf(data), known ? int64_t(data.size()) : -1);
                    check(client.lastStatusCode() == 200, label + ": 200");
                    check(std::string(resp.begin(), resp.end()) == std::to_string(data.size()),
                          label + ": upstream got every byte");
                    check(headerOf(client.lastResponse(), "x-sha256") == sha256Hex(data),
                          label + ": sha256 matches at upstream");
                } catch (std::exception &e) {
                    check(false, label + ": threw " + e.what());
                }
            }
            clientUploads(client, "H1", kOk, kTooBig);
        }
        {
            // Declared length over the limit: 413 before a single body byte is read.
@@ -381,6 +437,22 @@ int main() {
            sock.close();
        }

        std::cout << "-- HTTP/2 (TLS, HttpClient) --" << std::endl;
        {
            libhttppp::HttpClient client(
                libhttppp::HttpUrl("https://127.0.0.1:" + std::to_string(kTlsPort) + "/"), 2, 30);
            clientUploads(client, "H2/TLS", kOk, kTooBig);
        }

        std::cout << "-- HTTP/3 --" << std::endl;
        {
            libhttppp::HttpClient client(
                libhttppp::HttpUrl("https://127.0.0.1:" + std::to_string(kQuicPort) + "/",
                                   /*http3=*/true),
                3, 30);
            clientUploads(client, "H3", kOk, kTooBig);
        }

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