Skip to content

Commit

Permalink
ETag support for WebServer (#7709)
Browse files Browse the repository at this point in the history
* implemented with native md5
* testing locals variables
* less memory used, partil refactoring
* reworked serveStatic logic, different handler for File and Directory
  • Loading branch information
imwhocodes authored Dec 1, 2020
1 parent 2e4563c commit 7a36874
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 57 deletions.
29 changes: 22 additions & 7 deletions libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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];
}
}

Expand Down
3 changes: 1 addition & 2 deletions libraries/ESP8266WebServer/src/ESP8266WebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -308,5 +308,4 @@ class ESP8266WebServerTemplate
using ESP8266WebServer = esp8266webserver::ESP8266WebServerTemplate<WiFiServer>;
using RequestHandler = esp8266webserver::RequestHandler<WiFiServer>;


#endif //ESP8266WEBSERVER_H
#endif //ESP8266WEBSERVER_H
158 changes: 110 additions & 48 deletions libraries/ESP8266WebServer/src/detail/RequestHandlersImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,70 +72,82 @@ class StaticRequestHandler : public RequestHandler<ServerType> {
, _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, cache_header=%s\r\n", path, uri, cache_header == __null ? "" : cache_header);
}

DEBUGV("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header == __null ? "" : cache_header);
_baseUriLength = _uri.length();
bool validMethod(HTTPMethod requestMethod){
return (requestMethod == HTTP_GET) || (requestMethod == HTTP_HEAD);
}

bool canHandle(HTTPMethod requestMethod, const String& requestUri) override {
if ((requestMethod != HTTP_GET) && (requestMethod != HTTP_HEAD))
return false;
/* Deprecated version. Please use mime::getContentType instead */
static String getContentType(const String& path) __attribute__((deprecated)) {
return mime::getContentType(path);
}

if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
return false;
protected:
FS _fs;
String _uri;
String _path;
String _cache_header;
};

return true;

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("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
DEBUGV("DirectoryRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), SRH::_uri.c_str());

String path;
path.reserve(_path.length() + requestUri.length() + 32);
path = _path;
path.reserve(SRH::_path.length() + requestUri.length() + 32);
path = SRH::_path;

if (!_isFile) {
// Append whatever follows this URI in request to get the file path.
path += requestUri.substring(_baseUriLength);

// 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");

// 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';
}
// 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("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);

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)) && !_fs.exists(path)) {
if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !SRH::_fs.exists(path)) {
String pathWithGz = path + FPSTR(mimeTable[gz].endsWith);
if(_fs.exists(pathWithGz))
if(SRH::_fs.exists(pathWithGz))
path += FPSTR(mimeTable[gz].endsWith);
}

File f = _fs.open(path, "r");
File f = SRH::_fs.open(path, "r");
if (!f)
return false;

Expand All @@ -144,27 +156,77 @@ class StaticRequestHandler : public RequestHandler<ServerType> {
return false;
}

if (_cache_header.length() != 0)
server.sendHeader("Cache-Control", _cache_header);
if (SRH::_cache_header.length() != 0)
server.sendHeader("Cache-Control", SRH::_cache_header);

server.streamFile(f, contentType, requestMethod);
return true;
}

/* Deprecated version. Please use mime::getContentType instead */
static String getContentType(const String& path) __attribute__((deprecated)) {
return mime::getContentType(path);
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:
FS _fs;
String _uri;
String _path;
String _cache_header;
bool _isFile;
size_t _baseUriLength;
uint8_t _ETag_md5[16];
};

} // namespace

#endif //REQUESTHANDLERSIMPL_H
#endif //REQUESTHANDLERSIMPL_H

0 comments on commit 7a36874

Please sign in to comment.