Skip to content

Commit 0b6bab6

Browse files
vortigontmathieucarbou
authored andcommitted
fix: AsyncAbstractResponse might loose part of send buffer
AsyncAbstractResponse::_ack could allocate temp buffer with size larger than available sock buffer (i.e. to fit headers) and eventually lossing the remainder on transfer due to not checking if the complete data was added to sock buff. Refactoring code in favor of having a dedicated std::vector object acting as accumulating buffer and more carefull control on amount of data actually copied to sockbuff Closes #315
1 parent 37933e3 commit 0b6bab6

File tree

4 files changed

+272
-220
lines changed

4 files changed

+272
-220
lines changed

src/ESPAsyncWebServer.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1336,8 +1336,10 @@ class AsyncWebServerResponse {
13361336
bool _sendContentLength;
13371337
bool _chunked;
13381338
size_t _headLength;
1339+
// amount of data sent for content part of the response (excluding all headers)
13391340
size_t _sentLength;
13401341
size_t _ackedLength;
1342+
// amount of response bytes (including all headers) written to sockbuff for delivery
13411343
size_t _writtenLength;
13421344
WebResponseState _state;
13431345

@@ -1394,7 +1396,20 @@ class AsyncWebServerResponse {
13941396
virtual bool _failed() const;
13951397
virtual bool _sourceValid() const;
13961398
virtual void _respond(AsyncWebServerRequest *request);
1397-
virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
1399+
1400+
/**
1401+
* @brief write next portion of response data to send buffs
1402+
* this method (re)fills tcp send buffers, it could be called either at will
1403+
* or from a tcp_recv/tcp_poll callbacks from AsyncTCP
1404+
*
1405+
* @param request - used to access client object
1406+
* @param len - size of acknowledged data from the remote side (TCP window update, not TCP ack!)
1407+
* @param time - time passed between last sent and received packet
1408+
* @return size_t amount of response data placed to TCP send buffs for delivery (defined by sdkconfig value CONFIG_LWIP_TCP_SND_BUF_DEFAULT)
1409+
*/
1410+
virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
1411+
return 0;
1412+
};
13981413
};
13991414

