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

Improvements to the existing ETag implementation (#8227)

* WebServer eTag implementation improvements
This commit is contained in:
Matthias Hertel
2021-09-29 11:58:40 +02:00
committed by GitHub
parent 193043d19b
commit 3f4bcbe483
10 changed files with 708 additions and 15 deletions

View File

@ -68,6 +68,13 @@ template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::enableCORS(bool enable) {
_corsEnabled = enable;
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::enableETag(bool enable, ETagFunction fn) {
_eTagEnabled = enable;
_eTagFunction = fn;
}
template <typename ServerType>
void ESP8266WebServerTemplate<ServerType>::begin() {
close();
@ -264,10 +271,11 @@ void ESP8266WebServerTemplate<ServerType>::serveStatic(const char* uri, FS& fs,
file.close();
}
if(is_file)
if(is_file) {
_addRequestHandler(new StaticFileRequestHandler<ServerType>(fs, path, uri, cache_header));
else
} else {
_addRequestHandler(new StaticDirectoryRequestHandler<ServerType>(fs, path, uri, cache_header));
}
}
template <typename ServerType>
@ -436,6 +444,7 @@ void ESP8266WebServerTemplate<ServerType>::_prepareHeader(String& response, int
sendHeader(String(F("Keep-Alive")), String(F("timeout=")) + HTTP_MAX_CLOSE_WAIT);
}
response += _responseHeaders;
response += "\r\n";
_responseHeaders = "";

View File

@ -114,6 +114,8 @@ public:
void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& authFailMsg = String("") );
typedef std::function<void(void)> THandlerFunction;
typedef std::function<String(FS &fs, const String &fName)> ETagFunction;
void on(const Uri &uri, THandlerFunction handler);
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn);
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);
@ -122,6 +124,7 @@ public:
void onNotFound(THandlerFunction fn); //called when handler is not assigned
void onFileUpload(THandlerFunction fn); //handle file uploads
void enableCORS(bool enable);
void enableETag(bool enable, ETagFunction fn = nullptr);
const String& uri() const { return _currentUri; }
HTTPMethod method() const { return _currentMethod; }
@ -271,6 +274,9 @@ public:
}
}
bool _eTagEnabled = false;
ETagFunction _eTagFunction = nullptr;
protected:
void _addRequestHandler(RequestHandlerType* handler);
void _handleRequest();

View File

@ -9,6 +9,26 @@
namespace esp8266webserver {
// calculate an ETag for a file in filesystem based on md5 checksum
// that can be used in the http headers - include quotes.
static String calcETag(FS &fs, const String &path) {
String result;
// calculate eTag using md5 checksum
uint8_t md5_buf[16];
File f = fs.open(path, "r");
MD5Builder calcMD5;
calcMD5.begin();
calcMD5.addStream(f, f.size());
calcMD5.calculate();
calcMD5.getBytes(md5_buf);
f.close();
// create a minimal-length eTag using base64 byte[]->text encoding.
result = "\"" + base64::encode(md5_buf, 16, false) + "\"";
return(result);
} // calcETag
template<typename ServerType>
class FunctionRequestHandler : public RequestHandler<ServerType> {
using WebServerType = ESP8266WebServerTemplate<ServerType>;
@ -92,6 +112,7 @@ protected:
};
// serve all files within a given directory
template<typename ServerType>
class StaticDirectoryRequestHandler : public StaticRequestHandler<ServerType> {
@ -117,6 +138,7 @@ public:
DEBUGV("DirectoryRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), SRH::_uri.c_str());
String path;
String eTagCode;
path.reserve(SRH::_path.length() + requestUri.length() + 32);
path = SRH::_path;
@ -156,10 +178,28 @@ public:
return false;
}
if (server._eTagEnabled) {
if (server._eTagFunction) {
eTagCode = (server._eTagFunction)(SRH::_fs, path);
} else {
eTagCode = esp8266webserver::calcETag(SRH::_fs, path);
}
if (server.header("If-None-Match") == eTagCode) {
server.send(304);
return true;
}
}
if (SRH::_cache_header.length() != 0)
server.sendHeader("Cache-Control", SRH::_cache_header);
if ((server._eTagEnabled) && (eTagCode.length() > 0)) {
server.sendHeader("ETag", eTagCode);
}
server.streamFile(f, contentType, requestMethod);
return true;
}
@ -167,6 +207,8 @@ protected:
size_t _baseUriLength;
};
// Serve a specific, single file
template<typename ServerType>
class StaticFileRequestHandler
:
@ -180,13 +222,6 @@ public:
:
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 {
@ -197,11 +232,17 @@ public:
if (!canHandle(requestMethod, requestUri))
return false;
const String etag = "\"" + base64::encode(_ETag_md5, 16, false) + "\"";
if (server._eTagEnabled) {
if (server._eTagFunction) {
_eTagCode = (server._eTagFunction)(SRH::_fs, SRH::_path);
} else if (_eTagCode.isEmpty()) {
_eTagCode = esp8266webserver::calcETag(SRH::_fs, SRH::_path);
}
if(server.header("If-None-Match") == etag){
server.send(304);
return true;
if (server.header("If-None-Match") == _eTagCode) {
server.send(304);
return true;
}
}
File f = SRH::_fs.open(SRH::_path, "r");
@ -217,14 +258,16 @@ public:
if (SRH::_cache_header.length() != 0)
server.sendHeader("Cache-Control", SRH::_cache_header);
server.sendHeader("ETag", etag);
if ((server._eTagEnabled) && (_eTagCode.length() > 0)) {
server.sendHeader("ETag", _eTagCode);
}
server.streamFile(f, mime::getContentType(SRH::_path), requestMethod);
return true;
}
protected:
uint8_t _ETag_md5[16];
String _eTagCode; // ETag code calculated for this file as used in http header include quotes.
};
} // namespace