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(); - - }