Skip to content

Commit c99bca5

Browse files
committed
AsyncAbstractResponse: Use persistent packet buf
Allow the packet buffer to persist, so we can support partial writes in the event that the entire packet cannot be written at once. Includes some heuristics on ESP8266es to ensure that there's enough RAM for the heap and outgoing packet buffers.
1 parent 35799bc commit c99bca5

File tree

3 files changed

+117
-63
lines changed

3 files changed

+117
-63
lines changed

src/ESPAsyncWebServer.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -363,10 +363,10 @@ class AsyncWebServerResponse {
363363
size_t _contentLength;
364364
bool _sendContentLength;
365365
bool _chunked;
366-
size_t _headLength;
367-
size_t _sentLength;
368-
size_t _ackedLength;
369-
size_t _writtenLength;
366+
size_t _headLength; // size of header
367+
size_t _sentLength; // size of data read from source
368+
size_t _ackedLength; // size of data acked by client
369+
size_t _writtenLength; // size of data written to client
370370
WebResponseState _state;
371371
static const __FlashStringHelper* _responseCodeToString(int code);
372372

src/WebResponseImpl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class AsyncAbstractResponse: public AsyncWebServerResponse {
4646
// This is inefficient with vector, but if we use some other container,
4747
// we won't be able to access it as contiguous array of bytes when reading from it,
4848
// so by gaining performance in one place, we'll lose it in another.
49-
std::vector<uint8_t> _cache;
49+
std::vector<uint8_t> _packet, _cache;
5050
size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len);
5151
size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen);
5252
protected:

src/WebResponses.cpp

Lines changed: 112 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -278,95 +278,136 @@ void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){
278278
_ack(request, 0, 0);
279279
}
280280

