Skip to content

Commit 7a36874

Browse files
authored
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
1 parent 2e4563c commit 7a36874

File tree

3 files changed

+133
-57
lines changed

3 files changed

+133
-57
lines changed

libraries/ESP8266WebServer/src/ESP8266WebServer-impl.h

+22-7
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ static const char qop_auth[] PROGMEM = "qop=auth";
3434
static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\"";
3535
static const char WWW_Authenticate[] PROGMEM = "WWW-Authenticate";
3636
static const char Content_Length[] PROGMEM = "Content-Length";
37+
static const char ETAG_HEADER[] PROGMEM = "If-None-Match";
3738

3839
namespace esp8266webserver {
3940

@@ -254,7 +255,18 @@ void ESP8266WebServerTemplate<ServerType>::_addRequestHandler(RequestHandlerType
254255

255256
template <typename ServerType>
256257
void ESP8266WebServerTemplate<ServerType>::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
257-
_addRequestHandler(new StaticRequestHandler<ServerType>(fs, path, uri, cache_header));
258+
bool is_file = false;
259+
260+
if (fs.exists(path)) {
261+
File file = fs.open(path, "r");
262+
is_file = file && file.isFile();
263+
file.close();
264+
}
265+
266+
if(is_file)
267+
_addRequestHandler(new StaticFileRequestHandler<ServerType>(fs, path, uri, cache_header));
268+
else
269+
_addRequestHandler(new StaticDirectoryRequestHandler<ServerType>(fs, path, uri, cache_header));
258270
}
259271

260272
template <typename ServerType>
@@ -606,15 +618,18 @@ const String& ESP8266WebServerTemplate<ServerType>::header(const String& name) c
606618
return emptyString;
607619
}
608620

609-
template <typename ServerType>
621+
622+
template<typename ServerType>
610623
void ESP8266WebServerTemplate<ServerType>::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
611-
_headerKeysCount = headerKeysCount + 1;
612-
if (_currentHeaders)
613-
delete[]_currentHeaders;
624+
_headerKeysCount = headerKeysCount + 2;
625+
if (_currentHeaders){
626+
delete[] _currentHeaders;
627+
}
614628
_currentHeaders = new RequestArgument[_headerKeysCount];
615629
_currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER);
616-
for (int i = 1; i < _headerKeysCount; i++){
617-
_currentHeaders[i].key = headerKeys[i-1];
630+
_currentHeaders[1].key = FPSTR(ETAG_HEADER);
631+
for (int i = 2; i < _headerKeysCount; i++){
632+
_currentHeaders[i].key = headerKeys[i-2];
618633
}
619634
}
620635

libraries/ESP8266WebServer/src/ESP8266WebServer.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -308,5 +308,4 @@ class ESP8266WebServerTemplate
308308
using ESP8266WebServer = esp8266webserver::ESP8266WebServerTemplate<WiFiServer>;
309309
using RequestHandler = esp8266webserver::RequestHandler<WiFiServer>;
310310

311-
312-
#endif //ESP8266WEBSERVER_H
311+
#endif //ESP8266WEBSERVER_H

libraries/ESP8266WebServer/src/detail/RequestHandlersImpl.h

