mirror of
https://github.com/esp8266/Arduino.git
synced 2025-06-09 03:41:41 +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:
parent
2e4563c76b
commit
7a368747e0
@ -34,6 +34,7 @@ static const char qop_auth[] PROGMEM = "qop=auth";
|
|||||||
static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\"";
|
static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\"";
|
||||||
static const char WWW_Authenticate[] PROGMEM = "WWW-Authenticate";
|
static const char WWW_Authenticate[] PROGMEM = "WWW-Authenticate";
|
||||||
static const char Content_Length[] PROGMEM = "Content-Length";
|
static const char Content_Length[] PROGMEM = "Content-Length";
|
||||||
|
static const char ETAG_HEADER[] PROGMEM = "If-None-Match";
|
||||||
|
|
||||||
namespace esp8266webserver {
|
namespace esp8266webserver {
|
||||||
|
|
||||||
@ -254,7 +255,18 @@ void ESP8266WebServerTemplate<ServerType>::_addRequestHandler(RequestHandlerType
|
|||||||
|
|
||||||
template <typename ServerType>
|
template <typename ServerType>
|
||||||
void ESP8266WebServerTemplate<ServerType>::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
|
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>
|
template <typename ServerType>
|
||||||
@ -606,15 +618,18 @@ const String& ESP8266WebServerTemplate<ServerType>::header(const String& name) c
|
|||||||
return emptyString;
|
return emptyString;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ServerType>
|
|
||||||
|
template<typename ServerType>
|
||||||
void ESP8266WebServerTemplate<ServerType>::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
|
void ESP8266WebServerTemplate<ServerType>::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
|
||||||
_headerKeysCount = headerKeysCount + 1;
|
_headerKeysCount = headerKeysCount + 2;
|
||||||
if (_currentHeaders)
|
if (_currentHeaders){
|
||||||
delete[]_currentHeaders;
|
delete[] _currentHeaders;
|
||||||
|
}
|
||||||
_currentHeaders = new RequestArgument[_headerKeysCount];
|
_currentHeaders = new RequestArgument[_headerKeysCount];
|
||||||
_currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER);
|
_currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER);
|
||||||
for (int i = 1; i < _headerKeysCount; i++){
|
_currentHeaders[1].key = FPSTR(ETAG_HEADER);
|
||||||
_currentHeaders[i].key = headerKeys[i-1];
|
for (int i = 2; i < _headerKeysCount; i++){
|
||||||
|
_currentHeaders[i].key = headerKeys[i-2];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,5 +308,4 @@ protected:
|
|||||||
using ESP8266WebServer = esp8266webserver::ESP8266WebServerTemplate<WiFiServer>;
|
using ESP8266WebServer = esp8266webserver::ESP8266WebServerTemplate<WiFiServer>;
|
||||||
using RequestHandler = esp8266webserver::RequestHandler<WiFiServer>;
|
using RequestHandler = esp8266webserver::RequestHandler<WiFiServer>;
|
||||||
|
|
||||||
|
|
||||||
#endif //ESP8266WEBSERVER_H
|
#endif //ESP8266WEBSERVER_H
|
@ -72,83 +72,11 @@ public:
|
|||||||
, _path(path)
|
, _path(path)
|
||||||
, _cache_header(cache_header)
|
, _cache_header(cache_header)
|
||||||
{
|
{
|
||||||
if (fs.exists(path)) {
|
DEBUGV("StaticRequestHandler: path=%s uri=%s, cache_header=%s\r\n", path, uri, cache_header == __null ? "" : cache_header);
|
||||||
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);
|
bool validMethod(HTTPMethod requestMethod){
|
||||||
_baseUriLength = _uri.length();
|
return (requestMethod == HTTP_GET) || (requestMethod == HTTP_HEAD);
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Deprecated version. Please use mime::getContentType instead */
|
/* Deprecated version. Please use mime::getContentType instead */
|
||||||
@ -161,10 +89,144 @@ protected:
|
|||||||
String _uri;
|
String _uri;
|
||||||
String _path;
|
String _path;
|
||||||
String _cache_header;
|
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;
|
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
|
} // namespace
|
||||||
|
|
||||||
#endif //REQUESTHANDLERSIMPL_H
|
#endif //REQUESTHANDLERSIMPL_H
|
Loading…
x
Reference in New Issue
Block a user