diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h b/libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h index f18b8ae95c..9ea77718f8 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h @@ -34,6 +34,7 @@ static const char qop_auth[] PROGMEM = "qop=auth"; static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\""; static const char WWW_Authenticate[] PROGMEM = "WWW-Authenticate"; static const char Content_Length[] PROGMEM = "Content-Length"; +static const char ETAG_HEADER[] PROGMEM = "If-None-Match"; namespace esp8266webserver { @@ -254,7 +255,18 @@ void ESP8266WebServerTemplate::_addRequestHandler(RequestHandlerType template void ESP8266WebServerTemplate::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) { - _addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header)); + bool is_file = false; + + if (fs.exists(path)) { + File file = fs.open(path, "r"); + is_file = file && file.isFile(); + file.close(); + } + + if(is_file) + _addRequestHandler(new StaticFileRequestHandler(fs, path, uri, cache_header)); + else + _addRequestHandler(new StaticDirectoryRequestHandler(fs, path, uri, cache_header)); } template @@ -606,15 +618,18 @@ const String& ESP8266WebServerTemplate::header(const String& name) c return emptyString; } -template + +template void ESP8266WebServerTemplate::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) { - _headerKeysCount = headerKeysCount + 1; - if (_currentHeaders) - delete[]_currentHeaders; + _headerKeysCount = headerKeysCount + 2; + if (_currentHeaders){ + delete[] _currentHeaders; + } _currentHeaders = new RequestArgument[_headerKeysCount]; _currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER); - for (int i = 1; i < _headerKeysCount; i++){ - _currentHeaders[i].key = headerKeys[i-1]; + _currentHeaders[1].key = FPSTR(ETAG_HEADER); + for (int i = 2; i < _headerKeysCount; i++){ + _currentHeaders[i].key = headerKeys[i-2]; } } diff --git a/libraries/ESP8266WebServer/src/ESP8266WebServer.h b/libraries/ESP8266WebServer/src/ESP8266WebServer.h index d40313a59d..a8453b42a2 100644 --- a/libraries/ESP8266WebServer/src/ESP8266WebServer.h +++ b/libraries/ESP8266WebServer/src/ESP8266WebServer.h @@ -308,5 +308,4 @@ class ESP8266WebServerTemplate using ESP8266WebServer = esp8266webserver::ESP8266WebServerTemplate; using RequestHandler = esp8266webserver::RequestHandler; - -#endif //ESP8266WEBSERVER_H +#endif //ESP8266WEBSERVER_H \ No newline at end of file diff --git a/libraries/ESP8266WebServer/src/detail/RequestHandlersImpl.h b/libraries/ESP8266WebServer/src/detail/RequestHandlersImpl.h index 56ef000e43..c88c830320 100644 --- a/libraries/ESP8266WebServer/src/detail/RequestHandlersImpl.h +++ b/libraries/ESP8266WebServer/src/detail/RequestHandlersImpl.h @@ -72,27 +72,41 @@ class StaticRequestHandler : public RequestHandler { , _path(path) , _cache_header(cache_header) { - if (fs.exists(path)) { - File file = fs.open(path, "r"); - _isFile = file && file.isFile(); - file.close(); - } - else { - _isFile = false; - } + DEBUGV("StaticRequestHandler: path=%s uri=%s, cache_header=%s\r\n", path, uri, cache_header == __null ? "" : cache_header); + } - DEBUGV("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header == __null ? "" : cache_header); - _baseUriLength = _uri.length(); + bool validMethod(HTTPMethod requestMethod){ + return (requestMethod == HTTP_GET) || (requestMethod == HTTP_HEAD); } - bool canHandle(HTTPMethod requestMethod, const String& requestUri) override { - if ((requestMethod != HTTP_GET) && (requestMethod != HTTP_HEAD)) - return false; + /* Deprecated version. Please use mime::getContentType instead */ + static String getContentType(const String& path) __attribute__((deprecated)) { + return mime::getContentType(path); + } - if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri)) - return false; +protected: + FS _fs; + String _uri; + String _path; + String _cache_header; +}; - return true; + +template +class StaticDirectoryRequestHandler : public StaticRequestHandler { + + using SRH = StaticRequestHandler; + using WebServerType = ESP8266WebServerTemplate; + +public: + StaticDirectoryRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header) + : + SRH(fs, path, uri, cache_header), + _baseUriLength{SRH::_uri.length()} + {} + + bool canHandle(HTTPMethod requestMethod, const String& requestUri) override { + return SRH::validMethod(requestMethod) && requestUri.startsWith(SRH::_uri); } bool handle(WebServerType& server, HTTPMethod requestMethod, const String& requestUri) override { @@ -100,42 +114,40 @@ class StaticRequestHandler : public RequestHandler { if (!canHandle(requestMethod, requestUri)) return false; - DEBUGV("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str()); + DEBUGV("DirectoryRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), SRH::_uri.c_str()); String path; - path.reserve(_path.length() + requestUri.length() + 32); - path = _path; + path.reserve(SRH::_path.length() + requestUri.length() + 32); + path = SRH::_path; - if (!_isFile) { + // Append whatever follows this URI in request to get the file path. + path += requestUri.substring(_baseUriLength); - // Append whatever follows this URI in request to get the file path. - path += requestUri.substring(_baseUriLength); + // Base URI doesn't point to a file. + // If a directory is requested, look for index file. + if (path.endsWith("/")) + path += F("index.htm"); - // Base URI doesn't point to a file. - // If a directory is requested, look for index file. - if (path.endsWith("/")) - path += F("index.htm"); - - // If neither nor .gz exist, and is a file.htm, try it with file.html instead - // For the normal case this will give a search order of index.htm, index.htm.gz, index.html, index.html.gz - if (!_fs.exists(path) && !_fs.exists(path + ".gz") && path.endsWith(".htm")) { - path += 'l'; - } + // If neither nor .gz exist, and is a file.htm, try it with file.html instead + // For the normal case this will give a search order of index.htm, index.htm.gz, index.html, index.html.gz + if (!SRH::_fs.exists(path) && !SRH::_fs.exists(path + ".gz") && path.endsWith(".htm")) { + path += 'l'; } - DEBUGV("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile); + + DEBUGV("DirectoryRequestHandler::handle: path=%s\r\n", path.c_str()); String contentType = mime::getContentType(path); using namespace mime; // look for gz file, only if the original specified path is not a gz. So part only works to send gzip via content encoding when a non compressed is asked for // if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc... - if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !_fs.exists(path)) { + if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !SRH::_fs.exists(path)) { String pathWithGz = path + FPSTR(mimeTable[gz].endsWith); - if(_fs.exists(pathWithGz)) + if(SRH::_fs.exists(pathWithGz)) path += FPSTR(mimeTable[gz].endsWith); } - File f = _fs.open(path, "r"); + File f = SRH::_fs.open(path, "r"); if (!f) return false; @@ -144,27 +156,77 @@ class StaticRequestHandler : public RequestHandler { return false; } - if (_cache_header.length() != 0) - server.sendHeader("Cache-Control", _cache_header); + if (SRH::_cache_header.length() != 0) + server.sendHeader("Cache-Control", SRH::_cache_header); server.streamFile(f, contentType, requestMethod); return true; } - /* Deprecated version. Please use mime::getContentType instead */ - static String getContentType(const String& path) __attribute__((deprecated)) { - return mime::getContentType(path); +protected: + size_t _baseUriLength; +}; + +template +class StaticFileRequestHandler + : +public StaticRequestHandler { + + using SRH = StaticRequestHandler; + using WebServerType = ESP8266WebServerTemplate; + +public: + StaticFileRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header) + : + StaticRequestHandler{fs, path, uri, cache_header} + { + File f = SRH::_fs.open(path, "r"); + MD5Builder calcMD5; + calcMD5.begin(); + calcMD5.addStream(f, f.size()); + calcMD5.calculate(); + calcMD5.getBytes(_ETag_md5); + f.close(); + } + + bool canHandle(HTTPMethod requestMethod, const String& requestUri) override { + return SRH::validMethod(requestMethod) && requestUri == SRH::_uri; + } + + bool handle(WebServerType& server, HTTPMethod requestMethod, const String & requestUri) override { + if (!canHandle(requestMethod, requestUri)) + return false; + + const String etag = "\"" + base64::encode(_ETag_md5, 16, false) + "\""; + + if(server.header("If-None-Match") == etag){ + server.send(304); + return true; + } + + File f = SRH::_fs.open(SRH::_path, "r"); + + if (!f) + return false; + + if (!f.isFile()) { + f.close(); + return false; + } + + if (SRH::_cache_header.length() != 0) + server.sendHeader("Cache-Control", SRH::_cache_header); + + server.sendHeader("ETag", etag); + + server.streamFile(f, mime::getContentType(SRH::_path), requestMethod); + return true; } protected: - FS _fs; - String _uri; - String _path; - String _cache_header; - bool _isFile; - size_t _baseUriLength; + uint8_t _ETag_md5[16]; }; } // namespace -#endif //REQUESTHANDLERSIMPL_H +#endif //REQUESTHANDLERSIMPL_H \ No newline at end of file