Loading src/http.cpp +27 −0 Changes for src/http.cpp: 27 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -3750,6 +3750,27 @@ bool libhttppp::HttpResponse::_storeResponseInfo(netplus::con &curconnection, in return true; } // A negative datalen is this codebase's established "streaming, more to come" // signal (see _storeResponseInfo()/_finishH2Dispatch's identical is_streaming // check for H2/H3). H2/H3 already have their own resumption path for that -- // WINDOW_UPDATE-driven self-calls into ResponseEvent via _resumeH2Streams -- // but plain HTTP/1.x has no equivalent: a headers-only send() here appends no // body, so nothing is left in SendData to flush, and nothing ever re-arms // polling for this connection again. Marking ResponsePending here, uniformly // regardless of protocol, gives _dispatchH1Request (httpd.cpp) a signal to // keep calling ResponseEvent until the app stops asking for another turn -- // harmless for H2/H3 (and for an H1 caller that fully drains synchronously // inside RequestEvent() itself, e.g. proxyplus's own Range handling): an // extra ResponseEvent call on a connection with nothing left to resume is a // no-op for every existing ResponseEvent implementation in this codebase. static void markDeferredIfStreaming(netplus::con &curconnection, int datalen) { if (datalen >= 0) return; if (auto *req = dynamic_cast<libhttppp::HttpRequest*>(&curconnection)) { if (!req->slots.empty()) req->slots[0].ResponsePending.store(true, std::memory_order_release); } } void libhttppp::HttpResponse::send(netplus::con &curconnection,const std::string &data, int datalen){ if(datalen==0) datalen=data.size(); Loading @@ -3773,6 +3794,8 @@ void libhttppp::HttpResponse::send(netplus::con &curconnection,const std::string return; } markDeferredIfStreaming(curconnection, datalen); std::vector<char> header; printHeader(header); Loading Loading @@ -3802,6 +3825,8 @@ void libhttppp::HttpResponse::send(netplus::con &curconnection,const unsigned c return; } markDeferredIfStreaming(curconnection, datalen); std::vector<char> header; printHeader(header); Loading Loading @@ -3836,6 +3861,8 @@ void libhttppp::HttpResponse::send(netplus::con &curconnection,const std::vector return; } markDeferredIfStreaming(curconnection, datalen); std::vector<char> header; printHeader(header); Loading src/httpd.cpp +23 −0 Changes for src/httpd.cpp: 23 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -714,6 +714,29 @@ void libhttppp::HttpEvent::_dispatchH1Request(HttpRequest &cureq, size_t consume cureq2.slots[0].csock->close(); return; } // A deferred/streaming response (RequestEvent called send() with a // negative datalen -- see http.cpp's markDeferredIfStreaming()) needs // ResponseEvent called again to actually produce and flush its body. // H2/H3 self-drive this via _resumeH2Streams' WINDOW_UPDATE handling; // H1 has no such mechanism, so without this loop the connection would // reattach with its response permanently stuck at headers-only -- the // app's own STREAM_IDLE_TIMEOUT-style reaper (if it has one) would be // the only thing to ever notice, and only after that timeout elapses, // having produced zero bytes the whole time. Bounded by the same // kH1OffloadSocketTimeoutMs already set on this socket (each flush // below re-applies it) plus whatever stall detection the app's own // ResponseEvent has for its upstream source -- no separate iteration // cap needed on top of those. while (cureq2.slots[0].ResponsePending.exchange( false, std::memory_order_acq_rel)) { ResponseEvent(cureq2, tid, args); if (!cureq2.slots[0].csock) return; // see the identical check above if (!cureq2.flushSendData()) { cureq2.slots[0].csock->close(); return; } } } catch (const std::exception &) { // Either RequestEvent or the flush hit a hard error (peer reset, // socket-level failure) -- nothing to reattach. csock can't have gone Loading Loading
src/http.cpp +27 −0 Changes for src/http.cpp: 27 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -3750,6 +3750,27 @@ bool libhttppp::HttpResponse::_storeResponseInfo(netplus::con &curconnection, in return true; } // A negative datalen is this codebase's established "streaming, more to come" // signal (see _storeResponseInfo()/_finishH2Dispatch's identical is_streaming // check for H2/H3). H2/H3 already have their own resumption path for that -- // WINDOW_UPDATE-driven self-calls into ResponseEvent via _resumeH2Streams -- // but plain HTTP/1.x has no equivalent: a headers-only send() here appends no // body, so nothing is left in SendData to flush, and nothing ever re-arms // polling for this connection again. Marking ResponsePending here, uniformly // regardless of protocol, gives _dispatchH1Request (httpd.cpp) a signal to // keep calling ResponseEvent until the app stops asking for another turn -- // harmless for H2/H3 (and for an H1 caller that fully drains synchronously // inside RequestEvent() itself, e.g. proxyplus's own Range handling): an // extra ResponseEvent call on a connection with nothing left to resume is a // no-op for every existing ResponseEvent implementation in this codebase. static void markDeferredIfStreaming(netplus::con &curconnection, int datalen) { if (datalen >= 0) return; if (auto *req = dynamic_cast<libhttppp::HttpRequest*>(&curconnection)) { if (!req->slots.empty()) req->slots[0].ResponsePending.store(true, std::memory_order_release); } } void libhttppp::HttpResponse::send(netplus::con &curconnection,const std::string &data, int datalen){ if(datalen==0) datalen=data.size(); Loading @@ -3773,6 +3794,8 @@ void libhttppp::HttpResponse::send(netplus::con &curconnection,const std::string return; } markDeferredIfStreaming(curconnection, datalen); std::vector<char> header; printHeader(header); Loading Loading @@ -3802,6 +3825,8 @@ void libhttppp::HttpResponse::send(netplus::con &curconnection,const unsigned c return; } markDeferredIfStreaming(curconnection, datalen); std::vector<char> header; printHeader(header); Loading Loading @@ -3836,6 +3861,8 @@ void libhttppp::HttpResponse::send(netplus::con &curconnection,const std::vector return; } markDeferredIfStreaming(curconnection, datalen); std::vector<char> header; printHeader(header); Loading
src/httpd.cpp +23 −0 Changes for src/httpd.cpp: 23 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -714,6 +714,29 @@ void libhttppp::HttpEvent::_dispatchH1Request(HttpRequest &cureq, size_t consume cureq2.slots[0].csock->close(); return; } // A deferred/streaming response (RequestEvent called send() with a // negative datalen -- see http.cpp's markDeferredIfStreaming()) needs // ResponseEvent called again to actually produce and flush its body. // H2/H3 self-drive this via _resumeH2Streams' WINDOW_UPDATE handling; // H1 has no such mechanism, so without this loop the connection would // reattach with its response permanently stuck at headers-only -- the // app's own STREAM_IDLE_TIMEOUT-style reaper (if it has one) would be // the only thing to ever notice, and only after that timeout elapses, // having produced zero bytes the whole time. Bounded by the same // kH1OffloadSocketTimeoutMs already set on this socket (each flush // below re-applies it) plus whatever stall detection the app's own // ResponseEvent has for its upstream source -- no separate iteration // cap needed on top of those. while (cureq2.slots[0].ResponsePending.exchange( false, std::memory_order_acq_rel)) { ResponseEvent(cureq2, tid, args); if (!cureq2.slots[0].csock) return; // see the identical check above if (!cureq2.flushSendData()) { cureq2.slots[0].csock->close(); return; } } } catch (const std::exception &) { // Either RequestEvent or the flush hit a hard error (peer reset, // socket-level failure) -- nothing to reattach. csock can't have gone Loading