14001415
/*

src/WebRequest.cpp

Lines changed: 33 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -33,66 +33,66 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
3333
[](void *r, AsyncClient *c, int8_t error) {
3434
(void)c;
3535
// async_ws_log_e("AsyncWebServerRequest::_onError");
36-
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
37-
req->_onError(error);
36+
static_cast<AsyncWebServerRequest *>(r)->_onError(error);
3837
},
3938
this
4039
);
4140
c->onAck(
4241
[](void *r, AsyncClient *c, size_t len, uint32_t time) {
4342
(void)c;
4443
// async_ws_log_e("AsyncWebServerRequest::_onAck");
45-
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
46-
req->_onAck(len, time);
44+
static_cast<AsyncWebServerRequest *>(r)->_onAck(len, time);
4745
},
4846
this
4947
);
5048
c->onDisconnect(
5149
[](void *r, AsyncClient *c) {
5250
// async_ws_log_e("AsyncWebServerRequest::_onDisconnect");
53-
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
54-
req->_onDisconnect();
55-
delete c;
51+
static_cast<AsyncWebServerRequest *>(r)->_onDisconnect();
5652
},
5753
this
5854
);
5955
c->onTimeout(
6056
[](void *r, AsyncClient *c, uint32_t time) {
6157
(void)c;
6258
// async_ws_log_e("AsyncWebServerRequest::_onTimeout");
63-
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
64-
req->_onTimeout(time);
59+
static_cast<AsyncWebServerRequest *>(r)->_onTimeout(time);
6560
},
6661
this
6762
);
6863
c->onData(
6964
[](void *r, AsyncClient *c, void *buf, size_t len) {
7065
(void)c;
7166
// async_ws_log_e("AsyncWebServerRequest::_onData");
72-
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
73-
req->_onData(buf, len);
67+
static_cast<AsyncWebServerRequest *>(r)->_onData(buf, len);
7468
},
7569
this
7670
);
7771
c->onPoll(
7872
[](void *r, AsyncClient *c) {
7973
(void)c;
8074
// async_ws_log_e("AsyncWebServerRequest::_onPoll");
81-
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
82-
req->_onPoll();
75+
static_cast<AsyncWebServerRequest *>(r)->_onPoll();
8376
},
8477
this
8578
);
8679
}
8780

8881
AsyncWebServerRequest::~AsyncWebServerRequest() {
89-
// async_ws_log_e("AsyncWebServerRequest::~AsyncWebServerRequest");
82+
if (_client) {
83+
// usually it is _client's disconnect triggers object destruct, but for completeness we define behavior
84+
// if for some reason *this will be destructed while client is still connected
85+
_client->onDisconnect(nullptr);
86+
delete _client;
87+
_client = nullptr;
88+
}
9089

91-
_this.reset();
90+
if (_response) {
91+
delete _response;
92+
_response = nullptr;
93+
}
9294

93-
AsyncWebServerResponse *r = _response;
94-
_response = NULL;
95-
delete r;
95+
_this.reset();
9696

9797
if (_tempObject != NULL) {
9898
free(_tempObject);
@@ -217,31 +217,26 @@ void AsyncWebServerRequest::_onData(void *buf, size_t len) {
217217

218218
void AsyncWebServerRequest::_onPoll() {
219219
// os_printf("p\n");
220-
if (_response != NULL && _client != NULL && _client->canSend()) {
221-
if (!_response->_finished()) {
222-
_response->_ack(this, 0, 0);
223-
} else {
224-
AsyncWebServerResponse *r = _response;
225-
_response = NULL;
226-
delete r;
227-
228-
_client->close();
229-
}
220+
if (_response && _client && _client->canSend()) {
221+
_response->_ack(this, 0, 0);
230222
}
231223
}
232224

233225
void AsyncWebServerRequest::_onAck(size_t len, uint32_t time) {
234226
// os_printf("a:%u:%u\n", len, time);
235-
if (_response != NULL) {
236-
if (!_response->_finished()) {
237-
_response->_ack(this, len, time);
238-
} else if (_response->_finished()) {
239-
AsyncWebServerResponse *r = _response;
240-
_response = NULL;
241-
delete r;
242-
243-
_client->close();
227+
if (!_response) {
228+
return;
229+
}
230+
231+
if (!_response->_finished()) {
232+
_response->_ack(this, len, time);
233+
// recheck if response has just completed, close connection
234+
if (_response->_finished()) {
235+
_client->close(); // this will trigger _onDisconnect() and object destruction
244236
}
237+
} else {
238+
// this will close responses that were complete via a single _send() call
239+
_client->close(); // this will trigger _onDisconnect() and object destruction
245240
}
246241
}
247242

@@ -709,7 +704,7 @@ void AsyncWebServerRequest::_send() {
709704
send(500, T_text_plain, "Invalid data in handler");
710705
}
711706

712-
// here, we either have a response give nfrom user or one of the two above
707+
// here, we either have a response given from user or one of the two above
713708
_client->setRxTimeout(0);
714709
_response->_respond(this);
715710
_sent = true;

src/WebResponseImpl.h

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,46 @@
1414
#include <memory>
1515
#include <vector>
1616

17+
#ifndef CONFIG_LWIP_TCP_MSS
18+
// as it is defined for ESP32's Arduino LWIP
19+
#define CONFIG_LWIP_TCP_MSS 1436
20+
#endif
21+
22+
#define ASYNC_RESPONCE_BUFF_SIZE CONFIG_LWIP_TCP_MSS * 2
1723
// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max.
1824

1925
class AsyncBasicResponse : public AsyncWebServerResponse {
2026
private:
2127
String _content;
28+
// buffer to accumulate all response headers
29+
String _assembled_headers;
30+
// amount of headers buffer writtent to sockbuff
31+
size_t _writtenHeadersLength{0};
2232

2333
public:
2434
explicit AsyncBasicResponse(int code, const char *contentType = asyncsrv::empty, const char *content = asyncsrv::empty);
2535
AsyncBasicResponse(int code, const String &contentType, const String &content = emptyString)
2636
: AsyncBasicResponse(code, contentType.c_str(), content.c_str()) {}
2737
void _respond(AsyncWebServerRequest *request) override final;
28-
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override final;
38+
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override final {
39+
return write_send_buffs(request, len, time);
40+
};
2941
bool _sourceValid() const override final {
3042
return true;
3143
}
44+
45+
protected:
46+
/**
47+
* @brief write next portion of response data to send buffs
48+
* this method (re)fills tcp send buffers, it could be called either at will
49+
* or from a tcp_recv/tcp_poll callbacks from AsyncTCP
50+
*
51+
* @param request - used to access client object
52+
* @param len - size of acknowledged data from the remote side (TCP window update, not TCP ack!)
53+
* @param time - time passed between last sent and received packet
54+
* @return size_t amount of response data placed to TCP send buffs for delivery (defined by sdkconfig value CONFIG_LWIP_TCP_SND_BUF_DEFAULT)
55+
*/
56+
size_t write_send_buffs(AsyncWebServerRequest *request, size_t len, uint32_t time);
3257
};
3358

