Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to the existing ETag implementation #8227

Merged
merged 13 commits into from
Sep 29, 2021
13 changes: 11 additions & 2 deletions libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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 = "";
Expand Down
6 changes: 6 additions & 0 deletions libraries/ESP8266WebServer/src/ESP8266WebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ class ESP8266WebServerTemplate
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);
Expand All @@ -122,6 +124,7 @@ class ESP8266WebServerTemplate
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; }
Expand Down Expand Up @@ -271,6 +274,9 @@ class ESP8266WebServerTemplate
}
}

bool _eTagEnabled = false;
ETagFunction _eTagFunction = nullptr;

protected:
void _addRequestHandler(RequestHandlerType* handler);
void _handleRequest();
Expand Down
70 changes: 56 additions & 14 deletions libraries/ESP8266WebServer/src/detail/RequestHandlersImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@

namespace esp8266webserver {

// calculate an eTag for a file in filesystem
static String calcETag(FS &fs, const String path) {
mathertel marked this conversation as resolved.
Show resolved Hide resolved
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>;
Expand Down Expand Up @@ -92,6 +111,7 @@ class StaticRequestHandler : public RequestHandler<ServerType> {
};


// serve all files within a given directory
template<typename ServerType>
class StaticDirectoryRequestHandler : public StaticRequestHandler<ServerType> {

Expand All @@ -117,6 +137,7 @@ class StaticDirectoryRequestHandler : public StaticRequestHandler<ServerType> {
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;

Expand Down Expand Up @@ -156,17 +177,37 @@ class StaticDirectoryRequestHandler : public StaticRequestHandler<ServerType> {
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;
}

protected:
size_t _baseUriLength;
};


// Serve a specific, single file
template<typename ServerType>
class StaticFileRequestHandler
:
Expand All @@ -180,13 +221,6 @@ public StaticRequestHandler<ServerType> {
:
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 {
Expand All @@ -197,11 +231,17 @@ public StaticRequestHandler<ServerType> {
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;
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") == _eTagCode) {
server.send(304);
return true;
}
}

File f = SRH::_fs.open(SRH::_path, "r");
Expand All @@ -217,14 +257,16 @@ public StaticRequestHandler<ServerType> {
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 as used in http header for this single file
mathertel marked this conversation as resolved.
Show resolved Hide resolved
};

} // namespace
Expand Down