Loading src/http/HttpProxyServer.cpp +12 −20 Changes for src/http/HttpProxyServer.cpp: 12 added lines, 20 removed lines. Original line number Diff line number Diff line Loading @@ -117,14 +117,16 @@ HttpProxyController::HttpProxyController(std::vector<netplus::socket *> serverSo http::UpstreamConnectionPool &connectionPool, tls::AcmeChallengeStore *acmeChallenges, int idleTimeoutSeconds) // Keep H2 dispatch disabled for now. Enabling libhttppp's H2 offload pool in the deployed // stack makes an otherwise healthy proxied response remain queued after RequestEvent // returns, while synchronous H2 lets a frontend request burst occupy every listener worker // and prevents even new TLS handshakes. The constructor body therefore also removes h2 // from TLS ALPN instead of merely leaving these requests synchronous. Clients use the // independently-offloaded H1 path initially and can still discover/use H3 via Alt-Svc. // H2 dispatch now runs through the same offload pool as H1 (see shouldOffloadH2Dispatch // below): both RequestEvent calls do real backend I/O, so both need off the shared // epoll/kqueue listener workers to avoid one bursty connection starving new TLS // handshakes. This was unsafe before libhttppp's H2 offload lambda gained its own // try/catch around RequestEvent (see httpd.cpp's _dispatchH2Stream) -- without it, a // backend exception left the stream queued forever with no HEADERS/DATA/RST_STREAM ever // sent. ALPN is left at HttpEvent's own default (prefer h2, fall back to http/1.1) below; // H3 stays independent via QUIC/Alt-Svc regardless. : libhttppp::HttpEvent(serverSockets, /*timeout=*/1000, /*h2OffloadThreads=*/0, /*h2OffloadThreads=*/(std::max<size_t>)(4, std::thread::hardware_concurrency() * 2), idleTimeoutSeconds, /*h1OffloadThreads=*/(std::max<size_t>)(4, std::thread::hardware_concurrency() * 2)) , _listenerLabel(std::move(listenerLabel)) Loading @@ -135,20 +137,10 @@ HttpProxyController::HttpProxyController(std::vector<netplus::socket *> serverSo , _loadBalancer(loadBalancer) , _connectionPool(connectionPool) , _acmeChallenges(acmeChallenges) { // HttpEvent advertises h2 by default on every TLS-over-TCP listener. Do not advertise a // protocol whose dispatch mode is intentionally disabled above: browsers otherwise choose // h2 and can starve the shared listener workers before their WebSocket connection is even // accepted. QUIC sockets are separate and retain their h3 ALPN configuration. for (auto *sock : serverSockets) { if (auto *tlsSock = dynamic_cast<netplus::ssl *>(sock)) { tlsSock->setAlpnSelectCallback([](const std::vector<std::string> &protocols) { for (const auto &protocol : protocols) { if (protocol == "http/1.1") return protocol; } return std::string{}; }); } } bool HttpProxyController::shouldOffloadH2Dispatch(libhttppp::HttpRequest &, uint32_t) const { return true; } bool HttpProxyController::tryServeAcmeChallenge(libhttppp::HttpRequest &curreq, Loading src/http/HttpProxyServer.h +7 −0 Changes for src/http/HttpProxyServer.h: 7 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -56,6 +56,13 @@ public: return true; } // H2 analogue of the above: every route here does a real backend round-trip, so H2 // streams need the same offload as H1 to avoid a bursty multiplexed connection // occupying every shared listener worker. Safe now that libhttppp's H2 offload lambda // has its own try/catch around RequestEvent (see httpd.cpp's _dispatchH2Stream) -- before // that fix, a backend exception left the stream queued forever with no response ever sent. bool shouldOffloadH2Dispatch(libhttppp::HttpRequest &, uint32_t) const override; private: struct AuthOutcome { bool authenticated = false; Loading Loading
src/http/HttpProxyServer.cpp +12 −20 Changes for src/http/HttpProxyServer.cpp: 12 added lines, 20 removed lines. Original line number Diff line number Diff line Loading @@ -117,14 +117,16 @@ HttpProxyController::HttpProxyController(std::vector<netplus::socket *> serverSo http::UpstreamConnectionPool &connectionPool, tls::AcmeChallengeStore *acmeChallenges, int idleTimeoutSeconds) // Keep H2 dispatch disabled for now. Enabling libhttppp's H2 offload pool in the deployed // stack makes an otherwise healthy proxied response remain queued after RequestEvent // returns, while synchronous H2 lets a frontend request burst occupy every listener worker // and prevents even new TLS handshakes. The constructor body therefore also removes h2 // from TLS ALPN instead of merely leaving these requests synchronous. Clients use the // independently-offloaded H1 path initially and can still discover/use H3 via Alt-Svc. // H2 dispatch now runs through the same offload pool as H1 (see shouldOffloadH2Dispatch // below): both RequestEvent calls do real backend I/O, so both need off the shared // epoll/kqueue listener workers to avoid one bursty connection starving new TLS // handshakes. This was unsafe before libhttppp's H2 offload lambda gained its own // try/catch around RequestEvent (see httpd.cpp's _dispatchH2Stream) -- without it, a // backend exception left the stream queued forever with no HEADERS/DATA/RST_STREAM ever // sent. ALPN is left at HttpEvent's own default (prefer h2, fall back to http/1.1) below; // H3 stays independent via QUIC/Alt-Svc regardless. : libhttppp::HttpEvent(serverSockets, /*timeout=*/1000, /*h2OffloadThreads=*/0, /*h2OffloadThreads=*/(std::max<size_t>)(4, std::thread::hardware_concurrency() * 2), idleTimeoutSeconds, /*h1OffloadThreads=*/(std::max<size_t>)(4, std::thread::hardware_concurrency() * 2)) , _listenerLabel(std::move(listenerLabel)) Loading @@ -135,20 +137,10 @@ HttpProxyController::HttpProxyController(std::vector<netplus::socket *> serverSo , _loadBalancer(loadBalancer) , _connectionPool(connectionPool) , _acmeChallenges(acmeChallenges) { // HttpEvent advertises h2 by default on every TLS-over-TCP listener. Do not advertise a // protocol whose dispatch mode is intentionally disabled above: browsers otherwise choose // h2 and can starve the shared listener workers before their WebSocket connection is even // accepted. QUIC sockets are separate and retain their h3 ALPN configuration. for (auto *sock : serverSockets) { if (auto *tlsSock = dynamic_cast<netplus::ssl *>(sock)) { tlsSock->setAlpnSelectCallback([](const std::vector<std::string> &protocols) { for (const auto &protocol : protocols) { if (protocol == "http/1.1") return protocol; } return std::string{}; }); } } bool HttpProxyController::shouldOffloadH2Dispatch(libhttppp::HttpRequest &, uint32_t) const { return true; } bool HttpProxyController::tryServeAcmeChallenge(libhttppp::HttpRequest &curreq, Loading
src/http/HttpProxyServer.h +7 −0 Changes for src/http/HttpProxyServer.h: 7 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -56,6 +56,13 @@ public: return true; } // H2 analogue of the above: every route here does a real backend round-trip, so H2 // streams need the same offload as H1 to avoid a bursty multiplexed connection // occupying every shared listener worker. Safe now that libhttppp's H2 offload lambda // has its own try/catch around RequestEvent (see httpd.cpp's _dispatchH2Stream) -- before // that fix, a backend exception left the stream queued forever with no response ever sent. bool shouldOffloadH2Dispatch(libhttppp::HttpRequest &, uint32_t) const override; private: struct AuthOutcome { bool authenticated = false; Loading