2015-12-19 18:53:33 +02:00
|
|
|
/*
|
2016-02-09 02:56:01 +02:00
|
|
|
Asynchronous WebServer library for Espressif MCUs
|
|
|
|
|
|
|
|
|
|
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
|
|
|
|
|
This file is part of the esp8266 core for Arduino environment.
|
|
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
|
|
|
modify it under the terms of the GNU Lesser General Public
|
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
|
Lesser General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
|
|
|
License along with this library; if not, write to the Free Software
|
|
|
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
|
*/
|
2015-12-19 18:53:33 +02:00
|
|
|
#include "ESPAsyncWebServer.h"
|
2016-02-09 02:56:01 +02:00
|
|
|
#include "WebHandlerImpl.h"
|
2015-12-19 18:53:33 +02:00
|
|
|
|
2016-06-25 20:04:06 +01:00
|
|
|
AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control)
|
2024-06-27 20:34:10 +02:00
|
|
|
: _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr) {
|
2016-06-17 01:43:59 +03:00
|
|
|
// Ensure leading '/'
|
2024-06-27 20:34:10 +02:00
|
|
|
if (_uri.length() == 0 || _uri[0] != '/')
|
|
|
|
|
_uri = String('/') + _uri;
|
|
|
|
|
if (_path.length() == 0 || _path[0] != '/')
|
|
|
|
|
_path = String('/') + _path;
|
2016-06-17 01:43:59 +03:00
|
|
|
|
Fix bug in AsyncStaticWebHandler (#37)
* 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.
* Fix path problems in static handler and improve performance.
* Revert "Merge remote-tracking branch 'me-no-dev/master'"
This reverts commit 1621206357843b5de0272fe4579387af3011e656, reversing
changes made to a01972c9e569967dd3d761c364066518b4901e46.
* Revert "HTTP 302 and 304 Support"
This reverts commit a01972c9e569967dd3d761c364066518b4901e46.
* Sync with me-no-dev/master
* Fix AsyncStaticWebHandler
Fix ambiguity of serving file or directory.
The following options will all have the same outcome, the last two will
server the default file ‘index.htm’ faster:
server.serveStatic("/fs", SPIFFS, "/web");
server.serveStatic("/fs/", SPIFFS, "/web");
server.serveStatic("/fs", SPIFFS, "/web/");
server.serveStatic("/fs/", SPIFFS, "/web/");
2016-06-18 17:43:52 +01:00
|
|
|
// If path ends with '/' we assume a hint that this is a directory to improve performance.
|
|
|
|
|
// However - if it does not end with '/' we, can't assume a file, path can still be a directory.
|
2024-06-27 20:34:10 +02:00
|
|
|
_isDir = _path[_path.length() - 1] == '/';
|
2016-06-17 01:43:59 +03:00
|
|
|
|
Fix bug in AsyncStaticWebHandler (#37)
* 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.
* Fix path problems in static handler and improve performance.
* Revert "Merge remote-tracking branch 'me-no-dev/master'"
This reverts commit 1621206357843b5de0272fe4579387af3011e656, reversing
changes made to a01972c9e569967dd3d761c364066518b4901e46.
* Revert "HTTP 302 and 304 Support"
This reverts commit a01972c9e569967dd3d761c364066518b4901e46.
* Sync with me-no-dev/master
* Fix AsyncStaticWebHandler
Fix ambiguity of serving file or directory.
The following options will all have the same outcome, the last two will
server the default file ‘index.htm’ faster:
server.serveStatic("/fs", SPIFFS, "/web");
server.serveStatic("/fs/", SPIFFS, "/web");
server.serveStatic("/fs", SPIFFS, "/web/");
server.serveStatic("/fs/", SPIFFS, "/web/");
2016-06-18 17:43:52 +01:00
|
|
|
// Remove the trailing '/' so we can handle default file
|
2016-06-17 01:43:59 +03:00
|
|
|
// Notice that root will be "" not "/"
|
2024-06-27 20:34:10 +02:00
|
|
|
if (_uri[_uri.length() - 1] == '/')
|
|
|
|
|
_uri = _uri.substring(0, _uri.length() - 1);
|
|
|
|
|
if (_path[_path.length() - 1] == '/')
|
|
|
|
|
_path = _path.substring(0, _path.length() - 1);
|
2016-06-17 01:43:59 +03:00
|
|
|
|
|
|
|
|
// Reset stats
|
|
|
|
|
_gzipFirst = false;
|
2016-06-25 20:04:06 +01:00
|
|
|
_gzipStats = 0xF8;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-27 20:34:10 +02:00
|
|
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir) {
|
2016-06-25 20:04:06 +01:00
|
|
|
_isDir = isDir;
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-27 20:34:10 +02:00
|
|
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename) {
|
2016-06-25 20:04:06 +01:00
|
|
|
_default_file = String(filename);
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-27 20:34:10 +02:00
|
|
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control) {
|
2016-06-25 20:04:06 +01:00
|
|
|
_cache_control = String(cache_control);
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-27 20:34:10 +02:00
|
|
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified) {
|
2024-01-26 14:38:50 +09:00
|
|
|
_last_modified = last_modified;
|
2016-06-25 20:04:06 +01:00
|
|
|
return *this;
|
2016-06-17 01:43:59 +03:00
|
|
|
}
|
|
|
|
|
|
2024-06-27 20:34:10 +02:00
|
|
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified) {
|
2024-01-27 17:58:40 +09:00
|
|
|
auto formatP = PSTR("%a, %d %b %Y %H:%M:%S %Z");
|
|
|
|
|
char format[strlen_P(formatP) + 1];
|
|
|
|
|
strcpy_P(format, formatP);
|
|
|
|
|
|
|
|
|
|
char result[30];
|
|
|
|
|
strftime(result, sizeof(result), format, last_modified);
|
2024-06-27 20:34:10 +02:00
|
|
|
return setLastModified((const char*)result);
|
2016-06-27 20:08:09 +03:00
|
|
|
}
|
2017-03-06 02:02:33 +08:00
|
|
|
|
2016-06-28 01:45:43 +03:00
|
|
|
#ifdef ESP8266
|
2024-06-27 20:34:10 +02:00
|
|
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified) {
|
|
|
|
|
return setLastModified((struct tm*)gmtime(&last_modified));
|
2016-06-28 01:39:35 +03:00
|
|
|
}
|
|
|
|
|
|
2024-06-27 20:34:10 +02:00
|
|
|
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified() {
|
2016-06-28 01:39:35 +03:00
|
|
|
time_t last_modified;
|
2024-06-27 20:34:10 +02:00
|
|
|
if (time(&last_modified) == 0) // time is not yet set
|
2016-06-28 01:39:35 +03:00
|
|
|
return *this;
|
|
|
|
|
return setLastModified(last_modified);
|
|
|
|
|
}
|
2016-06-28 01:45:43 +03:00
|
|
|
#endif
|
2024-06-27 20:34:10 +02:00
|
|
|
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest* request) {
|
|
|
|
|
if (request->method() != HTTP_GET || !request->url().startsWith(_uri) || !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP)) {
|
2017-08-18 17:52:21 +03:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (_getFile(request)) {
|
2016-06-25 20:04:06 +01:00
|
|
|
// We interested in "If-Modified-Since" header to check if file was modified
|
|
|
|
|
if (_last_modified.length())
|
2020-04-28 16:36:40 -04:00
|
|
|
request->addInterestingHeader(F("If-Modified-Since"));
|
2016-06-25 20:04:06 +01:00
|
|
|
|
2024-06-27 20:34:10 +02:00
|
|
|
if (_cache_control.length())
|
2020-04-28 16:36:40 -04:00
|
|
|
request->addInterestingHeader(F("If-None-Match"));
|
2016-08-25 22:15:17 +03:00
|
|
|
|
2016-06-16 10:52:11 +01:00
|
|
|
return true;
|
2016-01-28 00:06:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
2015-12-19 18:53:33 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-27 20:34:10 +02:00
|
|
|
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest* request) {
|
2016-06-16 10:52:11 +01:00
|
|
|
// Remove the found uri
|
|
|
|
|
String path = request->url().substring(_uri.length());
|
|
|
|
|
|
Fix bug in AsyncStaticWebHandler (#37)
* 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.
* Fix path problems in static handler and improve performance.
* Revert "Merge remote-tracking branch 'me-no-dev/master'"
This reverts commit 1621206357843b5de0272fe4579387af3011e656, reversing
changes made to a01972c9e569967dd3d761c364066518b4901e46.
* Revert "HTTP 302 and 304 Support"
This reverts commit a01972c9e569967dd3d761c364066518b4901e46.
* Sync with me-no-dev/master
* Fix AsyncStaticWebHandler
Fix ambiguity of serving file or directory.
The following options will all have the same outcome, the last two will
server the default file ‘index.htm’ faster:
server.serveStatic("/fs", SPIFFS, "/web");
server.serveStatic("/fs/", SPIFFS, "/web");
server.serveStatic("/fs", SPIFFS, "/web/");
server.serveStatic("/fs/", SPIFFS, "/web/");
2016-06-18 17:43:52 +01:00
|
|
|
// We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/'
|
2024-06-27 20:34:10 +02:00
|
|
|
bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length() - 1] == '/');
|
2016-01-28 00:06:08 +02:00
|
|
|
|
2016-06-16 10:52:11 +01:00
|
|
|
path = _path + path;
|
2016-01-27 21:59:27 +02:00
|
|
|
|
2016-06-16 10:52:11 +01:00
|
|
|
// Do we have a file or .gz file
|
2016-06-17 01:43:59 +03:00
|
|
|
if (!canSkipFileCheck && _fileExists(request, path))
|
|
|
|
|
return true;
|
2016-01-28 00:06:08 +02:00
|
|
|
|
2016-06-25 20:04:06 +01:00
|
|
|
// Can't handle if not default file
|
|
|
|
|
if (_default_file.length() == 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// Try to add default file, ensure there is a trailing '/' ot the path.
|
2024-06-27 20:34:10 +02:00
|
|
|
if (path.length() == 0 || path[path.length() - 1] != '/')
|
2020-04-28 16:36:40 -04:00
|
|
|
path += String('/');
|
2016-06-25 20:04:06 +01:00
|
|
|
path += _default_file;
|
2016-01-27 21:59:27 +02:00
|
|
|
|
2016-06-17 01:53:21 +03:00
|
|
|
return _fileExists(request, path);
|
2016-06-16 10:52:11 +01:00
|
|
|
}
|
|
|
|
|
|
2017-09-07 22:01:58 +03:00
|
|
|
#ifdef ESP32
|
2024-06-27 20:34:10 +02:00
|
|
|
#define FILE_IS_REAL(f) (f == true && !f.isDirectory())
|
2017-09-07 22:01:58 +03:00
|
|
|
#else
|
2024-06-27 20:34:10 +02:00
|
|
|
#define FILE_IS_REAL(f) (f == true)
|
2017-09-07 22:01:58 +03:00
|
|
|
#endif
|
|
|
|
|
|
2024-06-27 20:34:10 +02:00
|
|
|
bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest* request, const String& path) {
|
2016-06-16 10:52:11 +01:00
|
|
|
bool fileFound = false;
|
|
|
|
|
bool gzipFound = false;
|
2016-01-27 23:42:16 +02:00
|
|
|
|
2020-04-28 16:36:40 -04:00
|
|
|
String gzip = path + F(".gz");
|
2016-06-16 10:52:11 +01:00
|
|
|
|
|
|
|
|
if (_gzipFirst) {
|
2024-06-27 20:34:10 +02:00
|
|
|
if (_fs.exists(gzip)) {
|
|
|
|
|
request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
|
|
|
|
|
gzipFound = FILE_IS_REAL(request->_tempFile);
|
|
|
|
|
}
|
|
|
|
|
if (!gzipFound) {
|
|
|
|
|
if (_fs.exists(path)) {
|
|
|
|
|
request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
|
|
|
|
|
fileFound = FILE_IS_REAL(request->_tempFile);
|
|
|
|
|
}
|
2016-06-17 01:43:59 +03:00
|
|
|
}
|
2016-01-21 19:52:06 +02:00
|
|
|
} else {
|
2024-06-27 20:34:10 +02:00
|
|
|
if (_fs.exists(path)) {
|
|
|
|
|
request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
|
|
|
|
|
fileFound = FILE_IS_REAL(request->_tempFile);
|
2020-08-19 14:22:06 -04:00
|
|
|
}
|
2024-06-27 20:34:10 +02:00
|
|
|
if (!fileFound) {
|
|
|
|
|
if (_fs.exists(gzip)) {
|
|
|
|
|
request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
|
|
|
|
|
gzipFound = FILE_IS_REAL(request->_tempFile);
|
|
|
|
|
}
|
2016-06-17 01:43:59 +03:00
|
|
|
}
|
2015-12-19 18:53:33 +02:00
|
|
|
}
|
2016-01-28 00:06:08 +02:00
|
|
|
|
2016-06-16 10:52:11 +01:00
|
|
|
bool found = fileFound || gzipFound;
|
|
|
|
|
|
2016-06-17 01:43:59 +03:00
|
|
|
if (found) {
|
2016-06-25 20:04:06 +01:00
|
|
|
// Extract the file name from the path and keep it in _tempObject
|
|
|
|
|
size_t pathLen = path.length();
|
2024-06-27 20:34:10 +02:00
|
|
|
char* _tempPath = (char*)malloc(pathLen + 1);
|
|
|
|
|
snprintf_P(_tempPath, pathLen + 1, PSTR("%s"), path.c_str());
|
2016-06-17 02:23:18 +03:00
|
|
|
request->_tempObject = (void*)_tempPath;
|
2016-06-25 20:04:06 +01:00
|
|
|
|
|
|
|
|
// Calculate gzip statistic
|
|
|
|
|
_gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0);
|
2024-06-27 20:34:10 +02:00
|
|
|
if (_gzipStats == 0x00)
|
|
|
|
|
_gzipFirst = false; // All files are not gzip
|
|
|
|
|
else if (_gzipStats == 0xFF)
|
|
|
|
|
_gzipFirst = true; // All files are gzip
|
|
|
|
|
else
|
|
|
|
|
_gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first
|
2016-06-16 10:52:11 +01:00
|
|
|
}
|
2016-01-28 00:06:08 +02:00
|
|
|
|
2016-06-16 10:52:11 +01:00
|
|
|
return found;
|
2016-01-28 00:06:08 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-27 20:34:10 +02:00
|
|
|
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const {
|
2016-06-16 10:52:11 +01:00
|
|
|
uint8_t w = value;
|
|
|
|
|
uint8_t n;
|
2024-06-27 20:34:10 +02:00
|
|
|
for (n = 0; w != 0; n++)
|
|
|
|
|
w &= w - 1;
|
2016-06-16 10:52:11 +01:00
|
|
|
return n;
|
|
|
|
|
}
|
2016-01-28 00:06:08 +02:00
|
|
|
|
2024-06-27 20:34:10 +02:00
|
|
|
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
|
2016-06-25 20:04:06 +01:00
|
|
|
// Get the filename from request->_tempObject and free it
|
2024-01-27 17:58:40 +09:00
|
|
|
String filename = String((char*)request->_tempObject);
|
2016-06-25 20:04:06 +01:00
|
|
|
free(request->_tempObject);
|
|
|
|
|
request->_tempObject = NULL;
|
2024-06-27 20:34:10 +02:00
|
|
|
if ((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str()))
|
|
|
|
|
return request->requestAuthentication();
|
2016-06-25 20:04:06 +01:00
|
|
|
|
2016-06-17 01:43:59 +03:00
|
|
|
if (request->_tempFile == true) {
|
2024-06-27 20:34:10 +02:00
|
|
|
time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS)
|
2024-06-14 10:58:45 +02:00
|
|
|
// set etag to lastmod timestamp if available, otherwise to size
|
|
|
|
|
String etag;
|
|
|
|
|
if (lw) {
|
|
|
|
|
setLastModified(gmtime(&lw));
|
2024-06-14 01:34:29 +05:30
|
|
|
#if defined(TARGET_RP2040)
|
|
|
|
|
// time_t == long long int
|
|
|
|
|
const size_t len = 1 + 8 * sizeof(time_t);
|
|
|
|
|
char buf[len];
|
|
|
|
|
char* ret = lltoa(lw, buf, len, 10);
|
|
|
|
|
etag = ret ? String(ret) : String(request->_tempFile.size());
|
|
|
|
|
#else
|
2024-06-14 10:58:45 +02:00
|
|
|
etag = String(lw);
|
2024-06-14 01:34:29 +05:30
|
|
|
#endif
|
2024-06-14 10:58:45 +02:00
|
|
|
} else {
|
|
|
|
|
etag = String(request->_tempFile.size());
|
|
|
|
|
}
|
2020-04-28 16:36:40 -04:00
|
|
|
if (_last_modified.length() && _last_modified == request->header(F("If-Modified-Since"))) {
|
2016-08-25 22:15:17 +03:00
|
|
|
request->_tempFile.close();
|
2016-06-25 20:04:06 +01:00
|
|
|
request->send(304); // Not modified
|
2020-04-28 16:36:40 -04:00
|
|
|
} else if (_cache_control.length() && request->hasHeader(F("If-None-Match")) && request->header(F("If-None-Match")).equals(etag)) {
|
2016-08-25 22:15:17 +03:00
|
|
|
request->_tempFile.close();
|
2024-06-27 20:34:10 +02:00
|
|
|
AsyncWebServerResponse* response = new AsyncBasicResponse(304); // Not modified
|
2020-04-28 16:36:40 -04:00
|
|
|
response->addHeader(F("Cache-Control"), _cache_control);
|
|
|
|
|
response->addHeader(F("ETag"), etag);
|
2016-08-25 22:29:27 +03:00
|
|
|
request->send(response);
|
2016-06-25 20:04:06 +01:00
|
|
|
} else {
|
2024-06-27 20:34:10 +02:00
|
|
|
AsyncWebServerResponse* response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback);
|
2016-06-25 20:04:06 +01:00
|
|
|
if (_last_modified.length())
|
2020-04-28 16:36:40 -04:00
|
|
|
response->addHeader(F("Last-Modified"), _last_modified);
|
2024-06-27 20:34:10 +02:00
|
|
|
if (_cache_control.length()) {
|
2020-04-28 16:36:40 -04:00
|
|
|
response->addHeader(F("Cache-Control"), _cache_control);
|
|
|
|
|
response->addHeader(F("ETag"), etag);
|
2016-08-25 22:15:17 +03:00
|
|
|
}
|
2016-06-25 20:04:06 +01:00
|
|
|
request->send(response);
|
|
|
|
|
}
|
2016-01-27 21:59:27 +02:00
|
|
|
} else {
|
2015-12-19 18:53:33 +02:00
|
|
|
request->send(404);
|
2016-01-27 21:59:27 +02:00
|
|
|
}
|
2015-12-19 18:53:33 +02:00
|
|
|
}
|