diff --git a/http/server/HttpHandler.cpp b/http/server/HttpHandler.cpp index fa2013b32..cbbafc839 100644 --- a/http/server/HttpHandler.cpp +++ b/http/server/HttpHandler.cpp @@ -549,7 +549,7 @@ int HttpHandler::HandleHttpRequest() { } } - if (writer && writer->state != hv::HttpResponseWriter::SEND_BEGIN) { + if (writer && !writer->isBegin()) { status_code = HTTP_STATUS_NEXT; } if (status_code == HTTP_STATUS_NEXT) { @@ -777,6 +777,18 @@ int HttpHandler::FeedRecvData(const char* data, size_t len) { case HttpHandler::HTTP_V1: case HttpHandler::HTTP_V2: if (state != WANT_RECV) { + // A new request arrived on this connection before the previous one + // finished. Reset() reuses the same req/resp objects, so if an async + // handler on another thread is still producing/sending the previous + // response (writer not yet End()ed), resetting here races that thread + // and can crash. In that case reject the pipelined/early data and let + // the caller close the connection. Otherwise (response already sent) + // it is safe to reset for the next keep-alive request. + if (writer && !writer->isEnd()) { + hloge("[%s:%d] new request while previous async response is still in flight", ip, port); + error = ERR_REQUEST; + return -1; + } Reset(); } nfeed = parser->FeedRecvData(data, len); diff --git a/http/server/HttpResponseWriter.h b/http/server/HttpResponseWriter.h index 18636c58d..badd1cadd 100644 --- a/http/server/HttpResponseWriter.h +++ b/http/server/HttpResponseWriter.h @@ -34,6 +34,12 @@ class HV_EXPORT HttpResponseWriter : public SocketChannel { bool isHttp2() const { return (bool)submitHttp2Response; } + // isBegin(): nothing has been written yet (still at SEND_BEGIN). + // isEnd(): the response has been fully handed off, i.e. End() was called + // (End() is the terminal call in every usage sequence below). + bool isBegin() const { return state == SEND_BEGIN; } + bool isEnd() const { return end == SEND_END; } + // Begin -> End // Begin -> WriteResponse -> End // Begin -> WriteStatus -> WriteHeader -> WriteBody -> End