1
0
mirror of https://github.com/esp8266/Arduino.git synced 2025-06-03 07:02:28 +03:00

ETag support for WebServer (#7709)

* implemented with native md5
* testing locals variables
* less memory used, partil refactoring
* reworked serveStatic logic, different handler for File and Directory
This commit is contained in:
Luca Passarella 2020-12-01 09:52:58 +01:00 committed by GitHub
parent 2e4563c76b
commit 7a368747e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 162 additions and 86 deletions

View File

@ -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<ServerType>::_addRequestHandler(RequestHandlerType
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
_addRequestHandler(new StaticRequestHandler<ServerType>(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<ServerType>(fs, path, uri, cache_header));
else
_addRequestHandler(new StaticDirectoryRequestHandler<ServerType>(fs, path, uri, cache_header));
}
template <typename ServerType>
@ -606,15 +618,18 @@ const String& ESP8266WebServerTemplate<ServerType>::header(const String& name) c
return emptyString;
}
template <typename ServerType>
template<typename ServerType>
void ESP8266WebServerTemplate<ServerType>::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];
}
}

View File

@ -308,5 +308,4 @@ protected:
using ESP8266WebServer = esp8266webserver::ESP8266WebServerTemplate<WiFiServer>;
using RequestHandler = esp8266webserver::RequestHandler<WiFiServer>;
#endif //ESP8266WEBSERVER_H
#endif //ESP8266WEBSERVER_H

View File

@ -72,83 +72,11 @@ public:
, _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 isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header == __null ? "" : cache_header);
_baseUriLength = _uri.length();
DEBUGV("StaticRequestHandler: path=%s uri=%s, cache_header=%s\r\n", path, uri, cache_header == __null ? "" : cache_header);
}
bool canHandle(HTTPMethod requestMethod, const String& requestUri) override {
if ((requestMethod != HTTP_GET) && (requestMethod != HTTP_HEAD))
return false;
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
return false;
return true;
}
bool handle(WebServerType& server, HTTPMethod requestMethod, const String& requestUri) override {
if (!canHandle(requestMethod, requestUri))
return false;
DEBUGV("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
String path;
path.reserve(_path.length() + requestUri.length() + 32);
path = _path;
if (!_isFile) {
// 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");
// If neither <blah> nor <blah>.gz exist, and <blah> 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';
}
}
DEBUGV("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
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)) {
String pathWithGz = path + FPSTR(mimeTable[gz].endsWith);
if(_fs.exists(pathWithGz))
path += FPSTR(mimeTable[gz].endsWith);
}
File f = _fs.open(path, "r");
if (!f)
return false;
if (!f.isFile()) {
f.close();
return false;
}
if (_cache_header.length() != 0)
server.sendHeader("Cache-Control", _cache_header);
server.streamFile(f, contentType, requestMethod);
return true;
bool validMethod(HTTPMethod requestMethod){
return (requestMethod == HTTP_GET) || (requestMethod == HTTP_HEAD);
}
/* Deprecated version. Please use mime::getContentType instead */
@ -161,10 +89,144 @@ protected:
String _uri;
String _path;
String _cache_header;
bool _isFile;
};
template<typename ServerType>
class StaticDirectoryRequestHandler : public StaticRequestHandler<ServerType> {
using SRH = StaticRequestHandler<ServerType>;
using WebServerType = ESP8266WebServerTemplate<ServerType>;
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 {
if (!canHandle(requestMethod, requestUri))
return false;
DEBUGV("DirectoryRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), SRH::_uri.c_str());
String path;
path.reserve(SRH::_path.length() + requestUri.length() + 32);
path = SRH::_path;
// 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");
// If neither <blah> nor <blah>.gz exist, and <blah> 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("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)) && !SRH::_fs.exists(path)) {
String pathWithGz = path + FPSTR(mimeTable[gz].endsWith);
if(SRH::_fs.exists(pathWithGz))
path += FPSTR(mimeTable[gz].endsWith);
}
File f = SRH::_fs.open(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.streamFile(f, contentType, requestMethod);
return true;
}
protected:
size_t _baseUriLength;
};
template<typename ServerType>
class StaticFileRequestHandler
:
public StaticRequestHandler<ServerType> {
using SRH = StaticRequestHandler<ServerType>;
using WebServerType = ESP8266WebServerTemplate<ServerType>;
public:
StaticFileRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header)
:
StaticRequestHandler<ServerType>{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:
uint8_t _ETag_md5[16];
};
} // namespace
#endif //REQUESTHANDLERSIMPL_H
#endif //REQUESTHANDLERSIMPL_H