3459
class AsyncAbstractResponse : public AsyncWebServerResponse {
@@ -39,23 +64,43 @@ class AsyncAbstractResponse : public AsyncWebServerResponse {
3964
// in-flight queue credits
4065
size_t _in_flight_credit{2};
4166
#endif
42-
String _head;
67+
// buffer to accumulate all response headers
68+
String _assembled_headers;
69+
// amount of headers buffer writtent to sockbuff
70+
size_t _writtenHeadersLength{0};
4371
// Data is inserted into cache at begin().
4472
// This is inefficient with vector, but if we use some other container,
4573
// we won't be able to access it as contiguous array of bytes when reading from it,
4674
// so by gaining performance in one place, we'll lose it in another.
4775
std::vector<uint8_t> _cache;
76+
// intermediate buffer to copy outbound data to, also it will keep pending data between _send calls
77+
std::unique_ptr<std::array<uint8_t, ASYNC_RESPONCE_BUFF_SIZE> > _send_buffer;
78+
// buffer data size specifiers
79+
size_t _send_buffer_offset{0}, _send_buffer_len{0};
4880
size_t _readDataFromCacheOrContent(uint8_t *data, const size_t len);
4981
size_t _fillBufferAndProcessTemplates(uint8_t *buf, size_t maxLen);
5082

5183
protected:
5284
AwsTemplateProcessor _callback;
85+
/**
86+
* @brief write next portion of response data to send buffs
87+
* this method (re)fills tcp send buffers, it could be called either at will
88+
* or from a tcp_recv/tcp_poll callbacks from AsyncTCP
89+
*
90+
* @param request - used to access client object
91+
* @param len - size of acknowledged data from the remote side (TCP window update, not TCP ack!)
92+
* @param time - time passed between last sent and received packet
93+
* @return size_t amount of response data placed to TCP send buffs for delivery (defined by sdkconfig value CONFIG_LWIP_TCP_SND_BUF_DEFAULT)
94+
*/
95+
size_t write_send_buffs(AsyncWebServerRequest *request, size_t len, uint32_t time);
5396

5497
public:
5598
AsyncAbstractResponse(AwsTemplateProcessor callback = nullptr);
5699
virtual ~AsyncAbstractResponse() {}
57100
void _respond(AsyncWebServerRequest *request) override final;
58-
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override final;
101+
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override final {
102+
return write_send_buffs(request, len, time);
103+
};
59104
virtual bool _sourceValid() const {
60105
return false;
61106
}
@@ -142,7 +187,8 @@ class AsyncChunkedResponse : public AsyncAbstractResponse {
142187
class AsyncProgmemResponse : public AsyncAbstractResponse {
143188
private:
144189
const uint8_t *_content;
145-
size_t _readLength;
190+
// offset index (how much we've sent already)
191+
size_t _index;
146192

147193
public:
148194
AsyncProgmemResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr);

0 commit comments

Comments
 (0)