Use http header ETag caching for all static content.
Using the md5sum as ETag http header value should enable caching on all static http content.
This commit is contained in:
parent
21ec72f4c0
commit
e752c433af
@ -10,4 +10,5 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
AsyncWebServer* _server;
|
AsyncWebServer* _server;
|
||||||
|
void responseBinaryDataWithETagCache(AsyncWebServerRequest* request, const String &contentType, const String &contentEncoding, const uint8_t *content, size_t len);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
* Copyright (C) 2022-2024 Thomas Basler and others
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "WebApi_webapp.h"
|
#include "WebApi_webapp.h"
|
||||||
|
#include <MD5Builder.h>
|
||||||
|
|
||||||
extern const uint8_t file_index_html_start[] asm("_binary_webapp_dist_index_html_gz_start");
|
extern const uint8_t file_index_html_start[] asm("_binary_webapp_dist_index_html_gz_start");
|
||||||
extern const uint8_t file_favicon_ico_start[] asm("_binary_webapp_dist_favicon_ico_start");
|
extern const uint8_t file_favicon_ico_start[] asm("_binary_webapp_dist_favicon_ico_start");
|
||||||
@ -18,79 +19,80 @@ extern const uint8_t file_zones_json_end[] asm("_binary_webapp_dist_zones_json_g
|
|||||||
extern const uint8_t file_app_js_end[] asm("_binary_webapp_dist_js_app_js_gz_end");
|
extern const uint8_t file_app_js_end[] asm("_binary_webapp_dist_js_app_js_gz_end");
|
||||||
extern const uint8_t file_site_webmanifest_end[] asm("_binary_webapp_dist_site_webmanifest_end");
|
extern const uint8_t file_site_webmanifest_end[] asm("_binary_webapp_dist_site_webmanifest_end");
|
||||||
|
|
||||||
#ifdef AUTO_GIT_HASH
|
void WebApiWebappClass::responseBinaryDataWithETagCache(AsyncWebServerRequest *request, const String &contentType, const String &contentEncoding, const uint8_t *content, size_t len)
|
||||||
#define ETAG_HTTP_HEADER_VAL "\"" AUTO_GIT_HASH "\"" // ETag value must be between quotes
|
{
|
||||||
#endif
|
auto _md5 = MD5Builder();
|
||||||
|
_md5.begin();
|
||||||
|
_md5.add(const_cast<uint8_t *>(content), len);
|
||||||
|
_md5.calculate();
|
||||||
|
|
||||||
|
String expectedEtag;
|
||||||
|
expectedEtag = "\"";
|
||||||
|
expectedEtag += _md5.toString();
|
||||||
|
expectedEtag += "\"";
|
||||||
|
|
||||||
|
bool eTagMatch = false;
|
||||||
|
if (request->hasHeader("If-None-Match")) {
|
||||||
|
const AsyncWebHeader* h = request->getHeader("If-None-Match");
|
||||||
|
eTagMatch = h->value().equals(expectedEtag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// begin response 200 or 304
|
||||||
|
AsyncWebServerResponse* response;
|
||||||
|
if (eTagMatch) {
|
||||||
|
response = request->beginResponse(304);
|
||||||
|
} else {
|
||||||
|
response = request->beginResponse_P(200, contentType, content, len);
|
||||||
|
if (contentEncoding.length() > 0) {
|
||||||
|
response->addHeader("Content-Encoding", contentEncoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP requires cache headers in 200 and 304 to be identical
|
||||||
|
response->addHeader("Cache-Control", "public, must-revalidate");
|
||||||
|
response->addHeader("ETag", expectedEtag);
|
||||||
|
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
void WebApiWebappClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
void WebApiWebappClass::init(AsyncWebServer& server, Scheduler& scheduler)
|
||||||
{
|
{
|
||||||
_server = &server;
|
_server = &server;
|
||||||
|
|
||||||
_server->on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
|
/*
|
||||||
AsyncWebServerResponse* response = request->beginResponse_P(200, "text/html", file_index_html_start, file_index_html_end - file_index_html_start);
|
We don't validate the request header "Accept-Encoding" if gzip compression is supported!
|
||||||
response->addHeader("Content-Encoding", "gzip");
|
We just have the gzipped data available - so we ship them!
|
||||||
request->send(response);
|
*/
|
||||||
|
|
||||||
|
_server->on("/", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
||||||
|
responseBinaryDataWithETagCache(request, "text/html", "gzip", file_index_html_start, file_index_html_end - file_index_html_start);
|
||||||
});
|
});
|
||||||
|
|
||||||
_server->onNotFound([](AsyncWebServerRequest* request) {
|
_server->onNotFound([&](AsyncWebServerRequest* request) {
|
||||||
AsyncWebServerResponse* response = request->beginResponse_P(200, "text/html", file_index_html_start, file_index_html_end - file_index_html_start);
|
responseBinaryDataWithETagCache(request, "text/html", "gzip", file_index_html_start, file_index_html_end - file_index_html_start);
|
||||||
response->addHeader("Content-Encoding", "gzip");
|
|
||||||
request->send(response);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_server->on("/index.html", HTTP_GET, [](AsyncWebServerRequest* request) {
|
_server->on("/index.html", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
||||||
AsyncWebServerResponse* response = request->beginResponse_P(200, "text/html", file_index_html_start, file_index_html_end - file_index_html_start);
|
responseBinaryDataWithETagCache(request, "text/html", "gzip", file_index_html_start, file_index_html_end - file_index_html_start);
|
||||||
response->addHeader("Content-Encoding", "gzip");
|
|
||||||
request->send(response);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_server->on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest* request) {
|
_server->on("/favicon.ico", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
||||||
AsyncWebServerResponse* response = request->beginResponse_P(200, "image/x-icon", file_favicon_ico_start, file_favicon_ico_end - file_favicon_ico_start);
|
responseBinaryDataWithETagCache(request, "image/x-icon", "", file_favicon_ico_start, file_favicon_ico_end - file_favicon_ico_start);
|
||||||
request->send(response);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_server->on("/favicon.png", HTTP_GET, [](AsyncWebServerRequest* request) {
|
_server->on("/favicon.png", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
||||||
AsyncWebServerResponse* response = request->beginResponse_P(200, "image/png", file_favicon_png_start, file_favicon_png_end - file_favicon_png_start);
|
responseBinaryDataWithETagCache(request, "image/png", "", file_favicon_png_start, file_favicon_png_end - file_favicon_png_start);
|
||||||
request->send(response);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_server->on("/zones.json", HTTP_GET, [](AsyncWebServerRequest* request) {
|
_server->on("/zones.json", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
||||||
AsyncWebServerResponse* response = request->beginResponse_P(200, "application/json", file_zones_json_start, file_zones_json_end - file_zones_json_start);
|
responseBinaryDataWithETagCache(request, "application/json", "gzip", file_zones_json_start, file_zones_json_end - file_zones_json_start);
|
||||||
response->addHeader("Content-Encoding", "gzip");
|
|
||||||
request->send(response);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_server->on("/site.webmanifest", HTTP_GET, [](AsyncWebServerRequest* request) {
|
_server->on("/site.webmanifest", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
||||||
AsyncWebServerResponse* response = request->beginResponse_P(200, "application/json", file_site_webmanifest_start, file_site_webmanifest_end - file_site_webmanifest_start);
|
responseBinaryDataWithETagCache(request, "application/json", "", file_site_webmanifest_start, file_site_webmanifest_end - file_site_webmanifest_start);
|
||||||
request->send(response);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
_server->on("/js/app.js", HTTP_GET, [](AsyncWebServerRequest* request) {
|
_server->on("/js/app.js", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
||||||
#ifdef ETAG_HTTP_HEADER_VAL
|
responseBinaryDataWithETagCache(request, "text/javascript", "gzip", file_app_js_start, file_app_js_end - file_app_js_start);
|
||||||
// check client If-None-Match header vs ETag/AUTO_GIT_HASH
|
|
||||||
bool eTagMatch = false;
|
|
||||||
if (request->hasHeader("If-None-Match")) {
|
|
||||||
const AsyncWebHeader* h = request->getHeader("If-None-Match");
|
|
||||||
if (strncmp(ETAG_HTTP_HEADER_VAL, h->value().c_str(), strlen(ETAG_HTTP_HEADER_VAL)) == 0) {
|
|
||||||
eTagMatch = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// begin response 200 or 304
|
|
||||||
AsyncWebServerResponse* response;
|
|
||||||
if (eTagMatch) {
|
|
||||||
response = request->beginResponse(304);
|
|
||||||
} else {
|
|
||||||
response = request->beginResponse_P(200, "text/javascript", file_app_js_start, file_app_js_end - file_app_js_start);
|
|
||||||
response->addHeader("Content-Encoding", "gzip");
|
|
||||||
}
|
|
||||||
// HTTP requires cache headers in 200 and 304 to be identical
|
|
||||||
response->addHeader("Cache-Control", "public, must-revalidate");
|
|
||||||
response->addHeader("ETag", ETAG_HTTP_HEADER_VAL);
|
|
||||||
#else
|
|
||||||
AsyncWebServerResponse* response = request->beginResponse_P(200, "text/javascript", file_app_js_start, file_app_js_end - file_app_js_start);
|
|
||||||
response->addHeader("Content-Encoding", "gzip");
|
|
||||||
#endif
|
|
||||||
request->send(response);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user