Skip to content

Commit 1af9b47

Browse files
committed
LinkedLists: Remove extra indirection
This reduces the total number of allocations required for every request. Also includes a pre-allocation optimization to AsyncWebServerResponse::_assembleHead and some more migration to PROGMEM strings.
1 parent c454457 commit 1af9b47

File tree

3 files changed

+76
-60
lines changed

3 files changed

+76
-60
lines changed

src/ESPAsyncWebServer.h

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,9 @@ class AsyncWebServerRequest {
163163
size_t _contentLength;
164164
size_t _parsedLength;
165165

166-
LinkedList<AsyncWebHeader *> _headers;
167-
LinkedList<AsyncWebParameter *> _params;
168-
LinkedList<String *> _pathParams;
166+
LinkedList<AsyncWebHeader> _headers;
167+
LinkedList<AsyncWebParameter> _params;
168+
LinkedList<String> _pathParams;
169169

170170
uint8_t _multiParseState;
171171
uint8_t _boundaryPosition;
@@ -186,7 +186,7 @@ class AsyncWebServerRequest {
186186
void _onDisconnect();
187187
void _onData(void *buf, size_t len);
188188

189-
void _addParam(AsyncWebParameter*);
189+
void _addParam(AsyncWebParameter);
190190
void _addPathParam(const char *param);
191191

192192
bool _parseReqHead();
@@ -358,7 +358,7 @@ typedef enum {
358358
class AsyncWebServerResponse {
359359
protected:
360360
int _code;
361-
LinkedList<AsyncWebHeader *> _headers;
361+
LinkedList<AsyncWebHeader> _headers;
362362
String _contentType;
363363
size_t _contentLength;
364364
bool _sendContentLength;
@@ -376,7 +376,7 @@ class AsyncWebServerResponse {
376376
virtual void setCode(int code);
377377
virtual void setContentLength(size_t len);
378378
virtual void setContentType(const String& type);
379-
virtual void addHeader(const String& name, const String& value);
379+
virtual void addHeader(String name, String value);
380380
virtual String _assembleHead(uint8_t version);
381381
virtual bool _started() const;
382382
virtual bool _finished() const;
@@ -440,17 +440,17 @@ class AsyncWebServer {
440440
};
441441

442442
class DefaultHeaders {
443-
using headers_t = LinkedList<AsyncWebHeader *>;
443+
using headers_t = LinkedList<AsyncWebHeader>;
444444
headers_t _headers;
445445

446446
DefaultHeaders()
447-
:_headers(headers_t([](AsyncWebHeader *h){ delete h; }))
447+
: _headers({})
448448
{}
449449
public:
450450
using ConstIterator = headers_t::ConstIterator;
451451

452-
void addHeader(const String& name, const String& value){
453-
_headers.add(new AsyncWebHeader(name, value));
452+
void addHeader(String name, String value){
453+
_headers.add(AsyncWebHeader(std::move(name), std::move(value)));
454454
}
455455

456456
ConstIterator begin() const { return _headers.begin(); }

src/WebRequest.cpp

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c)
5353
, _expectingContinue(false)
5454
, _contentLength(0)
5555
, _parsedLength(0)
56-
, _headers(LinkedList<AsyncWebHeader *>([](AsyncWebHeader *h){ delete h; }))
57-
, _params(LinkedList<AsyncWebParameter *>([](AsyncWebParameter *p){ delete p; }))
58-
, _pathParams(LinkedList<String *>([](String *p){ delete p; }))
56+
, _headers({})
57+
, _params({})
58+
, _pathParams({})
5959
, _multiParseState(0)
6060
, _boundaryPosition(0)
6161
, _itemStartIndex(0)
@@ -182,7 +182,7 @@ void AsyncWebServerRequest::_removeNotInterestingHeaders(){
182182
if (_interestingHeaders.containsIgnoreCase("ANY")) return; // nothing to do
183183
auto prev = decltype(_headers.begin()) { nullptr };
184184
for(auto it = _headers.begin(); it != _headers.end(); prev = it, ++it){
185-
if(!_interestingHeaders.containsIgnoreCase((*it)->name().c_str())){
185+
if(!_interestingHeaders.containsIgnoreCase(it->name().c_str())){
186186
_headers.remove(it, prev);
187187
it = prev;
188188
}
@@ -231,12 +231,12 @@ void AsyncWebServerRequest::_onDisconnect(){
231231
_server->_handleDisconnect(this);
232232
}
233233

234-
void AsyncWebServerRequest::_addParam(AsyncWebParameter *p){
235-
_params.add(p);
234+
void AsyncWebServerRequest::_addParam(AsyncWebParameter p){
235+
_params.add(std::move(p));
236236
}
237237

238238
void AsyncWebServerRequest::_addPathParam(const char *p){
239-
_pathParams.add(new String(p));
239+
_pathParams.add(String(p));
240240
}
241241

242242
void AsyncWebServerRequest::_addGetParams(const String& params){
@@ -248,7 +248,7 @@ void AsyncWebServerRequest::_addGetParams(const String& params){
248248
if (equal < 0 || equal > end) equal = end;
249249
String name = params.substring(start, equal);
250250
String value = equal + 1 < end ? params.substring(equal + 1, end) : String();
251-
_addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value)));
251+
_addParam(AsyncWebParameter(urlDecode(name), urlDecode(value)));
252252
start = end + 1;
253253
}
254254
}
@@ -316,38 +316,38 @@ bool AsyncWebServerRequest::_parseReqHeader(){
316316
if(index){
317317
String name = _temp.substring(0, index);
318318
String value = _temp.substring(index + 2);
319-
if(name.equalsIgnoreCase("Host")){
319+
if(name.equalsIgnoreCase(F("Host"))){
320320
_host = value;
321-
} else if(name.equalsIgnoreCase("Content-Type")){
321+
} else if(name.equalsIgnoreCase(F("Content-Type"))){
322322
_contentType = value.substring(0, value.indexOf(';'));
323-
if (value.startsWith("multipart/")){
323+
if (value.startsWith(F("multipart/"))){
324324
_boundary = value.substring(value.indexOf('=')+1);
325325
_boundary.replace("\"","");
326326
_isMultipart = true;
327327
}
328-
} else if(name.equalsIgnoreCase("Content-Length")){
328+
} else if(name.equalsIgnoreCase(F("Content-Length"))){
329329
_contentLength = atoi(value.c_str());
330-
} else if(name.equalsIgnoreCase("Expect") && value == "100-continue"){
330+
} else if(name.equalsIgnoreCase(F("Expect")) && value == F("100-continue")){
331331
_expectingContinue = true;
332-
} else if(name.equalsIgnoreCase("Authorization")){
333-
if(value.length() > 5 && value.substring(0,5).equalsIgnoreCase("Basic")){
332+
} else if(name.equalsIgnoreCase(F("Authorization"))){
333+
if(value.length() > 5 && value.substring(0,5).equalsIgnoreCase(F("Basic"))){
334334
_authorization = value.substring(6);
335-
} else if(value.length() > 6 && value.substring(0,6).equalsIgnoreCase("Digest")){
335+
} else if(value.length() > 6 && value.substring(0,6).equalsIgnoreCase(F("Digest"))){
336336
_isDigest = true;
337337
_authorization = value.substring(7);
338338
}
339339
} else {
340-
if(name.equalsIgnoreCase("Upgrade") && value.equalsIgnoreCase("websocket")){
340+
if(name.equalsIgnoreCase(F("Upgrade")) && value.equalsIgnoreCase(F("websocket"))){
341341
// WebSocket request can be uniquely identified by header: [Upgrade: websocket]
342342
_reqconntype = RCT_WS;
343343
} else {
344-
if(name.equalsIgnoreCase("Accept") && strContains(value, "text/event-stream", false)){
344+
if(name.equalsIgnoreCase(F("Accept")) && strContains(value, F("text/event-stream"), false)){
345345
// WebEvent request can be uniquely identified by header: [Accept: text/event-stream]
346346
_reqconntype = RCT_EVENT;
347347
}
348348
}
349349
}
350-
_headers.add(new AsyncWebHeader(name, value));
350+
_headers.add(AsyncWebHeader(name, value));
351351
}
352352
_temp = String();
353353
return true;
@@ -357,13 +357,13 @@ void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data){
357357
if(data && (char)data != '&')
358358
_temp += (char)data;
359359
if(!data || (char)data == '&' || _parsedLength == _contentLength){
360-
String name = "body";
360+
String name = F("body");
361361
String value = _temp;
362362
if(!_temp.startsWith("{") && !_temp.startsWith("[") && _temp.indexOf('=') > 0){
363363
name = _temp.substring(0, _temp.indexOf('='));
364364
value = _temp.substring(_temp.indexOf('=') + 1);
365365
}
366-
_addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value), true));
366+
_addParam(AsyncWebParameter(urlDecode(name), urlDecode(value), true));
367367
_temp = String();
368368
}
369369
}
@@ -510,13 +510,13 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last){
510510
} else if(_boundaryPosition == _boundary.length() - 1){
511511
_multiParseState = DASH3_OR_RETURN2;
512512
if(!_itemIsFile){
513-
_addParam(new AsyncWebParameter(_itemName, _itemValue, true));
513+
_addParam(AsyncWebParameter(_itemName, _itemValue, true));
514514
} else {
515515
if(_itemSize){
516516
//check if authenticated before calling the upload
517517
if(_handler) _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true);
518518
_itemBufferIndex = 0;
519-
_addParam(new AsyncWebParameter(_itemName, _itemFilename, true, true, _itemSize));
519+
_addParam(AsyncWebParameter(_itemName, _itemFilename, true, true, _itemSize));
520520
}
521521
free(_itemBuffer);
522522
_itemBuffer = NULL;
@@ -593,7 +593,7 @@ size_t AsyncWebServerRequest::headers() const{
593593

594594
bool AsyncWebServerRequest::hasHeader(const String& name) const {
595595
for(const auto& h: _headers){
596-
if(h->name().equalsIgnoreCase(name)){
596+
if(h.name().equalsIgnoreCase(name)){
597597
return true;
598598
}
599599
}
@@ -621,9 +621,9 @@ bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper * data) const {
621621
}
622622

623623
AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) const {
624-
for(const auto& h: _headers){
625-
if(h->name().equalsIgnoreCase(name)){
626-
return h;
624+
for(auto& h: _headers){
625+
if(h.name().equalsIgnoreCase(name)){
626+
return const_cast<AsyncWebHeader*>(&h); // maintain previous interface
627627
}
628628
}
629629
return nullptr;
@@ -644,8 +644,7 @@ AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper * dat
644644
}
645645

646646
AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const {
647-
auto header = _headers.nth(num);
648-
return header ? *header : nullptr;
647+
return const_cast<AsyncWebHeader*>(_headers.nth(num)); // maintain previous interface
649648
}
650649

651650
size_t AsyncWebServerRequest::params() const {
@@ -654,7 +653,7 @@ size_t AsyncWebServerRequest::params() const {
654653

655654
bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) const {
656655
for(const auto& p: _params){
657-
if(p->name() == name && p->isPost() == post && p->isFile() == file){
656+
if(p.name() == name && p.isPost() == post && p.isFile() == file){
658657
return true;
659658
}
660659
}
@@ -679,8 +678,8 @@ bool AsyncWebServerRequest::hasParam(const __FlashStringHelper * data, bool post
679678

680679
AsyncWebParameter* AsyncWebServerRequest::getParam(const String& name, bool post, bool file) const {
681680
for(const auto& p: _params){
682-
if(p->name() == name && p->isPost() == post && p->isFile() == file){
683-
return p;
681+
if(p.name() == name && p.isPost() == post && p.isFile() == file){
682+
return const_cast<AsyncWebParameter*>(&p); // maintain previous interface
684683
}
685684
}
686685
return nullptr;
@@ -701,8 +700,7 @@ AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelper * d
701700
}
702701

703702
AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const {
704-
auto param = _params.nth(num);
705-
return param ? *param : nullptr;
703+
return const_cast<AsyncWebParameter*>(_params.nth(num)); // maintain previous interface
706704
}
707705

708706
void AsyncWebServerRequest::addInterestingHeader(const String& name){
@@ -865,7 +863,7 @@ void AsyncWebServerRequest::requestAuthentication(const char * realm, bool isDig
865863

866864
bool AsyncWebServerRequest::hasArg(const char* name) const {
867865
for(const auto& arg: _params){
868-
if(arg->name() == name){
866+
if(arg.name() == name){
869867
return true;
870868
}
871869
}
@@ -889,8 +887,8 @@ bool AsyncWebServerRequest::hasArg(const __FlashStringHelper * data) const {
889887

890888
const String& AsyncWebServerRequest::arg(const String& name) const {
891889
for(const auto& arg: _params){
892-
if(arg->name() == name){
893-
return arg->value();
890+
if(arg.name() == name){
891+
return arg.value();
894892
}
895893
}
896894
return SharedEmptyString;
@@ -921,7 +919,7 @@ const String& AsyncWebServerRequest::argName(size_t i) const {
921919

922920
const String& AsyncWebServerRequest::pathArg(size_t i) const {
923921
auto param = _pathParams.nth(i);
924-
return param ? **param : SharedEmptyString;
922+
return param ? *param : SharedEmptyString;
925923
}
926924

927925
const String& AsyncWebServerRequest::header(const char* name) const {

src/WebResponses.cpp

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ const __FlashStringHelper* AsyncWebServerResponse::_responseCodeToString(int cod
8484

8585
AsyncWebServerResponse::AsyncWebServerResponse()
8686
: _code(0)
87-
, _headers(LinkedList<AsyncWebHeader *>([](AsyncWebHeader *h){ delete h; }))
87+
, _headers({})
8888
, _contentType()
8989
, _contentLength(0)
9090
, _sendContentLength(true)
@@ -95,8 +95,8 @@ AsyncWebServerResponse::AsyncWebServerResponse()
9595
, _writtenLength(0)
9696
, _state(RESPONSE_SETUP)
9797
{
98-
for(auto header: DefaultHeaders::Instance()) {
99-
_headers.add(new AsyncWebHeader(header->name(), header->value()));
98+
for(auto& header: DefaultHeaders::Instance()) {
99+
_headers.add(header);
100100
}
101101
}
102102

@@ -119,18 +119,33 @@ void AsyncWebServerResponse::setContentType(const String& type){
119119
_contentType = type;
120120
}
121121

122-
void AsyncWebServerResponse::addHeader(const String& name, const String& value){
123-
_headers.add(new AsyncWebHeader(name, value));
122+
void AsyncWebServerResponse::addHeader(String name, String value){
123+
_headers.add(AsyncWebHeader(std::move(name), std::move(value)));
124124
}
125125

126126
String AsyncWebServerResponse::_assembleHead(uint8_t version){
127127
if(version){
128-
addHeader(F("Accept-Ranges"),F("none"));
128+
addHeader(F("Accept-Ranges"), F("none"));
129129
if(_chunked)
130-
addHeader(F("Transfer-Encoding"),F("chunked"));
130+
addHeader(F("Transfer-Encoding"), F("chunked"));
131131
}
132+
133+
// Precalculate the output header block length
134+
size_t est_header_size = 10 + 4 + 2; // HTTP://1.version code + newlines
135+
est_header_size += strlen_P((const char*) _responseCodeToString(_code));
136+
if(_sendContentLength) {
137+
est_header_size += 18 + 10; // GBs ought to be enough for anyone
138+
};
139+
if (_contentType.length()) {
140+
est_header_size += 16 + _contentType.length();
141+
}
142+
for(const auto& header: _headers) {
143+
est_header_size += header.name().length() + header.value().length() + 4;
144+
};
145+
132146
String out = String();
133-
int bufSize = 300;
147+
out.reserve(est_header_size);
148+
const static int bufSize = 300;
134149
char buf[bufSize];
135150

136151
snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d "), version, _code);
@@ -148,7 +163,7 @@ String AsyncWebServerResponse::_assembleHead(uint8_t version){
148163
}
149164

150165
for(const auto& header: _headers){
151-
snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header->name().c_str(), header->value().c_str());
166+
snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header.name().c_str(), header.value().c_str());
152167
out.concat(buf);
153168
}
154169
_headers.free();
@@ -484,6 +499,9 @@ AsyncFileResponse::~AsyncFileResponse(){
484499
_content.close();
485500
}
486501

502+
const static char GZIP_EXTENSION_PMEM[] PROGMEM = ".gz";
503+
const static auto GZIP_EXTENSION = FPSTR(GZIP_EXTENSION_PMEM);
504+
487505
void AsyncFileResponse::_setContentType(const String& path){
488506
if (path.endsWith(F(".html"))) _contentType = F("text/html");
489507
else if (path.endsWith(F(".htm"))) _contentType = F("text/html");
@@ -502,13 +520,13 @@ void AsyncFileResponse::_setContentType(const String& path){
502520
else if (path.endsWith(F(".xml"))) _contentType = F("text/xml");
503521
else if (path.endsWith(F(".pdf"))) _contentType = F("application/pdf");
504522
else if (path.endsWith(F(".zip"))) _contentType = F("application/zip");
505-
else if(path.endsWith(F(".gz"))) _contentType = F("application/x-gzip");
523+
else if(path.endsWith(GZIP_EXTENSION)) _contentType = F("application/x-gzip");
506524
else _contentType = F("text/plain");
507525
}
508526

509527
static File fs_open_zipped(FS& fs, const String& path, bool force_absolute) {
510528
if (!force_absolute && !fs.exists(path)) {
511-
auto gz_path = path + F(".gz");
529+
auto gz_path = path + GZIP_EXTENSION;
512530
if (fs.exists(gz_path)) return fs.open(gz_path, "r");
513531
}
514532
return fs.open(path, "r");
@@ -521,7 +539,7 @@ AsyncFileResponse::AsyncFileResponse(File content, const String& path, const Str
521539
_code = 200;
522540
_path = path;
523541

524-
if(!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))){
542+
if(!download && String(content.name()).endsWith(GZIP_EXTENSION) && !path.endsWith(GZIP_EXTENSION)){
525543
addHeader(F("Content-Encoding"), F("gzip"));
526544
_callback = nullptr; // Unable to process gzipped templates
527545
_sendContentLength = true;

0 commit comments

Comments
 (0)