mirror of
https://github.com/esp8266/Arduino.git
synced 2025-06-03 07:02:28 +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 WWW_Authenticate[] PROGMEM = "WWW-Authenticate";
|
||||
static const char Content_Length[] PROGMEM = "Content-Length";
|
||||
static const char ETAG_HEADER[] PROGMEM = "If-None-Match";
|
||||
|
||||
namespace esp8266webserver {
|
||||
|
||||
@ -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>
|
||||
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -308,5 +308,4 @@ protected:
|
||||
using ESP8266WebServer = esp8266webserver::ESP8266WebServerTemplate<WiFiServer>;
|
||||
using RequestHandler = esp8266webserver::RequestHandler<WiFiServer>;
|
||||
|
||||
|
||||
#endif //ESP8266WEBSERVER_H
|
||||
#endif //ESP8266WEBSERVER_H
|
@ -72,83 +72,11 @@ public:
|
||||
, _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 isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header == __null ? "" : cache_header);
|
||||
_baseUriLength = _uri.length();
|
||||
DEBUGV("StaticRequestHandler: path=%s uri=%s, cache_header=%s\r\n", path, uri, cache_header == __null ? "" : cache_header);
|
||||
}
|
||||
|
||||
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;
|
||||
bool validMethod(HTTPMethod requestMethod){
|
||||
return (requestMethod == HTTP_GET) || (requestMethod == HTTP_HEAD);
|
||||
}
|
||||
|
||||
/* Deprecated version. Please use mime::getContentType instead */
|
||||
@ -161,10 +89,144 @@ protected:
|
||||
String _uri;
|
||||
String _path;
|
||||
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;
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
#endif //REQUESTHANDLERSIMPL_H
|
||||
#endif //REQUESTHANDLERSIMPL_H
|
Loading…
x
Reference in New Issue
Block a user