+110-48
Original file line numberDiff line numberDiff line change
@@ -72,70 +72,82 @@ class StaticRequestHandler : public RequestHandler<ServerType> {
7272
, _path(path)
7373
, _cache_header(cache_header)
7474
{
75-
if (fs.exists(path)) {
76-
File file = fs.open(path, "r");
77-
_isFile = file && file.isFile();
78-
file.close();
79-
}
80-
else {
81-
_isFile = false;
82-
}
75+
DEBUGV("StaticRequestHandler: path=%s uri=%s, cache_header=%s\r\n", path, uri, cache_header == __null ? "" : cache_header);
76+
}
8377

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

88-
bool canHandle(HTTPMethod requestMethod, const String& requestUri) override {
89-
if ((requestMethod != HTTP_GET) && (requestMethod != HTTP_HEAD))
90-
return false;
82+
/* Deprecated version. Please use mime::getContentType instead */
83+
static String getContentType(const String& path) __attribute__((deprecated)) {
84+
return mime::getContentType(path);
85+
}
9186

92-
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
93-
return false;
87+
protected:
88+
FS _fs;
89+
String _uri;
90+
String _path;
91+
String _cache_header;
92+
};
9493

95-
return true;
94+
95+
template<typename ServerType>
96+
class StaticDirectoryRequestHandler : public StaticRequestHandler<ServerType> {
97+
98+
using SRH = StaticRequestHandler<ServerType>;
99+
using WebServerType = ESP8266WebServerTemplate<ServerType>;
100+
101+
public:
102+
StaticDirectoryRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header)
103+
:
104+
SRH(fs, path, uri, cache_header),
105+
_baseUriLength{SRH::_uri.length()}
106+
{}
107+
108+
bool canHandle(HTTPMethod requestMethod, const String& requestUri) override {
109+
return SRH::validMethod(requestMethod) && requestUri.startsWith(SRH::_uri);
96110
}
97111

98112
bool handle(WebServerType& server, HTTPMethod requestMethod, const String& requestUri) override {
99113

100114
if (!canHandle(requestMethod, requestUri))
101115
return false;
102116

103-
DEBUGV("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
117+
DEBUGV("DirectoryRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), SRH::_uri.c_str());
104118

105119
String path;
106-
path.reserve(_path.length() + requestUri.length() + 32);
107-
path = _path;
120+
path.reserve(SRH::_path.length() + requestUri.length() + 32);
121+
path = SRH::_path;
108122

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

111-
// Append whatever follows this URI in request to get the file path.
112-
path += requestUri.substring(_baseUriLength);
126+
// Base URI doesn't point to a file.
127+
// If a directory is requested, look for index file.
128+
if (path.endsWith("/"))
129+
path += F("index.htm");
113130

114-
// Base URI doesn't point to a file.
115-
// If a directory is requested, look for index file.
116-
if (path.endsWith("/"))
117-
path += F("index.htm");
118-
119-
// If neither <blah> nor <blah>.gz exist, and <blah> is a file.htm, try it with file.html instead
120-
// For the normal case this will give a search order of index.htm, index.htm.gz, index.html, index.html.gz
121-
if (!_fs.exists(path) && !_fs.exists(path + ".gz") && path.endsWith(".htm")) {
122-
path += 'l';
123-
}
131+
// If neither <blah> nor <blah>.gz exist, and <blah> is a file.htm, try it with file.html instead
132+
// For the normal case this will give a search order of index.htm, index.htm.gz, index.html, index.html.gz
133+
if (!SRH::_fs.exists(path) && !SRH::_fs.exists(path + ".gz") && path.endsWith(".htm")) {
134+
path += 'l';
124135
}
125-
DEBUGV("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
136+
137+
DEBUGV("DirectoryRequestHandler::handle: path=%s\r\n", path.c_str());
126138

127139
String contentType = mime::getContentType(path);
128140

129141
using namespace mime;
130142
// 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
131143
// if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc...
132-
if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !_fs.exists(path)) {
144+
if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !SRH::_fs.exists(path)) {
133145
String pathWithGz = path + FPSTR(mimeTable[gz].endsWith);
134-
if(_fs.exists(pathWithGz))
146+
if(SRH::_fs.exists(pathWithGz))
135147
path += FPSTR(mimeTable[gz].endsWith);
136148
}
137149

138-
File f = _fs.open(path, "r");
150+
File f = SRH::_fs.open(path, "r");
139151
if (!f)
140152
return false;
141153

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

147-
if (_cache_header.length() != 0)
148-
server.sendHeader("Cache-Control", _cache_header);
159+
if (SRH::_cache_header.length() != 0)
160+
server.sendHeader("Cache-Control", SRH::_cache_header);
149161

150162
server.streamFile(f, contentType, requestMethod);
151163
return true;
152164
}
153165

154-
/* Deprecated version. Please use mime::getContentType instead */
155-
static String getContentType(const String& path) __attribute__((deprecated)) {
156-
return mime::getContentType(path);
166+
protected:
167+
size_t _baseUriLength;
168+
};
169+
170+
template<typename ServerType>
171+
class StaticFileRequestHandler
172+
:
173+
public StaticRequestHandler<ServerType> {
174+
175+
using SRH = StaticRequestHandler<ServerType>;
176+
using WebServerType = ESP8266WebServerTemplate<ServerType>;
177+
178+
public:
179+
StaticFileRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header)
180+
:
181+
StaticRequestHandler<ServerType>{fs, path, uri, cache_header}
182+
{
183+
File f = SRH::_fs.open(path, "r");
184+
MD5Builder calcMD5;
185+
calcMD5.begin();
186+
calcMD5.addStream(f, f.size());
187+
calcMD5.calculate();
188+
calcMD5.getBytes(_ETag_md5);
189+
f.close();
190+
}
191+
192+
bool canHandle(HTTPMethod requestMethod, const String& requestUri) override {
193+
return SRH::validMethod(requestMethod) && requestUri == SRH::_uri;
194+
}
195+
196+
bool handle(WebServerType& server, HTTPMethod requestMethod, const String & requestUri) override {
197+
if (!canHandle(requestMethod, requestUri))
198+
return false;
199+
200+
const String etag = "\"" + base64::encode(_ETag_md5, 16, false) + "\"";
201+
202+
if(server.header("If-None-Match") == etag){
203+
server.send(304);
204+
return true;
205+
}
206+
207+
File f = SRH::_fs.open(SRH::_path, "r");
208+
209+
if (!f)
210+
return false;
211+
212+
if (!f.isFile()) {
213+
f.close();
214+
return false;
215+
}
216+
217+
if (SRH::_cache_header.length() != 0)
218+
server.sendHeader("Cache-Control", SRH::_cache_header);
219+
220+
server.sendHeader("ETag", etag);
221+
222+
server.streamFile(f, mime::getContentType(SRH::_path), requestMethod);
223+
return true;
157224
}
158225

159226
protected:
160-
FS _fs;
161-
String _uri;
162-
String _path;
163-
String _cache_header;
164-
bool _isFile;
165-
size_t _baseUriLength;
227+
uint8_t _ETag_md5[16];
166228
};
167229

168230
} // namespace
169231

170-
#endif //REQUESTHANDLERSIMPL_H
232+
#endif //REQUESTHANDLERSIMPL_H

0 commit comments

Comments
 (0)