From 755021da38c58ed700ad3171f70440e0b4717c05 Mon Sep 17 00:00:00 2001 From: David Gauchard Date: Tue, 28 Feb 2023 01:14:49 +0100 Subject: [PATCH 1/3] avoid large reallocations in webserver header response --- .../src/ESP8266WebServer-impl.h | 106 +++++++++++------- .../ESP8266WebServer/src/ESP8266WebServer.h | 14 ++- 2 files changed, 78 insertions(+), 42 deletions(-) diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h b/libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h index eca341aee5..b392295a8a 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h @@ -274,7 +274,7 @@ void ESP8266WebServerTemplate::serveStatic(const char* uri, FS& fs, if(is_file) { _addRequestHandler(new StaticFileRequestHandler(fs, path, uri, cache_header)); } else { - _addRequestHandler(new StaticDirectoryRequestHandler(fs, path, uri, cache_header)); + _addRequestHandler(new StaticDirectoryRequestHandler(fs, path, uri, cache_header)); } } @@ -420,18 +420,11 @@ void ESP8266WebServerTemplate::stop() { } template -void ESP8266WebServerTemplate::sendHeader(const String& name, const String& value, bool first) { - String headerLine = name; - headerLine += F(": "); - headerLine += value; - headerLine += "\r\n"; - - if (first) { - _responseHeaders = headerLine + _responseHeaders; - } - else { - _responseHeaders += headerLine; - } +void ESP8266WebServerTemplate::sendHeader(String&& name, String&& value, bool first) { + if (first) + _userHeaders.emplace_front(std::pair(name, value)); + else + _userHeaders.emplace_back(std::pair(name, value)); } template @@ -440,44 +433,80 @@ void ESP8266WebServerTemplate::setContentLength(const size_t content } template -void ESP8266WebServerTemplate::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) { - response = String(F("HTTP/1.")) + String(_currentVersion) + ' '; - response += String(code); - response += ' '; - response += responseCodeToString(code); - response += "\r\n"; +bool ESP8266WebServerTemplate::_streamIt (Stream& s) { + int len = s.streamRemaining(); + int sent = s.sendAll(&_currentClient); + if (sent == len) + return true; + DBGWS("HTTPServer: error: sent %zd on %u bytes\n", sent, len); + return false; +} + +template +template +bool ESP8266WebServerTemplate::_streamHeader (K name, V value) +{ + return _streamIt(name) && _streamItC(F(": ")) && _streamIt(value) && _streamItC(F("\r\n")); +} + +template +bool ESP8266WebServerTemplate::_sendHeader(int code, const char* content_type, size_t contentLength) { + if ( !_streamItC(F("HTTP/1.")) + || !_streamIt(String(_currentVersion)) + || !_streamItC(F(" ")) + || !_streamIt(String(code)) + || !_streamItC(F(" ")) + || !_streamIt(responseCodeToString(code)) + || !_streamItC(F("\r\n"))) + { + return false; + } using namespace mime; if (!content_type) content_type = mimeTable[html].mimeType; + if (!_streamHeader(F("Content-Type"), content_type)) + return false; - sendHeader(String(F("Content-Type")), String(FPSTR(content_type)), true); if (_contentLength == CONTENT_LENGTH_NOT_SET) { - sendHeader(String(FPSTR(Content_Length)), String(contentLength)); + if (!_streamHeader(Content_Length, String(contentLength))) + return false; } else if (_contentLength != CONTENT_LENGTH_UNKNOWN) { - sendHeader(String(FPSTR(Content_Length)), String(_contentLength)); - } else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client - //let's do chunked - _chunked = true; - sendHeader(String(F("Accept-Ranges")),String(F("none"))); - sendHeader(String(F("Transfer-Encoding")),String(F("chunked"))); + if (!_streamHeader(Content_Length, String(_contentLength))) + return false; + } else if (_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client + //let's do chunked + _chunked = true; + if (!_streamHeader(F("Accept-Ranges"), F("none")) || !_streamHeader(F("Transfer-Encoding"), F("chunked"))) + return false; } + if (_corsEnabled) { - sendHeader(String(F("Access-Control-Allow-Origin")), String("*")); + if (!_streamHeader(F("Access-Control-Allow-Origin"), F("*"))) + return false; } - if (_keepAlive && _server.hasClient()) { // Disable keep alive if another client is waiting. - _keepAlive = false; + if (_keepAlive && _server.hasClient()) { + // Disable keep alive if another client is waiting. + _keepAlive = false; + } + if (!_streamHeader(F("Connection"), String(_keepAlive ? F("keep-alive") : F("close")))) { + return false; } - sendHeader(String(F("Connection")), String(_keepAlive ? F("keep-alive") : F("close"))); if (_keepAlive) { - sendHeader(String(F("Keep-Alive")), String(F("timeout=")) + HTTP_MAX_CLOSE_WAIT); + if (!_streamHeader(F("Keep-Alive"), String(F("timeout=")) + HTTP_MAX_CLOSE_WAIT)) + return false; } + for (const auto& kv: _userHeaders) + if (!_streamHeader(kv.first, kv.second)) + return false; + _userHeaders.clear(); - response += _responseHeaders; - response += "\r\n"; - _responseHeaders = ""; + if (!_streamItC(F("\r\n"))) + return false; + + return true; } template @@ -503,13 +532,10 @@ void ESP8266WebServerTemplate::sendContent(const String& content) { template void ESP8266WebServerTemplate::send(int code, const char* content_type, Stream* stream, size_t content_length /*= 0*/) { - String header; if (content_length == 0) content_length = std::max((ssize_t)0, stream->streamRemaining()); - _prepareHeader(header, code, content_type, content_length); - size_t sent = StreamConstPtr(header).sendAll(&_currentClient); - if (sent != header.length()) - DBGWS("HTTPServer: error: sent %zd on %u bytes\n", sent, header.length()); + if (!_sendHeader(code, content_type, content_length)) + return; if (content_length) return sendContent(stream, content_length); } diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.h b/libraries/ESP8266WebServer/src/ESP8266WebServer.h index e0dba27a90..1d8584cfe1 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.h @@ -27,8 +27,11 @@ #include #include #include +#include + #include #include +#include #include "detail/mimetable.h" #include "Uri.h" @@ -176,6 +179,7 @@ class ESP8266WebServerTemplate } void setContentLength(const size_t contentLength); + void sendHeader(String&& name, String&& value, bool first = false); void sendHeader(const String& name, const String& value, bool first = false); void sendContent(const String& content); void sendContent(String& content) { @@ -291,7 +295,7 @@ class ESP8266WebServerTemplate bool _parseFormUploadAborted(); void _uploadWriteByte(uint8_t b); int _uploadReadByte(ClientType& client); - void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength); + bool _sendHeader(int code, const char* content_type, size_t contentLength); bool _collectHeader(const char* headerName, const char* headerValue); void _streamFileCore(const size_t fileSize, const String & fileName, const String & contentType); @@ -300,6 +304,12 @@ class ESP8266WebServerTemplate // for extracting Auth parameters String _extractParam(String& authReq,const String& param,const char delimit = '"') const; + bool _streamIt (const String& s) { StreamConstPtr c(s.c_str(), s.length()); return _streamIt(c); } + bool _streamItC (StreamConstPtr&& s) { return _streamIt(s); } + bool _streamIt (Stream& s); + template + bool _streamHeader (K name, V value); + struct RequestArgument { String key; String value; @@ -330,7 +340,7 @@ class ESP8266WebServerTemplate RequestArgument* _currentHeaders = nullptr; size_t _contentLength = 0; - String _responseHeaders; + std::list> _userHeaders; String _hostHeader; bool _chunked = false; From 16bb9de85f7feb0876f1a091fef8449c3949ea31 Mon Sep 17 00:00:00 2001 From: David Gauchard Date: Tue, 28 Feb 2023 10:29:58 +0100 Subject: [PATCH 2/3] make sendHeader generic --- libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h | 3 ++- libraries/ESP8266WebServer/src/ESP8266WebServer.h | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h b/libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h index b392295a8a..8ce18f57b7 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h @@ -420,7 +420,8 @@ void ESP8266WebServerTemplate::stop() { } template -void ESP8266WebServerTemplate::sendHeader(String&& name, String&& value, bool first) { +template +void ESP8266WebServerTemplate::sendHeader(S1 name, S2 value, bool first) { if (first) _userHeaders.emplace_front(std::pair(name, value)); else diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.h b/libraries/ESP8266WebServer/src/ESP8266WebServer.h index 1d8584cfe1..5c1637abc9 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.h @@ -179,8 +179,8 @@ class ESP8266WebServerTemplate } void setContentLength(const size_t contentLength); - void sendHeader(String&& name, String&& value, bool first = false); - void sendHeader(const String& name, const String& value, bool first = false); + template + void sendHeader(S1 name, S2 value, bool first = false); void sendContent(const String& content); void sendContent(String& content) { sendContent((const String&)content); From 5bc850ebbda9d1ec6d03c10f415f6a910ea6be51 Mon Sep 17 00:00:00 2001 From: David Gauchard Date: Sun, 5 Mar 2023 22:43:31 +0100 Subject: [PATCH 3/3] automatic factorization of declarations with std::forward --- .../ESP8266WebServer/src/ESP8266WebServer-impl.h | 11 +++++------ libraries/ESP8266WebServer/src/ESP8266WebServer.h | 8 +++++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h b/libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h index 8ce18f57b7..dc640b2c53 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h @@ -219,11 +219,11 @@ void ESP8266WebServerTemplate::requestAuthentication(HTTPAuthMethod _srealm = String(realm); } if(mode == BASIC_AUTH) { - sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Basic realm=\"")) + _srealm + String('\"')); + sendHeader(FPSTR(WWW_Authenticate), String(F("Basic realm=\"")) + _srealm + String('\"')); } else { _snonce=_getRandomHexString(); _sopaque=_getRandomHexString(); - sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Digest realm=\"")) +_srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce + String(F("\", opaque=\"")) + _sopaque + String('\"')); + sendHeader(FPSTR(WWW_Authenticate), String(F("Digest realm=\"")) +_srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce + String(F("\", opaque=\"")) + _sopaque + String('\"')); } using namespace mime; send(401, String(FPSTR(mimeTable[html].mimeType)), authFailMsg); @@ -420,12 +420,11 @@ void ESP8266WebServerTemplate::stop() { } template -template -void ESP8266WebServerTemplate::sendHeader(S1 name, S2 value, bool first) { +void ESP8266WebServerTemplate::_storeHeader(std::pair&& nameValue, bool first) { if (first) - _userHeaders.emplace_front(std::pair(name, value)); + _userHeaders.emplace_front(std::move(nameValue)); else - _userHeaders.emplace_back(std::pair(name, value)); + _userHeaders.emplace_back(std::move(nameValue)); } template diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.h b/libraries/ESP8266WebServer/src/ESP8266WebServer.h index 5c1637abc9..1d26c10e90 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.h @@ -179,8 +179,13 @@ class ESP8266WebServerTemplate } void setContentLength(const size_t contentLength); + template - void sendHeader(S1 name, S2 value, bool first = false); + void sendHeader(S1&& name, S2&& value, bool first = false) + { + _storeHeader(std::make_pair(String(std::forward(name)), String(std::forward(value))), first); + } + void sendContent(const String& content); void sendContent(String& content) { sendContent((const String&)content); @@ -309,6 +314,7 @@ class ESP8266WebServerTemplate bool _streamIt (Stream& s); template bool _streamHeader (K name, V value); + void _storeHeader(std::pair&& nameValue, bool first); struct RequestArgument { String key;