281+
template<typename T>
282+
static void dealloc_vector(T& vec) {
283+
vec = T{}; // move construct an empty vector; space will be freed when scope exits
284+
}
285+
281286
size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){
282287
(void)time;
288+
283289
if(!_sourceValid()){
284290
_state = RESPONSE_FAILED;
285291
request->client()->close();
286292
return 0;
287293
}
288294
_ackedLength += len;
289-
size_t space = request->client()->space();
290295

296+
size_t space = request->client()->space(); // TCP window space available; NOT a guarantee we can actually send this much
291297
size_t headLen = _head.length();
298+
bool needs_send = false;
299+
292300
if(_state == RESPONSE_HEADERS){
293-
if(space >= headLen){
294-
_state = RESPONSE_CONTENT;
295-
space -= headLen;
296-
} else {
297-
String out = _head.substring(0, space);
298-
_head = _head.substring(space);
299-
_writtenLength += request->client()->write(out.c_str(), out.length());
300-
return out.length();
301+
auto headWritten = request->client()->add(_head.c_str(), std::min(space, headLen));
302+
_writtenLength += headWritten;
303+
if (headWritten < headLen) {
304+
_head = _head.substring(headWritten);
305+
request->client()->send();
306+
return headWritten;
301307
}
308+
_state = RESPONSE_CONTENT;
309+
space -= headWritten;
310+
_head = String(); // done
311+
needs_send = true;
302312
}
303313

304314
if(_state == RESPONSE_CONTENT){
305-
size_t outLen;
315+
if (_packet.size()) {
316+
// Complete the cached data
317+
auto written = request->client()->add((const char*) _packet.data(), std::min(space, (size_t) _packet.size()));
318+
_writtenLength += written;
319+
_packet.erase(_packet.begin(), _packet.begin() + written);
320+
space -= written;
321+
if (_packet.size()) {
322+
// Couldn't queue the full cache
323+
request->client()->send();
324+
return written;
325+
}
326+
needs_send = true;
327+
}
328+
329+
size_t outLen, readLen;
306330
if(_chunked){
307331
if(space <= 8){
308-
return 0;
332+
goto content_abort;
309333
}
310334
outLen = space;
311335
} else if(!_sendContentLength){
312336
outLen = space;
313337
} else {
314-
outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength);
315-
}
316-
317-
uint8_t *buf = (uint8_t *)malloc(outLen+headLen);
318-
if (!buf) {
319-
// os_printf("_ack malloc %d failed\n", outLen+headLen);
320-
return 0;
338+
outLen = std::min(space, _contentLength - _sentLength);
321339
}
322-
323-
if(headLen){
324-
memcpy(buf, _head.c_str(), _head.length());
340+
#ifdef ESP8266
341+
// Limit outlen based on available memory
342+
// We require two packet buffers - one allocated here, and one belonging to the TCP stack
343+
{
344+
auto old_space = _packet.capacity();
345+
auto max_block_size = ESP.getMaxFreeBlockSize() - 128;
346+
if ((old_space < outLen) || (outLen > max_block_size)) {
347+
Serial.printf_P(PSTR("Space adjustment, have %d, want %d, avail %d\n"), old_space, outLen, max_block_size);
348+
do {
349+
dealloc_vector(_packet);
350+
Serial.printf_P(PSTR("Released buffer - capacity %d\n"), _packet.capacity());
351+
outLen = std::min(outLen, max_block_size);
352+
_packet.resize(outLen);
353+
max_block_size = ESP.getMaxFreeBlockSize() - 128;
354+
Serial.printf_P(PSTR("Checking %d vs %d\n"), outLen, max_block_size);
355+
} while (max_block_size < outLen);
356+
} else {
357+
_packet.resize(outLen);
358+
}
325359
}
326-
327-
size_t readLen = 0;
328-
329-
if(_chunked){
360+
#else
361+
_packet.resize(outLen);
362+
#endif
363+
364+
if(_chunked){
330365
// HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added.
331366
// See RFC2616 sections 2, 3.6.1.
332-
readLen = _fillBufferAndProcessTemplates(buf+headLen+6, outLen - 8);
367+
readLen = _fillBufferAndProcessTemplates(_packet.data() + 6, outLen - 8);
333368
if(readLen == RESPONSE_TRY_AGAIN){
334-
free(buf);
335-
return 0;
369+
_packet.clear();
370+
goto content_abort;
336371
}
337-
outLen = sprintf((char*)buf+headLen, "%x", readLen) + headLen;
338-
while(outLen < headLen + 4) buf[outLen++] = ' ';
339-
buf[outLen++] = '\r';
340-
buf[outLen++] = '\n';
372+
outLen = sprintf((char*)_packet.data(), "%x", readLen);
373+
while(outLen < 4) _packet[outLen++] = ' ';
374+
_packet[outLen++] = '\r';
375+
_packet[outLen++] = '\n';
341376
outLen += readLen;
342-
buf[outLen++] = '\r';
343-
buf[outLen++] = '\n';
377+
_packet[outLen++] = '\r';
378+
_packet[outLen++] = '\n';
379+
344380
} else {
345-
readLen = _fillBufferAndProcessTemplates(buf+headLen, outLen);
381+
readLen = _fillBufferAndProcessTemplates(_packet.data(), _packet.size());
346382
if(readLen == RESPONSE_TRY_AGAIN){
347-
free(buf);
348-
return 0;
383+
_packet.clear();
384+
goto content_abort;
349385
}
350-
outLen = readLen + headLen;
351-
}
352-
353-
if(headLen){
354-
_head = String();
386+
outLen = readLen;
355387
}
356-
357-
if(outLen){
358-
_writtenLength += request->client()->write((const char*)buf, outLen);
359-
}
360-
361-
if(_chunked){
362-
_sentLength += readLen;
363-
} else {
364-
_sentLength += outLen - headLen;
388+
_packet.resize(outLen);
389+
390+
if(_packet.size()){
391+
auto acceptedLen = request->client()->write((const char*)_packet.data(), _packet.size());
392+
_writtenLength += acceptedLen;
393+
_packet.erase(_packet.begin(), _packet.begin() + acceptedLen); // TODO - does this realloc??
394+
if (acceptedLen < outLen) {
395+
// Save the unsent block in cache
396+
Serial.print(F("Incomplete write, ")); Serial.print(acceptedLen); Serial.print("/"); Serial.println(outLen);
397+
Serial.print(F("Heap: ")); Serial.print(ESP.getMaxFreeBlockSize()); Serial.print("/"); Serial.println(ESP.getFreeHeap());
398+
Serial.println(request->client()->space());
399+
// Try again, with less
400+
acceptedLen = request->client()->write((const char*)_packet.data(), _packet.size()/2);
401+
_writtenLength += acceptedLen;
402+
_packet.erase(_packet.begin(), _packet.begin() + acceptedLen); // TODO - does this realloc??
403+
Serial.println(acceptedLen);
404+
}
365405
}
366406

367-
free(buf);
368-
369-
if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){
407+
if( (_chunked && readLen == 0) // Chunked mode, no more data
408+
|| (!_sendContentLength && outLen == 0) // No content length, no more data
409+
|| (!_chunked && _writtenLength == (_headLength + _contentLength))) // non chunked mode, all data written
410+
{
370411
_state = RESPONSE_WAIT_ACK;
371412
}
372413
return outLen;
@@ -379,6 +420,12 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
379420
}
380421
}
381422
return 0;
423+
424+
content_abort:
425+
if (needs_send) {
426+
request->client()->send();
427+
}
428+
return 0;
382429
}
383430

384431
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len)
@@ -390,15 +437,22 @@ size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const s
390437
_cache.erase(_cache.begin(), _cache.begin() + readFromCache);
391438
}
392439
// If we need to read more...
393-
const size_t needFromFile = len - readFromCache;
394-
const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile);
395-
return readFromCache + readFromContent;
440+
if (len > readFromCache) {
441+
const size_t needFromFile = len - readFromCache;
442+
const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile);
443+
if (readFromContent != RESPONSE_TRY_AGAIN) {
444+
_sentLength += readFromContent;
445+
return readFromCache + readFromContent;
446+
}
447+
if (readFromCache == 0) return readFromContent;
448+
}
449+
return readFromCache;
396450
}
397451

398452
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len)
399453
{
400454
if(!_callback)
401-
return _fillBuffer(data, len);
455+
return _readDataFromCacheOrContent(data, len);
402456

403457
const size_t originalLen = len;
404458
len = _readDataFromCacheOrContent(data, len);

0 commit comments

Comments
 (0)