Loading src/http.cpp +34 −1 Changes for src/http.cpp: 34 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -2198,7 +2198,7 @@ size_t libhttppp::HttpClient::readBodyChunk(char *buf, size_t bufsize) { return 0; } size_t libhttppp::HttpClient::readBodyChunkNonBlocking(char *buf, size_t bufsize) { size_t libhttppp::HttpClient::_readBodyChunkNonBlockingRaw(char *buf, size_t bufsize) { // Stream finished: signal completion with (size_t)-1 if (_streamMode == STREAM_NONE || bufsize == 0) return static_cast<size_t>(-1); Loading Loading @@ -2285,6 +2285,39 @@ size_t libhttppp::HttpClient::readBodyChunkNonBlocking(char *buf, size_t bufsize return static_cast<size_t>(-1); } // Wraps _readBodyChunkNonBlockingRaw() with an idle-stream deadline: a caller // polling this method (typically an HTTP event loop driven off ResponsePending, // e.g. blogi's media proxy) has no way of knowing whether "0 bytes yet" means // "more is coming shortly" or "the peer went silent mid-response and will // never send another byte" -- the TCP connection stays ESTABLISHED either // way. Left unbounded, that turns into a permanently stuck stream (and, for // callers that hold a pooled connection alive until the stream finishes, a // leaked connection). Reuses _recvTimeoutSec (the same timeout setTimeout()/ // the constructor already configure) rather than inventing a second knob. size_t libhttppp::HttpClient::readBodyChunkNonBlocking(char *buf, size_t bufsize) { if (_streamMode == STREAM_NONE) { _streamIdleSince = std::chrono::steady_clock::time_point{}; return static_cast<size_t>(-1); } if (_streamIdleSince != std::chrono::steady_clock::time_point{} && std::chrono::steady_clock::now() - _streamIdleSince > std::chrono::seconds(_recvTimeoutSec)) { _streamMode = STREAM_NONE; _streamIdleSince = std::chrono::steady_clock::time_point{}; return static_cast<size_t>(-1); } size_t got = _readBodyChunkNonBlockingRaw(buf, bufsize); if (got == 0 && _streamMode != STREAM_NONE) { if (_streamIdleSince == std::chrono::steady_clock::time_point{}) _streamIdleSince = std::chrono::steady_clock::now(); } else { _streamIdleSince = std::chrono::steady_clock::time_point{}; } return got; } // --------------- HTTP/3 client frame helpers --------------- static size_t h3EncodeVarInt(uint64_t value, uint8_t *out) { if (value < 64) { Loading src/http.h +10 −0 Changes for src/http.h: 10 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -231,6 +231,10 @@ namespace libhttppp { // return" difference kept per-mode. size_t _streamReadH3(char *buf, size_t bufsize, bool blocking); // Mode-dispatch body of readBodyChunkNonBlocking(), wrapped by the // public method with an idle-stream deadline (see _streamIdleSince). size_t _readBodyChunkNonBlockingRaw(char *buf, size_t bufsize); // Shared Get/Post/Put/Delete implementation: builds+sends the request // over whichever transport is active (H1/H2/H3), follows redirects // per RFC 7231 §6.4 (303 always converts to a bodyless GET; 307/308 Loading Loading @@ -280,6 +284,12 @@ namespace libhttppp { bool _streamH3InDataFrame = false; // true when inside a partial DATA frame uint64_t _streamH3DataRemaining = 0; // bytes left in current DATA frame // Set the moment readBodyChunkNonBlocking() first sees "no data yet" // for the active stream; cleared on any progress or completion. Lets // the wrapper tell "briefly nothing to read" apart from "peer has // gone silent for _recvTimeoutSec and is never sending another byte". std::chrono::steady_clock::time_point _streamIdleSince{}; private: HttpUrl _url; std::unique_ptr<netplus::socket> _cltsock; Loading Loading
src/http.cpp +34 −1 Changes for src/http.cpp: 34 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -2198,7 +2198,7 @@ size_t libhttppp::HttpClient::readBodyChunk(char *buf, size_t bufsize) { return 0; } size_t libhttppp::HttpClient::readBodyChunkNonBlocking(char *buf, size_t bufsize) { size_t libhttppp::HttpClient::_readBodyChunkNonBlockingRaw(char *buf, size_t bufsize) { // Stream finished: signal completion with (size_t)-1 if (_streamMode == STREAM_NONE || bufsize == 0) return static_cast<size_t>(-1); Loading Loading @@ -2285,6 +2285,39 @@ size_t libhttppp::HttpClient::readBodyChunkNonBlocking(char *buf, size_t bufsize return static_cast<size_t>(-1); } // Wraps _readBodyChunkNonBlockingRaw() with an idle-stream deadline: a caller // polling this method (typically an HTTP event loop driven off ResponsePending, // e.g. blogi's media proxy) has no way of knowing whether "0 bytes yet" means // "more is coming shortly" or "the peer went silent mid-response and will // never send another byte" -- the TCP connection stays ESTABLISHED either // way. Left unbounded, that turns into a permanently stuck stream (and, for // callers that hold a pooled connection alive until the stream finishes, a // leaked connection). Reuses _recvTimeoutSec (the same timeout setTimeout()/ // the constructor already configure) rather than inventing a second knob. size_t libhttppp::HttpClient::readBodyChunkNonBlocking(char *buf, size_t bufsize) { if (_streamMode == STREAM_NONE) { _streamIdleSince = std::chrono::steady_clock::time_point{}; return static_cast<size_t>(-1); } if (_streamIdleSince != std::chrono::steady_clock::time_point{} && std::chrono::steady_clock::now() - _streamIdleSince > std::chrono::seconds(_recvTimeoutSec)) { _streamMode = STREAM_NONE; _streamIdleSince = std::chrono::steady_clock::time_point{}; return static_cast<size_t>(-1); } size_t got = _readBodyChunkNonBlockingRaw(buf, bufsize); if (got == 0 && _streamMode != STREAM_NONE) { if (_streamIdleSince == std::chrono::steady_clock::time_point{}) _streamIdleSince = std::chrono::steady_clock::now(); } else { _streamIdleSince = std::chrono::steady_clock::time_point{}; } return got; } // --------------- HTTP/3 client frame helpers --------------- static size_t h3EncodeVarInt(uint64_t value, uint8_t *out) { if (value < 64) { Loading
src/http.h +10 −0 Changes for src/http.h: 10 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -231,6 +231,10 @@ namespace libhttppp { // return" difference kept per-mode. size_t _streamReadH3(char *buf, size_t bufsize, bool blocking); // Mode-dispatch body of readBodyChunkNonBlocking(), wrapped by the // public method with an idle-stream deadline (see _streamIdleSince). size_t _readBodyChunkNonBlockingRaw(char *buf, size_t bufsize); // Shared Get/Post/Put/Delete implementation: builds+sends the request // over whichever transport is active (H1/H2/H3), follows redirects // per RFC 7231 §6.4 (303 always converts to a bodyless GET; 307/308 Loading Loading @@ -280,6 +284,12 @@ namespace libhttppp { bool _streamH3InDataFrame = false; // true when inside a partial DATA frame uint64_t _streamH3DataRemaining = 0; // bytes left in current DATA frame // Set the moment readBodyChunkNonBlocking() first sees "no data yet" // for the active stream; cleared on any progress or completion. Lets // the wrapper tell "briefly nothing to read" apart from "peer has // gone silent for _recvTimeoutSec and is never sending another byte". std::chrono::steady_clock::time_point _streamIdleSince{}; private: HttpUrl _url; std::unique_ptr<netplus::socket> _cltsock; Loading