Loading src/httpd.cpp +32 −1 Changes for src/httpd.cpp: 32 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -679,6 +679,16 @@ void libhttppp::HttpEvent::_finishH2Dispatch(HttpRequest &cureq, cureq.h2state().peerStreamWindows.erase(sid); } } } else { // Application never produced a response (forgot to call // HttpResponse::send / equivalent, or an internal handler path // returned without answering). Previously this left the stream // open with nothing ever sent -- no HEADERS, no DATA, no // RST_STREAM -- so the client just hangs forever. Cancel the // stream instead so the client sees a definitive failure. uint8_t rst[4] = {0, 0, 0, 0x02}; // INTERNAL_ERROR out += h2BuildFrame(H2_FRAME_RST_STREAM, 0, sid, std::string(reinterpret_cast<char*>(rst), 4)); } } Loading Loading @@ -1306,7 +1316,8 @@ done: // waiting on responses). // Guard: limit reprocessing iterations to prevent infinite spin when // frames continuously generate output (e.g. ACKs) without consuming data. if (reprocess_count < 16 && (!out.empty() || off > 0 || anyOffloaded) && cureq.RecvData.size() >= H2_FRAME_HEADER_LEN) { if ((!out.empty() || off > 0 || anyOffloaded) && cureq.RecvData.size() >= H2_FRAME_HEADER_LEN) { if (reprocess_count < 16) { ++reprocess_count; out.clear(); out.reserve(4096); Loading @@ -1314,6 +1325,21 @@ done: goto reprocess; } // Fairness cap hit while frames for further streams are still // buffered (e.g. a client bursting >17 simple GET/HEAD requests // over one multiplexed connection in a single read). SendData was // already flushed synchronously above, so epoll's own writable // check sees nothing queued and won't call ResponseEvent on its // own -- and the client won't send more bytes, it's waiting on // responses already dispatched. Without an explicit rearm here, // the remaining request(s) are stranded in RecvData forever. // ResponseEvent's H2 branch unconditionally reprocesses leftover // frames, so requesting a writable poll gets them handled with a // fresh reprocess budget. cureq.slots[0].ResponsePending.store(true, std::memory_order_release); netplus::requestWritablePoll(cureq); } return true; } Loading Loading @@ -1687,6 +1713,11 @@ void libhttppp::HttpEvent::Http3StreamEvent(netplus::socket *sock, tempreq.deldata(":res-content-type"); tempreq.deldata(":res-content-length"); } else { // The application never produced a response for this H3 request // (mirrors the H2 case in _finishH2Dispatch). Without this, the // stream is left open with nothing ever sent, and the client // hangs forever waiting for a reply that will never arrive. q->resetStream(stream_id, 0x0102); // H3_INTERNAL_ERROR } } Loading Loading
src/httpd.cpp +32 −1 Changes for src/httpd.cpp: 32 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -679,6 +679,16 @@ void libhttppp::HttpEvent::_finishH2Dispatch(HttpRequest &cureq, cureq.h2state().peerStreamWindows.erase(sid); } } } else { // Application never produced a response (forgot to call // HttpResponse::send / equivalent, or an internal handler path // returned without answering). Previously this left the stream // open with nothing ever sent -- no HEADERS, no DATA, no // RST_STREAM -- so the client just hangs forever. Cancel the // stream instead so the client sees a definitive failure. uint8_t rst[4] = {0, 0, 0, 0x02}; // INTERNAL_ERROR out += h2BuildFrame(H2_FRAME_RST_STREAM, 0, sid, std::string(reinterpret_cast<char*>(rst), 4)); } } Loading Loading @@ -1306,7 +1316,8 @@ done: // waiting on responses). // Guard: limit reprocessing iterations to prevent infinite spin when // frames continuously generate output (e.g. ACKs) without consuming data. if (reprocess_count < 16 && (!out.empty() || off > 0 || anyOffloaded) && cureq.RecvData.size() >= H2_FRAME_HEADER_LEN) { if ((!out.empty() || off > 0 || anyOffloaded) && cureq.RecvData.size() >= H2_FRAME_HEADER_LEN) { if (reprocess_count < 16) { ++reprocess_count; out.clear(); out.reserve(4096); Loading @@ -1314,6 +1325,21 @@ done: goto reprocess; } // Fairness cap hit while frames for further streams are still // buffered (e.g. a client bursting >17 simple GET/HEAD requests // over one multiplexed connection in a single read). SendData was // already flushed synchronously above, so epoll's own writable // check sees nothing queued and won't call ResponseEvent on its // own -- and the client won't send more bytes, it's waiting on // responses already dispatched. Without an explicit rearm here, // the remaining request(s) are stranded in RecvData forever. // ResponseEvent's H2 branch unconditionally reprocesses leftover // frames, so requesting a writable poll gets them handled with a // fresh reprocess budget. cureq.slots[0].ResponsePending.store(true, std::memory_order_release); netplus::requestWritablePoll(cureq); } return true; } Loading Loading @@ -1687,6 +1713,11 @@ void libhttppp::HttpEvent::Http3StreamEvent(netplus::socket *sock, tempreq.deldata(":res-content-type"); tempreq.deldata(":res-content-length"); } else { // The application never produced a response for this H3 request // (mirrors the H2 case in _finishH2Dispatch). Without this, the // stream is left open with nothing ever sent, and the client // hangs forever waiting for a reply that will never arrive. q->resetStream(stream_id, 0x0102); // H3_INTERNAL_ERROR } } Loading