From f384cd1f76ab6be703e22ed4b86677c9032463aa Mon Sep 17 00:00:00 2001 From: Clemens Kirchgatterer Date: Wed, 15 Jun 2016 17:21:16 +0200 Subject: [PATCH 01/12] fix for file download/rendering response (#40) tested with chromium and firefox and all content types i had handy. chrome and firefox render all files inline, if they can. chrome downloads files when asked to download, firefox opens a download dialog. --- src/WebResponses.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp index e063c70..6777465 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -333,7 +333,6 @@ void AsyncFileResponse::_setContentType(String path){ if (path.endsWith(".html")) _contentType = "text/html"; else if (path.endsWith(".htm")) _contentType = "text/html"; else if (path.endsWith(".css")) _contentType = "text/css"; - else if (path.endsWith(".txt")) _contentType = "text/plain"; else if (path.endsWith(".js")) _contentType = "application/javascript"; else if (path.endsWith(".png")) _contentType = "image/png"; else if (path.endsWith(".gif")) _contentType = "image/gif"; @@ -344,21 +343,33 @@ void AsyncFileResponse::_setContentType(String path){ else if (path.endsWith(".pdf")) _contentType = "application/pdf"; else if (path.endsWith(".zip")) _contentType = "application/zip"; else if(path.endsWith(".gz")) _contentType = "application/x-gzip"; - else _contentType = "application/octet-stream"; + else _contentType = "text/plain"; } AsyncFileResponse::AsyncFileResponse(FS &fs, String path, String contentType, bool download){ + char buf[64]; _code = 200; _path = path; + if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){ _path = _path+".gz"; addHeader("Content-Encoding", "gzip"); } - if(download) - _contentType = "application/octet-stream"; - else + if(contentType == "") _setContentType(path); + else + _contentType = contentType; + + if(download) { + // set filename and force download + snprintf(buf, sizeof (buf), "attachment; filename='%s'", path.c_str()); + } else { + // set filename and force rendering + snprintf(buf, sizeof (buf), "inline; filename='%s'", path.c_str()); + } + addHeader("Content-Disposition", buf); + _content = fs.open(_path, "r"); _contentLength = _content.size(); } From a52873b45159fe98450ae7d7a78017f489d885df Mon Sep 17 00:00:00 2001 From: Hagai Shatz Date: Thu, 16 Jun 2016 10:52:11 +0100 Subject: [PATCH 02/12] Fix path problems in static handler and improve performance. (#41) --- src/WebHandlerImpl.h | 35 ++++++++++----- src/WebHandlers.cpp | 101 ++++++++++++++++++++++++------------------- 2 files changed, 80 insertions(+), 56 deletions(-) diff --git a/src/WebHandlerImpl.h b/src/WebHandlerImpl.h index dc0992e..eb26a09 100644 --- a/src/WebHandlerImpl.h +++ b/src/WebHandlerImpl.h @@ -26,32 +26,43 @@ class AsyncStaticWebHandler: public AsyncWebHandler { private: - String _getPath(AsyncWebServerRequest *request); + String _getPath(AsyncWebServerRequest *request, const bool withStats); + bool _fileExists(const String path, const bool withStats); + uint8_t _countBits(const uint8_t value); protected: FS _fs; String _uri; String _path; String _cache_header; - bool _isFile; + bool _isDir; + bool _gzipFirst; + uint8_t _gzipStats; + uint8_t _fileStats; public: AsyncStaticWebHandler(FS& fs, const char* path, const char* uri, const char* cache_header) : _fs(fs), _uri(uri), _path(path), _cache_header(cache_header){ + // Ensure leading '/' + if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri; + if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path; - _isFile = _fs.exists(path) || _fs.exists((String(path)+".gz").c_str()); - if (_uri != "/" && _uri.endsWith("/")) { - _uri = _uri.substring(0, _uri.length() - 1); - DEBUGF("[AsyncStaticWebHandler] _uri / removed\n"); - } - if (_path != "/" && _path.endsWith("/")) { - _path = _path.substring(0, _path.length() - 1); - DEBUGF("[AsyncStaticWebHandler] _path / removed\n"); - } + // If uri or path ends with '/' we assume a hint that this is a directory to improve performance. + // However - if they both do not end '/' we, can't assume they are files, they can still be directory. + bool isUriDir = _uri[_uri.length()-1] == '/'; + bool isPathDir = _path[_path.length()-1] == '/'; + _isDir = isUriDir || isPathDir; + // If we serving directory - remove the trailing '/' so we can handle default file + // Notice that root will be "" not "/" + if (_isDir && isUriDir) _uri = _uri.substring(0, _uri.length()-1); + if (_isDir && isPathDir) _path = _path.substring(0, _path.length()-1); + // Reset stats + _gzipFirst = false; + _gzipStats = 0; + _fileStats = 0; } bool canHandle(AsyncWebServerRequest *request); void handleRequest(AsyncWebServerRequest *request); - }; class AsyncCallbackWebHandler: public AsyncWebHandler { diff --git a/src/WebHandlers.cpp b/src/WebHandlers.cpp index e55269f..cbf1842 100644 --- a/src/WebHandlers.cpp +++ b/src/WebHandlers.cpp @@ -23,66 +23,81 @@ bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request) { - if (request->method() != HTTP_GET) { - return false; - } - if ((_isFile && request->url() != _uri) ) { - return false; - } - // if the root of the request matches the _uri then it checks to see if there is a file it can handle. - if (request->url().startsWith(_uri)) { - String path = _getPath(request); - if (_fs.exists(path) || _fs.exists(path + ".gz")) { - DEBUGF("[AsyncStaticWebHandler::canHandle] TRUE\n"); - return true; - } + if (request->method() == HTTP_GET && + request->url().startsWith(_uri) && + _getPath(request, true).length()) { + + DEBUGF("[AsyncStaticWebHandler::canHandle] TRUE\n"); + return true; + } return false; } -String AsyncStaticWebHandler::_getPath(AsyncWebServerRequest *request) +String AsyncStaticWebHandler::_getPath(AsyncWebServerRequest *request, const bool withStats) { + // Remove the found uri + String path = request->url().substring(_uri.length()); - String path = request->url(); - DEBUGF("[AsyncStaticWebHandler::_getPath]\n"); - DEBUGF(" [stored] _uri = %s, _path = %s\n" , _uri.c_str(), _path.c_str() ) ; - DEBUGF(" [request] url = %s\n", request->url().c_str() ); + // We can skip the file check if we serving a directory and (we have full match or we end with '/') + bool canSkipFileCheck = _isDir && (path.length() == 0 || path[path.length()-1] == '/'); - if (!_isFile) { - DEBUGF(" _isFile = false\n"); - String baserequestUrl = request->url().substring(_uri.length()); // this is the request - stored _uri... /espman/ - DEBUGF(" baserequestUrl = %s\n", baserequestUrl.c_str()); + path = _path + path; - if (!baserequestUrl.length()) { - baserequestUrl += "/"; - } + // Do we have a file or .gz file + if (!canSkipFileCheck) if (_fileExists(path, withStats)) return path; - path = _path + baserequestUrl; - DEBUGF(" path = path + baserequestUrl, path = %s\n", path.c_str()); + // Try to add default page, ensure there is a trailing '/' ot the path. + if (path.length() == 0 || path[path.length()-1] != '/') path += "/"; + path += "index.htm"; - if (path.endsWith("/")) { - DEBUGF(" 3 path ends with / : path = index.htm \n"); - path += "index.htm"; - } - } else { - path = _path; - } + if (_fileExists(path, withStats)) return path; - DEBUGF(" final path = %s\n", path.c_str()); - DEBUGF("[AsyncStaticWebHandler::_getPath] END\n\n"); - - return path; + // No file - return empty string + return String(); } +bool AsyncStaticWebHandler::_fileExists(const String path, const bool withStats) +{ + bool fileFound = false; + bool gzipFound = false; + + String gzip = path + ".gz"; + + if (_gzipFirst) { + gzipFound = _fs.exists(gzip); + if (!gzipFound) fileFound = _fs.exists(path); + } else { + fileFound = _fs.exists(path); + if (!fileFound) gzipFound = _fs.exists(gzip); + } + + bool found = fileFound || gzipFound; + + if (withStats && found) { + _gzipStats = (_gzipStats << 1) + gzipFound ? 1 : 0; + _fileStats = (_fileStats << 1) + fileFound ? 1 : 0; + _gzipFirst = _countBits(_gzipStats) > _countBits(_fileStats); + } + + return found; +} + +uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) +{ + uint8_t w = value; + uint8_t n; + for (n=0; w!=0; n++) w&=w-1; + return n; +} void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) { + String path = _getPath(request, false); - String path = _getPath(request); - - if (_fs.exists(path) || _fs.exists(path + ".gz")) { - AsyncWebServerResponse * response = request->beginResponse(_fs, path); + if (path.length()) { + AsyncWebServerResponse * response = new AsyncFileResponse(_fs, path); if (_cache_header.length() != 0) response->addHeader("Cache-Control", _cache_header); request->send(response); @@ -90,6 +105,4 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) request->send(404); } path = String(); - - } From 5ee66e18428a11cb5a5ca2a5c3d37802177dc48e Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Thu, 16 Jun 2016 22:13:02 +0300 Subject: [PATCH 03/12] send just the filename in Content-Disposition Thanks go to: @hagai-shatz --- src/WebResponses.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp index 6777465..b3b1b7d 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -347,9 +347,11 @@ void AsyncFileResponse::_setContentType(String path){ } AsyncFileResponse::AsyncFileResponse(FS &fs, String path, String contentType, bool download){ - char buf[64]; _code = 200; _path = path; + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26+path.length()-filenameStart]; + char* filename = (char*)path.c_str() + filenameStart; if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){ _path = _path+".gz"; @@ -363,10 +365,10 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, String path, String contentType, bo if(download) { // set filename and force download - snprintf(buf, sizeof (buf), "attachment; filename='%s'", path.c_str()); + snprintf(buf, sizeof (buf), "attachment; filename='%s'", filename); } else { // set filename and force rendering - snprintf(buf, sizeof (buf), "inline; filename='%s'", path.c_str()); + snprintf(buf, sizeof (buf), "inline; filename='%s'", filename); } addHeader("Content-Disposition", buf); @@ -469,4 +471,3 @@ size_t AsyncResponseStream::write(const uint8_t *data, size_t len){ size_t AsyncResponseStream::write(uint8_t data){ return write(&data, 1); } - From 41228cb24678c46860a4da3a2fd38ccedfd97245 Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Thu, 16 Jun 2016 23:09:58 +0300 Subject: [PATCH 04/12] add uint8_t * request->_tempBuffer to be used for storing body data or other request specific stuff --- src/ESPAsyncWebServer.h | 1 + src/WebRequest.cpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index 94d8f89..60ea679 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -160,6 +160,7 @@ class AsyncWebServerRequest { public: File _tempFile; + uint8_t *_tempBuffer; AsyncWebServerRequest *next; AsyncWebServerRequest(AsyncWebServer*, AsyncClient*); diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index f1e8ffa..3f67daf 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -63,6 +63,7 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c) , _itemBuffer(0) , _itemBufferIndex(0) , _itemIsFile(false) + , _tempBuffer(NULL) , next(NULL) { c->onError([](void *r, AsyncClient* c, int8_t error){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this); @@ -93,6 +94,10 @@ AsyncWebServerRequest::~AsyncWebServerRequest(){ delete _response; } + if(_tempBuffer != NULL){ + delete _tempBuffer; + } + } void AsyncWebServerRequest::_onData(void *buf, size_t len){ From 798fcc3c05186310a9a60e118d37470e08c9c592 Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Thu, 16 Jun 2016 23:24:09 +0300 Subject: [PATCH 05/12] rename to _tempObject and give type void * so it can be used for any object/array --- src/ESPAsyncWebServer.h | 2 +- src/WebRequest.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index 60ea679..fae9829 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -160,7 +160,7 @@ class AsyncWebServerRequest { public: File _tempFile; - uint8_t *_tempBuffer; + void *_tempObject; AsyncWebServerRequest *next; AsyncWebServerRequest(AsyncWebServer*, AsyncClient*); diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index 3f67daf..bd16712 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -63,7 +63,7 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c) , _itemBuffer(0) , _itemBufferIndex(0) , _itemIsFile(false) - , _tempBuffer(NULL) + , _tempObject(NULL) , next(NULL) { c->onError([](void *r, AsyncClient* c, int8_t error){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this); From 16a6016759e70a77784656c807f1abce29e0e850 Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Thu, 16 Jun 2016 23:27:01 +0300 Subject: [PATCH 06/12] fix missed rename --- src/WebRequest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index bd16712..527f507 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -94,8 +94,8 @@ AsyncWebServerRequest::~AsyncWebServerRequest(){ delete _response; } - if(_tempBuffer != NULL){ - delete _tempBuffer; + if(_tempObject != NULL){ + delete _tempObject; } } From 3709dd5e14f5e7552cb92d1352555773c0adc8fc Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Fri, 17 Jun 2016 01:43:59 +0300 Subject: [PATCH 07/12] add ability to send File objects directly utilize request's tempFile to store a handle to the file to send use open instead of exists in static handler --- src/ESPAsyncWebServer.h | 2 ++ src/WebHandlerImpl.h | 27 ++--------------- src/WebHandlers.cpp | 67 +++++++++++++++++++++++++++++------------ src/WebRequest.cpp | 12 ++++++++ src/WebResponseImpl.h | 1 + src/WebResponses.cpp | 25 +++++++++++++++ 6 files changed, 90 insertions(+), 44 deletions(-) diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index fae9829..db4807b 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -186,12 +186,14 @@ class AsyncWebServerRequest { void send(AsyncWebServerResponse *response); void send(int code, String contentType=String(), String content=String()); void send(FS &fs, String path, String contentType=String(), bool download=false); + void send(File content, String contentType=String(), bool download=false); void send(Stream &stream, String contentType, size_t len); void send(String contentType, size_t len, AwsResponseFiller callback); void sendChunked(String contentType, AwsResponseFiller callback); AsyncWebServerResponse *beginResponse(int code, String contentType=String(), String content=String()); AsyncWebServerResponse *beginResponse(FS &fs, String path, String contentType=String(), bool download=false); + AsyncWebServerResponse *beginResponse(File content, String contentType=String(), bool download=false); AsyncWebServerResponse *beginResponse(Stream &stream, String contentType, size_t len); AsyncWebServerResponse *beginResponse(String contentType, size_t len, AwsResponseFiller callback); AsyncWebServerResponse *beginChunkedResponse(String contentType, AwsResponseFiller callback); diff --git a/src/WebHandlerImpl.h b/src/WebHandlerImpl.h index eb26a09..158cb60 100644 --- a/src/WebHandlerImpl.h +++ b/src/WebHandlerImpl.h @@ -26,8 +26,8 @@ class AsyncStaticWebHandler: public AsyncWebHandler { private: - String _getPath(AsyncWebServerRequest *request, const bool withStats); - bool _fileExists(const String path, const bool withStats); + bool _getFile(AsyncWebServerRequest *request); + bool _fileExists(AsyncWebServerRequest *request, const String path); uint8_t _countBits(const uint8_t value); protected: FS _fs; @@ -39,28 +39,7 @@ class AsyncStaticWebHandler: public AsyncWebHandler { uint8_t _gzipStats; uint8_t _fileStats; public: - AsyncStaticWebHandler(FS& fs, const char* path, const char* uri, const char* cache_header) - : _fs(fs), _uri(uri), _path(path), _cache_header(cache_header){ - // Ensure leading '/' - if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri; - if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path; - - // If uri or path ends with '/' we assume a hint that this is a directory to improve performance. - // However - if they both do not end '/' we, can't assume they are files, they can still be directory. - bool isUriDir = _uri[_uri.length()-1] == '/'; - bool isPathDir = _path[_path.length()-1] == '/'; - _isDir = isUriDir || isPathDir; - - // If we serving directory - remove the trailing '/' so we can handle default file - // Notice that root will be "" not "/" - if (_isDir && isUriDir) _uri = _uri.substring(0, _uri.length()-1); - if (_isDir && isPathDir) _path = _path.substring(0, _path.length()-1); - - // Reset stats - _gzipFirst = false; - _gzipStats = 0; - _fileStats = 0; - } + AsyncStaticWebHandler(FS& fs, const char* path, const char* uri, const char* cache_header); bool canHandle(AsyncWebServerRequest *request); void handleRequest(AsyncWebServerRequest *request); }; diff --git a/src/WebHandlers.cpp b/src/WebHandlers.cpp index cbf1842..81a7f15 100644 --- a/src/WebHandlers.cpp +++ b/src/WebHandlers.cpp @@ -21,21 +21,44 @@ #include "ESPAsyncWebServer.h" #include "WebHandlerImpl.h" +AsyncStaticWebHandler::AsyncStaticWebHandler(FS& fs, const char* path, const char* uri, const char* cache_header) + : _fs(fs), _uri(uri), _path(path), _cache_header(cache_header) +{ + // Ensure leading '/' + if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri; + if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path; + + // If uri or path ends with '/' we assume a hint that this is a directory to improve performance. + // However - if they both do not end '/' we, can't assume they are files, they can still be directory. + bool isUriDir = _uri[_uri.length()-1] == '/'; + bool isPathDir = _path[_path.length()-1] == '/'; + _isDir = isUriDir || isPathDir; + + // If we serving directory - remove the trailing '/' so we can handle default file + // Notice that root will be "" not "/" + if (_isDir && isUriDir) _uri = _uri.substring(0, _uri.length()-1); + if (_isDir && isPathDir) _path = _path.substring(0, _path.length()-1); + + // Reset stats + _gzipFirst = false; + _gzipStats = 0; + _fileStats = 0; +} + bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request) { if (request->method() == HTTP_GET && request->url().startsWith(_uri) && - _getPath(request, true).length()) { + _getFile(request)) { DEBUGF("[AsyncStaticWebHandler::canHandle] TRUE\n"); return true; - } return false; } -String AsyncStaticWebHandler::_getPath(AsyncWebServerRequest *request, const bool withStats) +bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) { // Remove the found uri String path = request->url().substring(_uri.length()); @@ -46,19 +69,18 @@ String AsyncStaticWebHandler::_getPath(AsyncWebServerRequest *request, const boo path = _path + path; // Do we have a file or .gz file - if (!canSkipFileCheck) if (_fileExists(path, withStats)) return path; + if (!canSkipFileCheck && _fileExists(request, path)) + return true; // Try to add default page, ensure there is a trailing '/' ot the path. - if (path.length() == 0 || path[path.length()-1] != '/') path += "/"; + if (path.length() == 0 || path[path.length()-1] != '/') + path += "/"; path += "index.htm"; - if (_fileExists(path, withStats)) return path; - - // No file - return empty string - return String(); + return_fileExists(request, path); } -bool AsyncStaticWebHandler::_fileExists(const String path, const bool withStats) +bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String path) { bool fileFound = false; bool gzipFound = false; @@ -66,16 +88,24 @@ bool AsyncStaticWebHandler::_fileExists(const String path, const bool withStats) String gzip = path + ".gz"; if (_gzipFirst) { - gzipFound = _fs.exists(gzip); - if (!gzipFound) fileFound = _fs.exists(path); + request->_tempFile = _fs.open(gzip, "r"); + gzipFound = request->_tempFile == true; + if (!gzipFound){ + request->_tempFile = _fs.open(path, "r"); + fileFound = request->_tempFile == true; + } } else { - fileFound = _fs.exists(path); - if (!fileFound) gzipFound = _fs.exists(gzip); + request->_tempFile = _fs.open(path, "r"); + fileFound = request->_tempFile == true; + if (!fileFound){ + request->_tempFile = _fs.open(gzip, "r"); + gzipFound = request->_tempFile == true; + } } bool found = fileFound || gzipFound; - if (withStats && found) { + if (found) { _gzipStats = (_gzipStats << 1) + gzipFound ? 1 : 0; _fileStats = (_fileStats << 1) + fileFound ? 1 : 0; _gzipFirst = _countBits(_gzipStats) > _countBits(_fileStats); @@ -94,15 +124,12 @@ uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) { - String path = _getPath(request, false); - - if (path.length()) { - AsyncWebServerResponse * response = new AsyncFileResponse(_fs, path); + if (request->_tempFile == true) { + AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile); if (_cache_header.length() != 0) response->addHeader("Cache-Control", _cache_header); request->send(response); } else { request->send(404); } - path = String(); } diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index 527f507..1b6aa49 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -651,6 +651,12 @@ AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, String pat return NULL; } +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File content, String contentType, bool download){ + if(content == true) + return new AsyncFileResponse(content, contentType, download); + return NULL; +} + AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, String contentType, size_t len){ return new AsyncStreamResponse(stream, contentType, len); } @@ -679,6 +685,12 @@ void AsyncWebServerRequest::send(FS &fs, String path, String contentType, bool d } else send(404); } +void AsyncWebServerRequest::send(File content, String contentType, bool download){ + if(content == true){ + send(beginResponse(content, contentType, download)); + } else send(404); +} + void AsyncWebServerRequest::send(Stream &stream, String contentType, size_t len){ send(beginResponse(stream, contentType, len)); } diff --git a/src/WebResponseImpl.h b/src/WebResponseImpl.h index 59400f6..e9e64ff 100644 --- a/src/WebResponseImpl.h +++ b/src/WebResponseImpl.h @@ -48,6 +48,7 @@ class AsyncFileResponse: public AsyncAbstractResponse { void _setContentType(String path); public: AsyncFileResponse(FS &fs, String path, String contentType=String(), bool download=false); + AsyncFileResponse(File content, String contentType=String(), bool download=false); ~AsyncFileResponse(); bool _sourceValid(){ return !!(_content); } size_t _fillBuffer(uint8_t *buf, size_t maxLen); diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp index b3b1b7d..2148996 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -376,6 +376,31 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, String path, String contentType, bo _contentLength = _content.size(); } +AsyncFileResponse::AsyncFileResponse(File content, String contentType, bool download){ + _code = 200; + _content = content; + _path = String(_content.name()); + _contentLength = _content.size(); + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26+path.length()-filenameStart]; + char* filename = (char*)path.c_str() + filenameStart; + + if(!download && _path.endsWith(".gz")) + addHeader("Content-Encoding", "gzip"); + + if(contentType == "") + _setContentType(_path); + else + _contentType = contentType; + + if(download) { + snprintf(buf, sizeof (buf), "attachment; filename='%s'", filename); + } else { + snprintf(buf, sizeof (buf), "inline; filename='%s'", filename); + } + addHeader("Content-Disposition", buf); +} + size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){ _content.read(data, len); return len; From d8809137a36a4636a1dee3eda07692eec99699e6 Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Fri, 17 Jun 2016 01:53:21 +0300 Subject: [PATCH 08/12] fix typos --- src/WebHandlers.cpp | 2 +- src/WebRequest.cpp | 2 +- src/WebResponses.cpp | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/WebHandlers.cpp b/src/WebHandlers.cpp index 81a7f15..5c64c61 100644 --- a/src/WebHandlers.cpp +++ b/src/WebHandlers.cpp @@ -77,7 +77,7 @@ bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) path += "/"; path += "index.htm"; - return_fileExists(request, path); + return _fileExists(request, path); } bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String path) diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index 1b6aa49..32d065e 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -95,7 +95,7 @@ AsyncWebServerRequest::~AsyncWebServerRequest(){ } if(_tempObject != NULL){ - delete _tempObject; + free(_tempObject); } } diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp index 2148996..60a3d3b 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -381,9 +381,9 @@ AsyncFileResponse::AsyncFileResponse(File content, String contentType, bool down _content = content; _path = String(_content.name()); _contentLength = _content.size(); - int filenameStart = path.lastIndexOf('/') + 1; - char buf[26+path.length()-filenameStart]; - char* filename = (char*)path.c_str() + filenameStart; + int filenameStart = _path.lastIndexOf('/') + 1; + char buf[26+_path.length()-filenameStart]; + char* filename = (char*)_path.c_str() + filenameStart; if(!download && _path.endsWith(".gz")) addHeader("Content-Encoding", "gzip"); From dc5b7f708a667ee6cf83a054ef37cc4a31645054 Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Fri, 17 Jun 2016 02:23:18 +0300 Subject: [PATCH 09/12] provide original name so proper content type is set for gzipped content --- src/ESPAsyncWebServer.h | 4 ++-- src/WebHandlers.cpp | 8 +++++++- src/WebRequest.cpp | 8 ++++---- src/WebResponseImpl.h | 2 +- src/WebResponses.cpp | 6 +++--- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index db4807b..9fe4873 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -186,14 +186,14 @@ class AsyncWebServerRequest { void send(AsyncWebServerResponse *response); void send(int code, String contentType=String(), String content=String()); void send(FS &fs, String path, String contentType=String(), bool download=false); - void send(File content, String contentType=String(), bool download=false); + void send(File content, String path, String contentType=String(), bool download=false); void send(Stream &stream, String contentType, size_t len); void send(String contentType, size_t len, AwsResponseFiller callback); void sendChunked(String contentType, AwsResponseFiller callback); AsyncWebServerResponse *beginResponse(int code, String contentType=String(), String content=String()); AsyncWebServerResponse *beginResponse(FS &fs, String path, String contentType=String(), bool download=false); - AsyncWebServerResponse *beginResponse(File content, String contentType=String(), bool download=false); + AsyncWebServerResponse *beginResponse(File content, String path, String contentType=String(), bool download=false); AsyncWebServerResponse *beginResponse(Stream &stream, String contentType, size_t len); AsyncWebServerResponse *beginResponse(String contentType, size_t len, AwsResponseFiller callback); AsyncWebServerResponse *beginChunkedResponse(String contentType, AwsResponseFiller callback); diff --git a/src/WebHandlers.cpp b/src/WebHandlers.cpp index 5c64c61..94483ae 100644 --- a/src/WebHandlers.cpp +++ b/src/WebHandlers.cpp @@ -106,6 +106,10 @@ bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const St bool found = fileFound || gzipFound; if (found) { + size_t plen = path.length(); + char * _tempPath = (char*)malloc(plen+1); + snprintf(_tempPath, plen+1, "%s", path.c_str()); + request->_tempObject = (void*)_tempPath; _gzipStats = (_gzipStats << 1) + gzipFound ? 1 : 0; _fileStats = (_fileStats << 1) + fileFound ? 1 : 0; _gzipFirst = _countBits(_gzipStats) > _countBits(_fileStats); @@ -125,7 +129,9 @@ uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) { if (request->_tempFile == true) { - AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile); + AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, String((char*)request->_tempObject)); + free(request->_tempObject); + request->_tempObject = NULL; if (_cache_header.length() != 0) response->addHeader("Cache-Control", _cache_header); request->send(response); diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index 32d065e..ee8efcd 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -651,9 +651,9 @@ AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, String pat return NULL; } -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File content, String contentType, bool download){ +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File content, String path, String contentType, bool download){ if(content == true) - return new AsyncFileResponse(content, contentType, download); + return new AsyncFileResponse(content, path, contentType, download); return NULL; } @@ -685,9 +685,9 @@ void AsyncWebServerRequest::send(FS &fs, String path, String contentType, bool d } else send(404); } -void AsyncWebServerRequest::send(File content, String contentType, bool download){ +void AsyncWebServerRequest::send(File content, String path, String contentType, bool download){ if(content == true){ - send(beginResponse(content, contentType, download)); + send(beginResponse(content, path, contentType, download)); } else send(404); } diff --git a/src/WebResponseImpl.h b/src/WebResponseImpl.h index e9e64ff..c7f539b 100644 --- a/src/WebResponseImpl.h +++ b/src/WebResponseImpl.h @@ -48,7 +48,7 @@ class AsyncFileResponse: public AsyncAbstractResponse { void _setContentType(String path); public: AsyncFileResponse(FS &fs, String path, String contentType=String(), bool download=false); - AsyncFileResponse(File content, String contentType=String(), bool download=false); + AsyncFileResponse(File content, String path, String contentType=String(), bool download=false); ~AsyncFileResponse(); bool _sourceValid(){ return !!(_content); } size_t _fillBuffer(uint8_t *buf, size_t maxLen); diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp index 60a3d3b..5650bbb 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -376,16 +376,16 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, String path, String contentType, bo _contentLength = _content.size(); } -AsyncFileResponse::AsyncFileResponse(File content, String contentType, bool download){ +AsyncFileResponse::AsyncFileResponse(File content, String path, String contentType, bool download){ _code = 200; _content = content; - _path = String(_content.name()); + _path = path; _contentLength = _content.size(); int filenameStart = _path.lastIndexOf('/') + 1; char buf[26+_path.length()-filenameStart]; char* filename = (char*)_path.c_str() + filenameStart; - if(!download && _path.endsWith(".gz")) + if(!download && String(_content.name()).endsWith(".gz")) addHeader("Content-Encoding", "gzip"); if(contentType == "") From 78fc89d4623ba62917367e0e51cef8baa9cd0ede Mon Sep 17 00:00:00 2001 From: Clemens Kirchgatterer Date: Fri, 17 Jun 2016 16:34:38 +0200 Subject: [PATCH 10/12] filename quote changed from ' to " (#43) now for real. :-) --- src/WebResponses.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp index 5650bbb..3cdfbb8 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -365,10 +365,10 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, String path, String contentType, bo if(download) { // set filename and force download - snprintf(buf, sizeof (buf), "attachment; filename='%s'", filename); + snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename); } else { // set filename and force rendering - snprintf(buf, sizeof (buf), "inline; filename='%s'", filename); + snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename); } addHeader("Content-Disposition", buf); @@ -394,9 +394,9 @@ AsyncFileResponse::AsyncFileResponse(File content, String path, String contentTy _contentType = contentType; if(download) { - snprintf(buf, sizeof (buf), "attachment; filename='%s'", filename); + snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename); } else { - snprintf(buf, sizeof (buf), "inline; filename='%s'", filename); + snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename); } addHeader("Content-Disposition", buf); } From 5bbc7320284e543209991cecf099602bc4415bf8 Mon Sep 17 00:00:00 2001 From: Me No Dev Date: Fri, 17 Jun 2016 17:41:06 +0300 Subject: [PATCH 11/12] remove Access-Control-Allow-Origin --- src/WebResponses.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp index 3cdfbb8..b1de8a6 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -158,7 +158,6 @@ AsyncBasicResponse::AsyncBasicResponse(int code, String contentType, String cont _contentType = "text/plain"; } addHeader("Connection","close"); - addHeader("Access-Control-Allow-Origin","*"); } void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){ @@ -228,7 +227,6 @@ size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){ addHeader("Connection","close"); - addHeader("Access-Control-Allow-Origin","*"); _head = _assembleHead(request->version()); _state = RESPONSE_HEADERS; _ack(request, 0, 0); @@ -349,19 +347,22 @@ void AsyncFileResponse::_setContentType(String path){ AsyncFileResponse::AsyncFileResponse(FS &fs, String path, String contentType, bool download){ _code = 200; _path = path; - int filenameStart = path.lastIndexOf('/') + 1; - char buf[26+path.length()-filenameStart]; - char* filename = (char*)path.c_str() + filenameStart; - + _content = fs.open(_path, "r"); + _contentLength = _content.size(); + if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){ _path = _path+".gz"; addHeader("Content-Encoding", "gzip"); } - + if(contentType == "") _setContentType(path); else _contentType = contentType; + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26+path.length()-filenameStart]; + char* filename = (char*)path.c_str() + filenameStart; if(download) { // set filename and force download @@ -372,26 +373,25 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, String path, String contentType, bo } addHeader("Content-Disposition", buf); - _content = fs.open(_path, "r"); - _contentLength = _content.size(); } AsyncFileResponse::AsyncFileResponse(File content, String path, String contentType, bool download){ _code = 200; - _content = content; _path = path; + _content = content; _contentLength = _content.size(); - int filenameStart = _path.lastIndexOf('/') + 1; - char buf[26+_path.length()-filenameStart]; - char* filename = (char*)_path.c_str() + filenameStart; - + if(!download && String(_content.name()).endsWith(".gz")) addHeader("Content-Encoding", "gzip"); - + if(contentType == "") - _setContentType(_path); + _setContentType(path); else _contentType = contentType; + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26+path.length()-filenameStart]; + char* filename = (char*)path.c_str() + filenameStart; if(download) { snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename); From b63a86eba8435bf99be2cbc163d50f89e3813eea Mon Sep 17 00:00:00 2001 From: Hagai Shatz Date: Fri, 17 Jun 2016 15:42:47 +0100 Subject: [PATCH 12/12] Performance (#44) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * HTTP 302 and 304 Support Add support for http redirection (302) and http not modified (304) to reduce the load the server. server.redirect(“url”, “location”, exclude-ip) will respond with 302 to redirect the browser to a different url, this is useful for backward compatibility and to redirect call to CDN when not no AP mode. server.serveStatic has a new optional parameter to get the Last-Modified date for all files serve for this location, when the browser request have the same If-Modified-Since header value, the server respond with 304 code instead of serving the file. * First round of performance improvements. * Merge remote-tracking branch 'me-no-dev/master' into performance # Conflicts: # src/WebHandlerImpl.h # src/WebHandlers.cpp * use of sprintf * Remove sections not related. --- .gitignore | 2 + src/ESPAsyncWebServer.h | 10 ++-- src/WebRequest.cpp | 123 ++++++++++++++++++++-------------------- src/WebResponses.cpp | 24 +++++--- 4 files changed, 85 insertions(+), 74 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df30947 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +.idea/ diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index 9fe4873..6800a82 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -81,10 +81,11 @@ class AsyncWebHeader { AsyncWebHeader(String name, String value): _name(name), _value(value), next(NULL){} AsyncWebHeader(String data): _name(), _value(), next(NULL){ - if(!data || !data.length() || data.indexOf(':') < 0) - return; - _name = data.substring(0, data.indexOf(':')); - _value = data.substring(data.indexOf(':') + 2); + if(!data) return; + int index = data.indexOf(':'); + if (index < 0) return; + _name = data.substring(0, index); + _value = data.substring(index + 2); } ~AsyncWebHeader(){} String name(){ return _name; } @@ -149,7 +150,6 @@ class AsyncWebServerRequest { bool _parseReqHead(); bool _parseReqHeader(); void _parseLine(); - void _parseByte(uint8_t data); void _parsePlainPostChar(uint8_t data); void _parseMultipartPostByte(uint8_t data, bool last); void _addGetParam(String param); diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index ee8efcd..617f8f1 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -102,12 +102,22 @@ AsyncWebServerRequest::~AsyncWebServerRequest(){ void AsyncWebServerRequest::_onData(void *buf, size_t len){ if(_parseState < PARSE_REQ_BODY){ - size_t i; - for(i=0; ihandleRequest(this); else send(501); - return; } } } @@ -201,16 +210,23 @@ void AsyncWebServerRequest::_addGetParam(String param){ param = urlDecode(param); String name = param; String value = ""; - if(param.indexOf('=') > 0){ - name = param.substring(0, param.indexOf('=')); - value = param.substring(param.indexOf('=') + 1); + int index = param.indexOf('='); + if(index > 0){ + name = param.substring(0, index); + value = param.substring(index + 1); } _addParam(new AsyncWebParameter(name, value)); } bool AsyncWebServerRequest::_parseReqHead(){ - String m = _temp.substring(0, _temp.indexOf(' ')); + // Split the head into method, url and version + int index = _temp.indexOf(' '); + String m = _temp.substring(0, index); + index = _temp.indexOf(' ', index+1); + String u = _temp.substring(m.length()+1, index); + _temp = _temp.substring(index+1); + if(m == "GET"){ _method = HTTP_GET; } else if(m == "POST"){ @@ -227,22 +243,22 @@ bool AsyncWebServerRequest::_parseReqHead(){ _method = HTTP_OPTIONS; } - _temp = _temp.substring(_temp.indexOf(' ')+1); - String u = _temp.substring(0, _temp.indexOf(' ')); u = urlDecode(u); String g = String(); - if(u.indexOf('?') > 0){ - g = u.substring(u.indexOf('?') + 1); - u = u.substring(0, u.indexOf('?')); + index = u.indexOf('?'); + if(index > 0){ + g = u.substring(index+1); + u = u.substring(0, index); } _url = u; if(g.length()){ while(true){ if(g.length() == 0) break; - if(g.indexOf('&') > 0){ - _addGetParam(g.substring(0, g.indexOf('&'))); - g = g.substring(g.indexOf('&') + 1); + index = g.indexOf('&'); + if(index > 0){ + _addGetParam(g.substring(0, index)); + g = g.substring(index+1); } else { _addGetParam(g); break; @@ -250,7 +266,6 @@ bool AsyncWebServerRequest::_parseReqHead(){ } } - _temp = _temp.substring(_temp.indexOf(' ')+1); if(_temp.startsWith("HTTP/1.1")) _version = 1; _temp = String(); @@ -258,36 +273,32 @@ bool AsyncWebServerRequest::_parseReqHead(){ } bool AsyncWebServerRequest::_parseReqHeader(){ - if(_temp.indexOf(':')){ - AsyncWebHeader *h = new AsyncWebHeader(_temp); - if(h == NULL) - return false; - if(h->name() == "Host"){ - _host = h->value(); - delete h; + int index = _temp.indexOf(':'); + if(index){ + String name = _temp.substring(0, index); + String value = _temp.substring(index + 2); + if(name == "Host"){ + _host = value; _server->_handleRequest(this); - } else if(h->name() == "Content-Type"){ - if (h->value().startsWith("multipart/")){ - _boundary = h->value().substring(h->value().indexOf('=')+1); - _contentType = h->value().substring(0, h->value().indexOf(';')); + } else if(name == "Content-Type"){ + if (value.startsWith("multipart/")){ + _boundary = value.substring(value.indexOf('=')+1); + _contentType = value.substring(0, value.indexOf(';')); _isMultipart = true; } else { - _contentType = h->value(); + _contentType = value; } - delete h; - } else if(h->name() == "Content-Length"){ - _contentLength = atoi(h->value().c_str()); - delete h; - } else if(h->name() == "Expect" && h->value() == "100-continue"){ + } else if(name == "Content-Length"){ + _contentLength = atoi(value.c_str()); + } else if(name == "Expect" && value == "100-continue"){ _expectingContinue = true; - delete h; - } else if(h->name() == "Authorization"){ - if(h->value().startsWith("Basic")){ - _authorization = h->value().substring(6); + } else if(name == "Authorization"){ + if(value.startsWith("Basic")){ + _authorization = value.substring(6); } - delete h; } else { - if(_interestingHeaders->contains(h->name()) || _interestingHeaders->contains("ANY")){ + if(_interestingHeaders->contains(name) || _interestingHeaders->contains("ANY")){ + AsyncWebHeader *h = new AsyncWebHeader(name, value); if(_headers == NULL) _headers = h; else { @@ -295,10 +306,8 @@ bool AsyncWebServerRequest::_parseReqHeader(){ while(hs->next != NULL) hs = hs->next; hs->next = h; } - } else - delete h; + } } - } _temp = String(); return true; @@ -532,14 +541,6 @@ void AsyncWebServerRequest::_parseLine(){ } } -void AsyncWebServerRequest::_parseByte(uint8_t data){ - if((char)data != '\r' && (char)data != '\n') - _temp += (char)data; - if((char)data == '\n') - _parseLine(); -} - - int AsyncWebServerRequest::headers(){ int i = 0; @@ -793,26 +794,24 @@ bool AsyncWebServerRequest::hasHeader(const char* name){ String AsyncWebServerRequest::urlDecode(const String& text){ - String decoded = ""; char temp[] = "0x00"; unsigned int len = text.length(); unsigned int i = 0; + String decoded = String(); + decoded.reserve(len); // Allocate the string internal buffer - never longer from source text while (i < len){ char decodedChar; char encodedChar = text.charAt(i++); if ((encodedChar == '%') && (i + 1 < len)){ temp[2] = text.charAt(i++); temp[3] = text.charAt(i++); - decodedChar = strtol(temp, NULL, 16); + } else if (encodedChar == '+') { + decodedChar = ' '; } else { - if (encodedChar == '+'){ - decodedChar = ' '; - } else { - decodedChar = encodedChar; // normal ascii char - } + decodedChar = encodedChar; // normal ascii char } - decoded += decodedChar; + decoded.concat(decodedChar); } return decoded; } diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp index b1de8a6..5eead49 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -119,21 +119,31 @@ String AsyncWebServerResponse::_assembleHead(uint8_t version){ if(_chunked) addHeader("Transfer-Encoding","chunked"); } - String out = "HTTP/1." + String(version) + " " + String(_code) + " " + _responseCodeToString(_code) + "\r\n"; - if(_sendContentLength) - out += "Content-Length: " + String(_contentLength) + "\r\n"; + String out = String(); + int bufSize = 300; + char buf[bufSize]; - if(_contentType.length()) - out += "Content-Type: " + _contentType + "\r\n"; + snprintf(buf, bufSize, "HTTP/1.%d %d %s\r\n", version, _code, _responseCodeToString(_code)); + out.concat(buf); + + if(_sendContentLength) { + snprintf(buf, bufSize, "Content-Length: %d\r\n", _contentLength); + out.concat(buf); + } + if(_contentType.length()) { + snprintf(buf, bufSize, "Content-Type: %s\r\n", _contentType.c_str()); + out.concat(buf); + } AsyncWebHeader *h; while(_headers != NULL){ h = _headers; _headers = _headers->next; - out += h->toString(); + snprintf(buf, bufSize, "%s: %s\r\n", h->name().c_str(), h->value().c_str()); + out.concat(buf); delete h; } - out += "\r\n"; + out.concat("\r\n"); _headLength = out.length(); return out; }