From 2bf6f8384f008ff5e398d54f9ec6f8308cf25cd2 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 24 Oct 2026 11:34:29 -0400 Subject: [PATCH 1/2] Remove dead checks We no longer use RESPONSE_WAIT_ACK in built-in responses; remove the unnecessary checks. --- src/WebResponses.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp index 83d3b7db..437fdea3 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -315,11 +315,6 @@ size_t AsyncBasicResponse::write_send_buffs(AsyncWebServerRequest *request, size } } - // implicit complete - if (_state == RESPONSE_WAIT_ACK) { - _state = RESPONSE_END; - } - return payloadlen; } @@ -515,12 +510,6 @@ size_t AsyncAbstractResponse::write_send_buffs(AsyncWebServerRequest *request, s return payloadlen; } // (_state == RESPONSE_CONTENT) - // implicit check - if (_state == RESPONSE_WAIT_ACK) { - // we do not need to wait for any acks actually if we won't send any more data, - // connection would be closed gracefully with last piece of data (in AsyncWebServerRequest::_onAck) - _state = RESPONSE_END; - } return 0; } From c764160d875a7a97a2d9463edff90b8a705a89df Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 24 Oct 2026 12:42:27 -0400 Subject: [PATCH 2/2] fix: Ensure all data is commited before closing Correct state tracking so the connection isn't closed until its queued to the TCP socket. --- src/WebResponseImpl.h | 2 +- src/WebResponses.cpp | 33 +++++++++++++++++++++------------ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/WebResponseImpl.h b/src/WebResponseImpl.h index 7f0d25ac..6f632abf 100644 --- a/src/WebResponseImpl.h +++ b/src/WebResponseImpl.h @@ -95,7 +95,7 @@ class AsyncAbstractResponse : public AsyncWebServerResponse { * @param request - used to access client object * @param len - size of acknowledged data from the remote side (TCP window update, not TCP ack!) * @param time - time passed between last sent and received packet - * @return size_t amount of response data placed to TCP send buffs for delivery (defined by sdkconfig value CONFIG_LWIP_TCP_SND_BUF_DEFAULT) + * @return size_t amount of data placed in the TCP send buffs for delivery (defined by sdkconfig value CONFIG_LWIP_TCP_SND_BUF_DEFAULT) */ size_t write_send_buffs(AsyncWebServerRequest *request, size_t len, uint32_t time); diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp index 437fdea3..4280f494 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -404,10 +404,15 @@ size_t AsyncAbstractResponse::write_send_buffs(AsyncWebServerRequest *request, s } // send content body - if (_state == RESPONSE_CONTENT) { + if ((_state == RESPONSE_CONTENT) || (_state == RESPONSE_WAIT_ACK)) { do { + auto tcp_win = request->client()->space(); + if (tcp_win == 0) { + break; // no room left in TCP window + } + if (_send_buffer_len && _send_buffer) { - // data is pending in buffer from a previous call or previous iteration + // data is pending in buffer from a previous call or previous iteration; attempt to send it size_t const added_len = request->client()->add(reinterpret_cast(_send_buffer->data() + _send_buffer_offset), _send_buffer_len - _send_buffer_offset); if (added_len != _send_buffer_len - _send_buffer_offset) { @@ -419,18 +424,19 @@ size_t AsyncAbstractResponse::write_send_buffs(AsyncWebServerRequest *request, s _send_buffer_len = _send_buffer_offset = 0; // consider buffer empty } payloadlen += added_len; + tcp_win -= added_len; } - auto tcp_win = request->client()->space(); - if (tcp_win == 0 || _state == RESPONSE_END) { - break; // no room left or no more data + if (_state == RESPONSE_WAIT_ACK) { + break; // all data added; exit loop } - if ((_chunked || !_sendContentLength) && (tcp_win < CONFIG_LWIP_TCP_MSS / 2)) { + if ((tcp_win == 0) || ((_chunked || !_sendContentLength) && (tcp_win < CONFIG_LWIP_TCP_MSS / 2))) { // available window size is not enough to send a new chunk sized half of tcp mss, let's wait for better chance and reduce pressure to AsyncTCP's event Q break; } + // Allocate send buffer if necessary if (!_send_buffer) { auto p = new (std::nothrow) std::array; if (p) { @@ -462,7 +468,7 @@ size_t AsyncAbstractResponse::write_send_buffs(AsyncWebServerRequest *request, s _sentLength += readLen; // data is not sent yet, but we won't get a chance to count this later properly for chunked data if (!readLen) { // last chunk? - _state = RESPONSE_END; + _state = RESPONSE_WAIT_ACK; } } } else { @@ -478,13 +484,13 @@ size_t AsyncAbstractResponse::write_send_buffs(AsyncWebServerRequest *request, s if (readLen == 0) { // no more data to send - _state = RESPONSE_END; + _state = RESPONSE_WAIT_ACK; } else if (readLen != RESPONSE_TRY_AGAIN) { _send_buffer_len += readLen; // set buffers's size to match added data _sentLength += readLen; // data is not sent yet, but we need it to understand that it would be last block if (_sendContentLength && (_sentLength == _contentLength)) { // it was last piece of content - _state = RESPONSE_END; + _state = RESPONSE_WAIT_ACK; } } } @@ -507,10 +513,13 @@ size_t AsyncAbstractResponse::write_send_buffs(AsyncWebServerRequest *request, s // buffer empty, we can release mem, otherwise need to keep it till next run (should not happen under normal conditions) _send_buffer.reset(); } - return payloadlen; - } // (_state == RESPONSE_CONTENT) + } // (_state == RESPONSE_CONTENT) || (_state == RESPONSE_WAIT_ACK) + + if ((_state == RESPONSE_WAIT_ACK) && (_send_buffer_len == 0)) { + _state = RESPONSE_END; // All done! + } - return 0; + return payloadlen; } size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t *data, const size_t len) {