Loading src/http.cpp +126 −54 Original line number Diff line number Diff line Loading @@ -285,7 +285,6 @@ void libhttppp::HttpClient::resetConnection(){ _h2PrefaceSent = false; _h2NextStreamId = 1; _h2Decoder.reset(); _h3ControlSent = false; if (_url.getProtocol() == HttpUrl::HTTP3) { auto q = std::make_unique<netplus::quic>(); q->connect(_url.getHost(), _url.getPort(), false); Loading Loading @@ -360,7 +359,6 @@ void libhttppp::HttpClient::reconnect(){ _h2PrefaceSent = false; _h2NextStreamId = 1; _h2Decoder.reset(); _h3ControlSent = false; if (_url.getProtocol() == HttpUrl::HTTP3) { auto q = std::make_unique<netplus::quic>(); q->connect(_url.getHost(), _url.getPort(), false); Loading Loading @@ -750,6 +748,7 @@ static std::string h2cBuildFrame(uint8_t type, uint8_t flags, uint32_t stream_id static std::string h2cBuildSettings(); static std::string h2cBuildSettingsAck(); static std::string h2cBuildWindowUpdate(uint32_t stream_id, uint32_t increment); static std::string h2cConnectionWindowBoost(); // ── Streaming API ────────────────────────────────────────────────────── Loading Loading @@ -794,26 +793,6 @@ libhttppp::HttpResponse libhttppp::HttpClient::GetStream(libhttppp::HttpRequest // ── HTTP/3 (QUIC) streaming path ── if (auto *q = dynamic_cast<netplus::quic*>(_cltsock.get())) { // Send HTTP/3 control streams once per connection if (!_h3ControlSent) { uint64_t ctrl_id = q->openStream(false); std::vector<uint8_t> ctrl_payload; ctrl_payload.push_back(0x00); ctrl_payload.push_back(0x04); ctrl_payload.push_back(0x00); q->sendStreamData(ctrl_id, ctrl_payload, false); uint64_t enc_id = q->openStream(false); std::vector<uint8_t> enc_payload = {0x02}; q->sendStreamData(enc_id, enc_payload, false); uint64_t dec_id = q->openStream(false); std::vector<uint8_t> dec_payload = {0x03}; q->sendStreamData(dec_id, dec_payload, false); _h3ControlSent = true; } std::string path = nreq.getRequestURL(); if (path.empty()) path = _url.getPath(); std::string scheme = (_url.getProtocol() == HttpUrl::HTTP) ? "http" : "https"; Loading Loading @@ -966,6 +945,7 @@ libhttppp::HttpResponse libhttppp::HttpClient::GetStream(libhttppp::HttpRequest if (!_h2PrefaceSent) { std::string preface(H2C_CLIENT_PREFACE, H2C_CLIENT_PREFACE_LEN); preface += h2cBuildSettings(); preface += h2cConnectionWindowBoost(); _sendAll(preface); _h2PrefaceSent = true; } Loading Loading @@ -1946,15 +1926,33 @@ static std::string h2cBuildFrame(uint8_t type, uint8_t flags, uint32_t stream_id return f; } // RFC 7540: SETTINGS_INITIAL_WINDOW_SIZE — use 16 MB so both large // downloads and uploads don't stall on the default 65535-byte window. static constexpr uint32_t H2C_INITIAL_WINDOW_SIZE = 16u * 1024 * 1024; static std::string h2cBuildSettings() { // Send empty SETTINGS (accept defaults) return h2cBuildFrame(H2C_FRAME_SETTINGS, 0, 0, ""); // SETTINGS_INITIAL_WINDOW_SIZE (0x04) = H2C_INITIAL_WINDOW_SIZE std::string p(6, '\0'); p[0] = 0x00; p[1] = 0x04; p[2] = static_cast<char>((H2C_INITIAL_WINDOW_SIZE >> 24) & 0xff); p[3] = static_cast<char>((H2C_INITIAL_WINDOW_SIZE >> 16) & 0xff); p[4] = static_cast<char>((H2C_INITIAL_WINDOW_SIZE >> 8) & 0xff); p[5] = static_cast<char>((H2C_INITIAL_WINDOW_SIZE ) & 0xff); return h2cBuildFrame(H2C_FRAME_SETTINGS, 0, 0, p); } static std::string h2cBuildSettingsAck() { return h2cBuildFrame(H2C_FRAME_SETTINGS, H2C_FLAG_ACK, 0, ""); } // Connection-level WINDOW_UPDATE to raise the default 65535 connection // window to match the per-stream INITIAL_WINDOW_SIZE (RFC 7540 §6.9.2). static std::string h2cConnectionWindowBoost() { uint32_t boost = H2C_INITIAL_WINDOW_SIZE - 65535u; if (boost == 0) return ""; return h2cBuildWindowUpdate(0, boost); } static std::string h2cBuildWindowUpdate(uint32_t stream_id, uint32_t increment) { std::string p(4, '\0'); p[0] = static_cast<char>((increment >> 24) & 0x7f); Loading Loading @@ -2030,6 +2028,7 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( if (!_h2PrefaceSent) { std::string preface(H2C_CLIENT_PREFACE, H2C_CLIENT_PREFACE_LEN); preface += h2cBuildSettings(); preface += h2cConnectionWindowBoost(); send_all(preface); _h2PrefaceSent = true; } Loading Loading @@ -2074,23 +2073,120 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( stream_id, hpack_block); send_all(headers_frame); // 3) Send DATA frames for POST body if present // 3) Send DATA frames for POST body with flow control (RFC 7540 §6.9) // raw is shared between POST-send and response-read phases std::vector<uint8_t> raw; raw.reserve(16384); if (postBody && !postBody->empty()) { // Track the peer's (server's) flow control window. // Initial value is 65535 until we receive the server's SETTINGS. int32_t peer_conn_window = 65535; int32_t peer_stream_window = 65535; // Helper: parse buffered frames for SETTINGS / WINDOW_UPDATE / PING auto process_fc_frames = [&]() { size_t foff = 0; while (foff + H2C_FRAME_HEADER_LEN <= raw.size()) { uint32_t flen = (static_cast<uint32_t>(raw[foff]) << 16) | (static_cast<uint32_t>(raw[foff+1]) << 8) | static_cast<uint32_t>(raw[foff+2]); uint8_t ftype = raw[foff+3]; uint8_t fflags = raw[foff+4]; uint32_t fsid = ((static_cast<uint32_t>(raw[foff+5]) & 0x7f) << 24) | (static_cast<uint32_t>(raw[foff+6]) << 16) | (static_cast<uint32_t>(raw[foff+7]) << 8) | static_cast<uint32_t>(raw[foff+8]); if (foff + H2C_FRAME_HEADER_LEN + flen > raw.size()) break; const uint8_t *fp = raw.data() + foff + H2C_FRAME_HEADER_LEN; if (ftype == H2C_FRAME_SETTINGS && !(fflags & H2C_FLAG_ACK)) { for (size_t si = 0; si + 6 <= flen; si += 6) { uint16_t setting_id = (static_cast<uint16_t>(fp[si]) << 8) | fp[si+1]; uint32_t setting_val = (static_cast<uint32_t>(fp[si+2]) << 24) | (static_cast<uint32_t>(fp[si+3]) << 16) | (static_cast<uint32_t>(fp[si+4]) << 8) | fp[si+5]; if (setting_id == 0x04) { // SETTINGS_INITIAL_WINDOW_SIZE int32_t delta = static_cast<int32_t>(setting_val) - peer_stream_window; peer_stream_window += delta; } } send_all(h2cBuildSettingsAck()); } else if (ftype == H2C_FRAME_WINDOW_UPDATE && flen >= 4) { uint32_t inc = ((static_cast<uint32_t>(fp[0]) & 0x7f) << 24) | (static_cast<uint32_t>(fp[1]) << 16) | (static_cast<uint32_t>(fp[2]) << 8) | fp[3]; if (inc > 0) { if (fsid == 0) peer_conn_window += static_cast<int32_t>(inc); else if (fsid == stream_id) peer_stream_window += static_cast<int32_t>(inc); } } else if (ftype == H2C_FRAME_PING && !(fflags & H2C_FLAG_ACK) && flen == 8) { std::string pp(reinterpret_cast<const char*>(fp), flen); send_all(h2cBuildFrame(H2C_FRAME_PING, H2C_FLAG_ACK, 0, pp)); } // Leave HEADERS / DATA in the buffer for the response phase // but only consume control frames here. foff += H2C_FRAME_HEADER_LEN + flen; } if (foff > 0) raw.erase(raw.begin(), raw.begin() + static_cast<ptrdiff_t>(foff)); }; // Try a non-blocking read to pick up the server's SETTINGS // (which usually arrives right after TLS handshake). { netplus::buffer nbuf(BLOCKSIZE); size_t n = 0; try { n = _cltsock->recvData(nbuf, 0); } catch (...) {} if (n > 0) { raw.insert(raw.end(), reinterpret_cast<uint8_t*>(nbuf.data.buf), reinterpret_cast<uint8_t*>(nbuf.data.buf) + n); process_fc_frames(); } } size_t offset = 0; while (offset < postBody->size()) { size_t chunk = std::min(postBody->size() - offset, H2C_MAX_FRAME_SIZE); int32_t allowed = std::min(peer_conn_window, peer_stream_window); // If window exhausted, block-read for WINDOW_UPDATE while (allowed <= 0) { netplus::buffer nbuf(BLOCKSIZE); size_t n = recv_blocking(nbuf); if (n == 0) { HTTPException he; he[HTTPException::Error] << "HTTP/2: EOF waiting for WINDOW_UPDATE"; throw he; } raw.insert(raw.end(), reinterpret_cast<uint8_t*>(nbuf.data.buf), reinterpret_cast<uint8_t*>(nbuf.data.buf) + n); process_fc_frames(); allowed = std::min(peer_conn_window, peer_stream_window); } size_t chunk = std::min({postBody->size() - offset, static_cast<size_t>(allowed), H2C_MAX_FRAME_SIZE}); bool last = (offset + chunk >= postBody->size()); uint8_t flags = last ? H2C_FLAG_END_STREAM : 0; std::string payload(postBody->data() + offset, chunk); std::string frame = h2cBuildFrame(H2C_FRAME_DATA, flags, stream_id, payload); send_all(frame); send_all(h2cBuildFrame(H2C_FRAME_DATA, flags, stream_id, payload)); peer_conn_window -= static_cast<int32_t>(chunk); peer_stream_window -= static_cast<int32_t>(chunk); offset += chunk; } } // 4) Read response frames std::vector<uint8_t> raw; raw.reserve(16384); bool got_response_headers = false; bool got_end_stream = false; Loading Loading @@ -2218,30 +2314,6 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( throw ee; } // Send HTTP/3 control streams once per connection (RFC 9114 §6.2) if (!_h3ControlSent) { // 1) Control stream: unidirectional, stream type 0x00, then SETTINGS frame uint64_t ctrl_id = q->openStream(false); std::vector<uint8_t> ctrl_payload; ctrl_payload.push_back(0x00); // stream type: control // SETTINGS frame: type=0x04, length=0 (accept all defaults) ctrl_payload.push_back(0x04); // frame type ctrl_payload.push_back(0x00); // frame length (empty) q->sendStreamData(ctrl_id, ctrl_payload, false); // 2) QPACK encoder stream: unidirectional, stream type 0x02 uint64_t enc_id = q->openStream(false); std::vector<uint8_t> enc_payload = {0x02}; q->sendStreamData(enc_id, enc_payload, false); // 3) QPACK decoder stream: unidirectional, stream type 0x03 uint64_t dec_id = q->openStream(false); std::vector<uint8_t> dec_payload = {0x03}; q->sendStreamData(dec_id, dec_payload, false); _h3ControlSent = true; } const int kMaxRedirects = 1; int redirects = 0; Loading src/http.h +23 −1 Original line number Diff line number Diff line Loading @@ -138,7 +138,6 @@ namespace libhttppp { bool _h2PrefaceSent = false; // true after connection preface sent // HTTP/3 client helpers bool _h3ControlSent = false; // true after H3 control streams sent uint32_t _h2NextStreamId = 1; // next client-initiated stream ID (odd) std::unique_ptr<hpack::Decoder> _h2Decoder; // persistent HPACK decoder for connection const std::vector<char> _h2Request(const std::string &method, Loading Loading @@ -371,6 +370,22 @@ namespace libhttppp { size_t offset = 0; // how far into body we've sent }; // Active streaming response state — lives on the connection's H2State // so Http2RequestEvent can resume sending after WINDOW_UPDATE. struct H2StreamingResponse { uint32_t streamId = 0; size_t contentLength = 0; size_t totalSent = 0; std::string pendingData; // buffered DATA not yet framed size_t pendingOffset = 0; std::unique_ptr<HttpRequest> tempreq; // per-stream request for ResponseEvent int tid = 0; ULONG_PTR args = 0; size_t emptyCount = 0; unsigned int backoffMs = 1; bool finished = false; }; struct H2PendingIncoming { std::vector<hpack::HeaderField> headers; std::string body; Loading @@ -382,11 +397,18 @@ namespace libhttppp { struct H2State { uint32_t streamId = 0; bool headersSent = false; bool serverPrefaceSent = false; size_t expectedContentLength = 0; size_t bodyBytesSent = 0; std::deque<H2PendingResponse> pendingResponses; std::map<uint32_t, H2PendingIncoming> pendingIncoming; hpack::Decoder hpackDecoder; // Peer flow-control windows (RFC 7540 §6.9) int32_t peerConnWindow = 65535; // connection-level int32_t peerInitialStreamWindow = 65535; // from peer SETTINGS std::map<uint32_t, int32_t> peerStreamWindows; // per-stream // Active streaming responses (one per stream) std::map<uint32_t, std::shared_ptr<H2StreamingResponse>> activeStreams; }; // Lazily allocated when the connection is upgraded to HTTP/2. Loading src/httpd.cpp +346 −195 File changed.Preview size limit exceeded, changes collapsed. Show changes src/httpd.h +2 −2 Original line number Diff line number Diff line Loading @@ -79,14 +79,14 @@ namespace libhttppp { }; std::mutex _h3BufferMutex; std::map<uint64_t, H3StreamBuffer> _h3StreamBuffers; std::set<netplus::socket*> _h3ControlStreamsSent; std::atomic<int> _h3NextTid{0}; void _initH3ControlStreams(netplus::quic *q); void _dispatchH2Stream(HttpRequest &cureq, std::string &out, uint32_t sid, const std::vector<hpack::HeaderField> &decoded, const std::string &reqBody, const int tid, ULONG_PTR args); void _resumeH2Streams(HttpRequest &cureq, std::string &out, const int tid, ULONG_PTR args); void RequestEvent(netplus::con &curcon, const int tid, ULONG_PTR args); void ResponseEvent(netplus::con &curcon,const int tid,ULONG_PTR args); void ConnectEvent(netplus::con &curcon,const int tid,ULONG_PTR args); Loading Loading
src/http.cpp +126 −54 Original line number Diff line number Diff line Loading @@ -285,7 +285,6 @@ void libhttppp::HttpClient::resetConnection(){ _h2PrefaceSent = false; _h2NextStreamId = 1; _h2Decoder.reset(); _h3ControlSent = false; if (_url.getProtocol() == HttpUrl::HTTP3) { auto q = std::make_unique<netplus::quic>(); q->connect(_url.getHost(), _url.getPort(), false); Loading Loading @@ -360,7 +359,6 @@ void libhttppp::HttpClient::reconnect(){ _h2PrefaceSent = false; _h2NextStreamId = 1; _h2Decoder.reset(); _h3ControlSent = false; if (_url.getProtocol() == HttpUrl::HTTP3) { auto q = std::make_unique<netplus::quic>(); q->connect(_url.getHost(), _url.getPort(), false); Loading Loading @@ -750,6 +748,7 @@ static std::string h2cBuildFrame(uint8_t type, uint8_t flags, uint32_t stream_id static std::string h2cBuildSettings(); static std::string h2cBuildSettingsAck(); static std::string h2cBuildWindowUpdate(uint32_t stream_id, uint32_t increment); static std::string h2cConnectionWindowBoost(); // ── Streaming API ────────────────────────────────────────────────────── Loading Loading @@ -794,26 +793,6 @@ libhttppp::HttpResponse libhttppp::HttpClient::GetStream(libhttppp::HttpRequest // ── HTTP/3 (QUIC) streaming path ── if (auto *q = dynamic_cast<netplus::quic*>(_cltsock.get())) { // Send HTTP/3 control streams once per connection if (!_h3ControlSent) { uint64_t ctrl_id = q->openStream(false); std::vector<uint8_t> ctrl_payload; ctrl_payload.push_back(0x00); ctrl_payload.push_back(0x04); ctrl_payload.push_back(0x00); q->sendStreamData(ctrl_id, ctrl_payload, false); uint64_t enc_id = q->openStream(false); std::vector<uint8_t> enc_payload = {0x02}; q->sendStreamData(enc_id, enc_payload, false); uint64_t dec_id = q->openStream(false); std::vector<uint8_t> dec_payload = {0x03}; q->sendStreamData(dec_id, dec_payload, false); _h3ControlSent = true; } std::string path = nreq.getRequestURL(); if (path.empty()) path = _url.getPath(); std::string scheme = (_url.getProtocol() == HttpUrl::HTTP) ? "http" : "https"; Loading Loading @@ -966,6 +945,7 @@ libhttppp::HttpResponse libhttppp::HttpClient::GetStream(libhttppp::HttpRequest if (!_h2PrefaceSent) { std::string preface(H2C_CLIENT_PREFACE, H2C_CLIENT_PREFACE_LEN); preface += h2cBuildSettings(); preface += h2cConnectionWindowBoost(); _sendAll(preface); _h2PrefaceSent = true; } Loading Loading @@ -1946,15 +1926,33 @@ static std::string h2cBuildFrame(uint8_t type, uint8_t flags, uint32_t stream_id return f; } // RFC 7540: SETTINGS_INITIAL_WINDOW_SIZE — use 16 MB so both large // downloads and uploads don't stall on the default 65535-byte window. static constexpr uint32_t H2C_INITIAL_WINDOW_SIZE = 16u * 1024 * 1024; static std::string h2cBuildSettings() { // Send empty SETTINGS (accept defaults) return h2cBuildFrame(H2C_FRAME_SETTINGS, 0, 0, ""); // SETTINGS_INITIAL_WINDOW_SIZE (0x04) = H2C_INITIAL_WINDOW_SIZE std::string p(6, '\0'); p[0] = 0x00; p[1] = 0x04; p[2] = static_cast<char>((H2C_INITIAL_WINDOW_SIZE >> 24) & 0xff); p[3] = static_cast<char>((H2C_INITIAL_WINDOW_SIZE >> 16) & 0xff); p[4] = static_cast<char>((H2C_INITIAL_WINDOW_SIZE >> 8) & 0xff); p[5] = static_cast<char>((H2C_INITIAL_WINDOW_SIZE ) & 0xff); return h2cBuildFrame(H2C_FRAME_SETTINGS, 0, 0, p); } static std::string h2cBuildSettingsAck() { return h2cBuildFrame(H2C_FRAME_SETTINGS, H2C_FLAG_ACK, 0, ""); } // Connection-level WINDOW_UPDATE to raise the default 65535 connection // window to match the per-stream INITIAL_WINDOW_SIZE (RFC 7540 §6.9.2). static std::string h2cConnectionWindowBoost() { uint32_t boost = H2C_INITIAL_WINDOW_SIZE - 65535u; if (boost == 0) return ""; return h2cBuildWindowUpdate(0, boost); } static std::string h2cBuildWindowUpdate(uint32_t stream_id, uint32_t increment) { std::string p(4, '\0'); p[0] = static_cast<char>((increment >> 24) & 0x7f); Loading Loading @@ -2030,6 +2028,7 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( if (!_h2PrefaceSent) { std::string preface(H2C_CLIENT_PREFACE, H2C_CLIENT_PREFACE_LEN); preface += h2cBuildSettings(); preface += h2cConnectionWindowBoost(); send_all(preface); _h2PrefaceSent = true; } Loading Loading @@ -2074,23 +2073,120 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( stream_id, hpack_block); send_all(headers_frame); // 3) Send DATA frames for POST body if present // 3) Send DATA frames for POST body with flow control (RFC 7540 §6.9) // raw is shared between POST-send and response-read phases std::vector<uint8_t> raw; raw.reserve(16384); if (postBody && !postBody->empty()) { // Track the peer's (server's) flow control window. // Initial value is 65535 until we receive the server's SETTINGS. int32_t peer_conn_window = 65535; int32_t peer_stream_window = 65535; // Helper: parse buffered frames for SETTINGS / WINDOW_UPDATE / PING auto process_fc_frames = [&]() { size_t foff = 0; while (foff + H2C_FRAME_HEADER_LEN <= raw.size()) { uint32_t flen = (static_cast<uint32_t>(raw[foff]) << 16) | (static_cast<uint32_t>(raw[foff+1]) << 8) | static_cast<uint32_t>(raw[foff+2]); uint8_t ftype = raw[foff+3]; uint8_t fflags = raw[foff+4]; uint32_t fsid = ((static_cast<uint32_t>(raw[foff+5]) & 0x7f) << 24) | (static_cast<uint32_t>(raw[foff+6]) << 16) | (static_cast<uint32_t>(raw[foff+7]) << 8) | static_cast<uint32_t>(raw[foff+8]); if (foff + H2C_FRAME_HEADER_LEN + flen > raw.size()) break; const uint8_t *fp = raw.data() + foff + H2C_FRAME_HEADER_LEN; if (ftype == H2C_FRAME_SETTINGS && !(fflags & H2C_FLAG_ACK)) { for (size_t si = 0; si + 6 <= flen; si += 6) { uint16_t setting_id = (static_cast<uint16_t>(fp[si]) << 8) | fp[si+1]; uint32_t setting_val = (static_cast<uint32_t>(fp[si+2]) << 24) | (static_cast<uint32_t>(fp[si+3]) << 16) | (static_cast<uint32_t>(fp[si+4]) << 8) | fp[si+5]; if (setting_id == 0x04) { // SETTINGS_INITIAL_WINDOW_SIZE int32_t delta = static_cast<int32_t>(setting_val) - peer_stream_window; peer_stream_window += delta; } } send_all(h2cBuildSettingsAck()); } else if (ftype == H2C_FRAME_WINDOW_UPDATE && flen >= 4) { uint32_t inc = ((static_cast<uint32_t>(fp[0]) & 0x7f) << 24) | (static_cast<uint32_t>(fp[1]) << 16) | (static_cast<uint32_t>(fp[2]) << 8) | fp[3]; if (inc > 0) { if (fsid == 0) peer_conn_window += static_cast<int32_t>(inc); else if (fsid == stream_id) peer_stream_window += static_cast<int32_t>(inc); } } else if (ftype == H2C_FRAME_PING && !(fflags & H2C_FLAG_ACK) && flen == 8) { std::string pp(reinterpret_cast<const char*>(fp), flen); send_all(h2cBuildFrame(H2C_FRAME_PING, H2C_FLAG_ACK, 0, pp)); } // Leave HEADERS / DATA in the buffer for the response phase // but only consume control frames here. foff += H2C_FRAME_HEADER_LEN + flen; } if (foff > 0) raw.erase(raw.begin(), raw.begin() + static_cast<ptrdiff_t>(foff)); }; // Try a non-blocking read to pick up the server's SETTINGS // (which usually arrives right after TLS handshake). { netplus::buffer nbuf(BLOCKSIZE); size_t n = 0; try { n = _cltsock->recvData(nbuf, 0); } catch (...) {} if (n > 0) { raw.insert(raw.end(), reinterpret_cast<uint8_t*>(nbuf.data.buf), reinterpret_cast<uint8_t*>(nbuf.data.buf) + n); process_fc_frames(); } } size_t offset = 0; while (offset < postBody->size()) { size_t chunk = std::min(postBody->size() - offset, H2C_MAX_FRAME_SIZE); int32_t allowed = std::min(peer_conn_window, peer_stream_window); // If window exhausted, block-read for WINDOW_UPDATE while (allowed <= 0) { netplus::buffer nbuf(BLOCKSIZE); size_t n = recv_blocking(nbuf); if (n == 0) { HTTPException he; he[HTTPException::Error] << "HTTP/2: EOF waiting for WINDOW_UPDATE"; throw he; } raw.insert(raw.end(), reinterpret_cast<uint8_t*>(nbuf.data.buf), reinterpret_cast<uint8_t*>(nbuf.data.buf) + n); process_fc_frames(); allowed = std::min(peer_conn_window, peer_stream_window); } size_t chunk = std::min({postBody->size() - offset, static_cast<size_t>(allowed), H2C_MAX_FRAME_SIZE}); bool last = (offset + chunk >= postBody->size()); uint8_t flags = last ? H2C_FLAG_END_STREAM : 0; std::string payload(postBody->data() + offset, chunk); std::string frame = h2cBuildFrame(H2C_FRAME_DATA, flags, stream_id, payload); send_all(frame); send_all(h2cBuildFrame(H2C_FRAME_DATA, flags, stream_id, payload)); peer_conn_window -= static_cast<int32_t>(chunk); peer_stream_window -= static_cast<int32_t>(chunk); offset += chunk; } } // 4) Read response frames std::vector<uint8_t> raw; raw.reserve(16384); bool got_response_headers = false; bool got_end_stream = false; Loading Loading @@ -2218,30 +2314,6 @@ const std::vector<char> libhttppp::HttpClient::_h2Request( throw ee; } // Send HTTP/3 control streams once per connection (RFC 9114 §6.2) if (!_h3ControlSent) { // 1) Control stream: unidirectional, stream type 0x00, then SETTINGS frame uint64_t ctrl_id = q->openStream(false); std::vector<uint8_t> ctrl_payload; ctrl_payload.push_back(0x00); // stream type: control // SETTINGS frame: type=0x04, length=0 (accept all defaults) ctrl_payload.push_back(0x04); // frame type ctrl_payload.push_back(0x00); // frame length (empty) q->sendStreamData(ctrl_id, ctrl_payload, false); // 2) QPACK encoder stream: unidirectional, stream type 0x02 uint64_t enc_id = q->openStream(false); std::vector<uint8_t> enc_payload = {0x02}; q->sendStreamData(enc_id, enc_payload, false); // 3) QPACK decoder stream: unidirectional, stream type 0x03 uint64_t dec_id = q->openStream(false); std::vector<uint8_t> dec_payload = {0x03}; q->sendStreamData(dec_id, dec_payload, false); _h3ControlSent = true; } const int kMaxRedirects = 1; int redirects = 0; Loading
src/http.h +23 −1 Original line number Diff line number Diff line Loading @@ -138,7 +138,6 @@ namespace libhttppp { bool _h2PrefaceSent = false; // true after connection preface sent // HTTP/3 client helpers bool _h3ControlSent = false; // true after H3 control streams sent uint32_t _h2NextStreamId = 1; // next client-initiated stream ID (odd) std::unique_ptr<hpack::Decoder> _h2Decoder; // persistent HPACK decoder for connection const std::vector<char> _h2Request(const std::string &method, Loading Loading @@ -371,6 +370,22 @@ namespace libhttppp { size_t offset = 0; // how far into body we've sent }; // Active streaming response state — lives on the connection's H2State // so Http2RequestEvent can resume sending after WINDOW_UPDATE. struct H2StreamingResponse { uint32_t streamId = 0; size_t contentLength = 0; size_t totalSent = 0; std::string pendingData; // buffered DATA not yet framed size_t pendingOffset = 0; std::unique_ptr<HttpRequest> tempreq; // per-stream request for ResponseEvent int tid = 0; ULONG_PTR args = 0; size_t emptyCount = 0; unsigned int backoffMs = 1; bool finished = false; }; struct H2PendingIncoming { std::vector<hpack::HeaderField> headers; std::string body; Loading @@ -382,11 +397,18 @@ namespace libhttppp { struct H2State { uint32_t streamId = 0; bool headersSent = false; bool serverPrefaceSent = false; size_t expectedContentLength = 0; size_t bodyBytesSent = 0; std::deque<H2PendingResponse> pendingResponses; std::map<uint32_t, H2PendingIncoming> pendingIncoming; hpack::Decoder hpackDecoder; // Peer flow-control windows (RFC 7540 §6.9) int32_t peerConnWindow = 65535; // connection-level int32_t peerInitialStreamWindow = 65535; // from peer SETTINGS std::map<uint32_t, int32_t> peerStreamWindows; // per-stream // Active streaming responses (one per stream) std::map<uint32_t, std::shared_ptr<H2StreamingResponse>> activeStreams; }; // Lazily allocated when the connection is upgraded to HTTP/2. Loading
src/httpd.h +2 −2 Original line number Diff line number Diff line Loading @@ -79,14 +79,14 @@ namespace libhttppp { }; std::mutex _h3BufferMutex; std::map<uint64_t, H3StreamBuffer> _h3StreamBuffers; std::set<netplus::socket*> _h3ControlStreamsSent; std::atomic<int> _h3NextTid{0}; void _initH3ControlStreams(netplus::quic *q); void _dispatchH2Stream(HttpRequest &cureq, std::string &out, uint32_t sid, const std::vector<hpack::HeaderField> &decoded, const std::string &reqBody, const int tid, ULONG_PTR args); void _resumeH2Streams(HttpRequest &cureq, std::string &out, const int tid, ULONG_PTR args); void RequestEvent(netplus::con &curcon, const int tid, ULONG_PTR args); void ResponseEvent(netplus::con &curcon,const int tid,ULONG_PTR args); void ConnectEvent(netplus::con &curcon,const int tid,ULONG_PTR args); Loading