Merge pull request #3639 from WoodyLetsCode/caching

Improve ETag Caching
This commit is contained in:
Blaž Kristan 2024-01-04 19:16:56 +01:00 committed by GitHub
commit 6ad4493a91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 92 additions and 104 deletions

View File

@ -182,7 +182,7 @@ ${result}
const result = hexdump(buf); const result = hexdump(buf);
const chunk = ` const chunk = `
// Autogenerated from ${srcDir}/${s.file}, do not edit!! // Autogenerated from ${srcDir}/${s.file}, do not edit!!
const uint16_t ${s.name}_length = ${result.length}; const uint16_t ${s.name}_length = ${buf.length};
const uint8_t ${s.name}[] PROGMEM = { const uint8_t ${s.name}[] PROGMEM = {
${result} ${result}
}; };

View File

@ -15,8 +15,9 @@
* Integrated HTTP web server page declarations * Integrated HTTP web server page declarations
*/ */
bool handleIfNoneMatchCacheHeader(AsyncWebServerRequest* request); bool handleIfNoneMatchCacheHeader(AsyncWebServerRequest* request, int code, uint16_t eTagSuffix = 0);
void setStaticContentCacheHeaders(AsyncWebServerResponse *response); void setStaticContentCacheHeaders(AsyncWebServerResponse *response, int code, uint16_t eTagSuffix = 0);
void handleStaticContent(AsyncWebServerRequest *request, const String &path, int code, const String &contentType, const uint8_t *content, size_t len, bool gzip = true, uint16_t eTagSuffix = 0);
// define flash strings once (saves flash memory) // define flash strings once (saves flash memory)
static const char s_redirecting[] PROGMEM = "Redirecting..."; static const char s_redirecting[] PROGMEM = "Redirecting...";
@ -115,20 +116,12 @@ void initServer()
#ifdef WLED_ENABLE_WEBSOCKETS #ifdef WLED_ENABLE_WEBSOCKETS
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
server.on("/liveview2D", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/liveview2D", HTTP_GET, [](AsyncWebServerRequest *request) {
if (handleIfNoneMatchCacheHeader(request)) return; handleStaticContent(request, "", 200, "text/html", PAGE_liveviewws2D, PAGE_liveviewws2D_length);
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", PAGE_liveviewws2D, PAGE_liveviewws2D_length);
response->addHeader(FPSTR(s_content_enc),"gzip");
setStaticContentCacheHeaders(response);
request->send(response);
}); });
#endif #endif
#endif #endif
server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request) {
if (handleIfNoneMatchCacheHeader(request)) return; handleStaticContent(request, "", 200, "text/html", PAGE_liveview, PAGE_liveview_length);
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", PAGE_liveview, PAGE_liveview_length);
response->addHeader(FPSTR(s_content_enc),"gzip");
setStaticContentCacheHeaders(response);
request->send(response);
}); });
//settings page //settings page
@ -139,16 +132,11 @@ void initServer()
// "/settings/settings.js&p=x" request also handled by serveSettings() // "/settings/settings.js&p=x" request also handled by serveSettings()
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request) {
if (handleIfNoneMatchCacheHeader(request)) return; handleStaticContent(request, "/style.css", 200, "text/css", PAGE_settingsCss, PAGE_settingsCss_length);
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/css", PAGE_settingsCss, PAGE_settingsCss_length);
response->addHeader(FPSTR(s_content_enc),"gzip");
setStaticContentCacheHeaders(response);
request->send(response);
}); });
server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request) {
if (handleFileRead(request, "/favicon.ico")) return; handleStaticContent(request, "/favicon.ico", 200, "image/x-icon", favicon, favicon_length, false);
request->send_P(200, "image/x-icon", favicon, 156);
}); });
server.on("/skin.css", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/skin.css", HTTP_GET, [](AsyncWebServerRequest *request) {
@ -237,11 +225,7 @@ void initServer()
#ifdef WLED_ENABLE_USERMOD_PAGE #ifdef WLED_ENABLE_USERMOD_PAGE
server.on("/u", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/u", HTTP_GET, [](AsyncWebServerRequest *request) {
if (handleIfNoneMatchCacheHeader(request)) return; handleStaticContent(request, "", 200, "text/html", PAGE_usermod, PAGE_usermod_length);
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", PAGE_usermod, PAGE_usermod_length);
response->addHeader(FPSTR(s_content_enc),"gzip");
setStaticContentCacheHeaders(response);
request->send(response);
}); });
#endif #endif
@ -328,7 +312,7 @@ void initServer()
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
if (captivePortal(request)) return; if (captivePortal(request)) return;
if (!showWelcomePage || request->hasArg(F("sliders"))) { if (!showWelcomePage || request->hasArg(F("sliders"))) {
serveIndex(request); handleStaticContent(request, "/index.htm", 200, "text/html", PAGE_index, PAGE_index_L);
} else { } else {
serveSettings(request); serveSettings(request);
} }
@ -336,33 +320,18 @@ void initServer()
#ifdef WLED_ENABLE_PIXART #ifdef WLED_ENABLE_PIXART
server.on("/pixart.htm", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/pixart.htm", HTTP_GET, [](AsyncWebServerRequest *request) {
if (handleFileRead(request, "/pixart.htm")) return; handleStaticContent(request, "/pixart.htm", 200, "text/html", PAGE_pixart, PAGE_pixart_L);
if (handleIfNoneMatchCacheHeader(request)) return;
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", PAGE_pixart, PAGE_pixart_L);
response->addHeader(FPSTR(s_content_enc),"gzip");
setStaticContentCacheHeaders(response);
request->send(response);
}); });
#endif #endif
#ifndef WLED_DISABLE_PXMAGIC #ifndef WLED_DISABLE_PXMAGIC
server.on("/pxmagic.htm", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/pxmagic.htm", HTTP_GET, [](AsyncWebServerRequest *request) {
if (handleFileRead(request, "/pxmagic.htm")) return; handleStaticContent(request, "/pxmagic.htm", 200, "text/html", PAGE_pxmagic, PAGE_pxmagic_L);
if (handleIfNoneMatchCacheHeader(request)) return;
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", PAGE_pxmagic, PAGE_pxmagic_L);
response->addHeader(FPSTR(s_content_enc),"gzip");
setStaticContentCacheHeaders(response);
request->send(response);
}); });
#endif #endif
server.on("/cpal.htm", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/cpal.htm", HTTP_GET, [](AsyncWebServerRequest *request) {
if (handleFileRead(request, "/cpal.htm")) return; handleStaticContent(request, "/cpal.htm", 200, "text/html", PAGE_cpal, PAGE_cpal_L);
if (handleIfNoneMatchCacheHeader(request)) return;
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", PAGE_cpal, PAGE_cpal_L);
response->addHeader(FPSTR(s_content_enc),"gzip");
setStaticContentCacheHeaders(response);
request->send(response);
}); });
#ifdef WLED_ENABLE_WEBSOCKETS #ifdef WLED_ENABLE_WEBSOCKETS
@ -388,26 +357,34 @@ void initServer()
#ifndef WLED_DISABLE_ALEXA #ifndef WLED_DISABLE_ALEXA
if(espalexa.handleAlexaApiCall(request)) return; if(espalexa.handleAlexaApiCall(request)) return;
#endif #endif
if(handleFileRead(request, request->url())) return; handleStaticContent(request, request->url(), 404, "text/html", PAGE_404, PAGE_404_length);
AsyncWebServerResponse *response = request->beginResponse_P(404, "text/html", PAGE_404, PAGE_404_length);
response->addHeader(FPSTR(s_content_enc),"gzip");
setStaticContentCacheHeaders(response);
request->send(response);
}); });
} }
bool handleIfNoneMatchCacheHeader(AsyncWebServerRequest *request) { void generateEtag(char *etag, uint16_t eTagSuffix) {
sprintf_P(etag, PSTR("%7d-%02x-%04x"), VERSION, cacheInvalidate, eTagSuffix);
}
bool handleIfNoneMatchCacheHeader(AsyncWebServerRequest *request, int code, uint16_t eTagSuffix) {
// Only send 304 (Not Modified) if response code is 200 (OK)
if (code != 200) return false;
AsyncWebHeader *header = request->getHeader("If-None-Match"); AsyncWebHeader *header = request->getHeader("If-None-Match");
char etag[11]; char etag[14];
sprintf_P(etag, PSTR("%7d-%02x"), VERSION, cacheInvalidate); generateEtag(etag, eTagSuffix);
if (header && header->value() == etag) { if (header && header->value() == etag) {
request->send(304); AsyncWebServerResponse *response = request->beginResponse(304);
setStaticContentCacheHeaders(response, code, eTagSuffix);
request->send(response);
return true; return true;
} }
return false; return false;
} }
void setStaticContentCacheHeaders(AsyncWebServerResponse *response) { void setStaticContentCacheHeaders(AsyncWebServerResponse *response, int code, uint16_t eTagSuffix) {
// Only send ETag for 200 (OK) responses
if (code != 200) return;
// https://medium.com/@codebyamir/a-web-developers-guide-to-browser-caching-cc41f3b73e7c // https://medium.com/@codebyamir/a-web-developers-guide-to-browser-caching-cc41f3b73e7c
#ifndef WLED_DEBUG #ifndef WLED_DEBUG
// this header name is misleading, "no-cache" will not disable cache, // this header name is misleading, "no-cache" will not disable cache,
@ -416,25 +393,37 @@ void setStaticContentCacheHeaders(AsyncWebServerResponse *response) {
#else #else
response->addHeader(F("Cache-Control"), "no-store,max-age=0"); // prevent caching if debug build response->addHeader(F("Cache-Control"), "no-store,max-age=0"); // prevent caching if debug build
#endif #endif
char etag[11]; char etag[14];
sprintf_P(etag, PSTR("%7d-%02x"), VERSION, cacheInvalidate); generateEtag(etag, eTagSuffix);
response->addHeader(F("ETag"), etag); response->addHeader(F("ETag"), etag);
} }
void serveIndex(AsyncWebServerRequest* request) /**
{ * Handels the request for a static file.
if (handleFileRead(request, "/index.htm")) return; * If the file was found in the filesystem, it will be sent to the client.
* Otherwise it will be checked if the browser cached the file and if so, a 304 response will be sent.
if (handleIfNoneMatchCacheHeader(request)) return; * If the file was not found in the filesystem and not in the browser cache, the request will be handled as a 200 response with the content of the page.
*
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", PAGE_index, PAGE_index_L); * @param request The request object
* @param path If a file with this path exists in the filesystem, it will be sent to the client. Set to "" to skip this check.
response->addHeader(FPSTR(s_content_enc),"gzip"); * @param code The HTTP status code
setStaticContentCacheHeaders(response); * @param contentType The content type of the web page
* @param content Content of the web page
* @param len Length of the content
* @param gzip Optional. Defaults to true. If false, the gzip header will not be added.
* @param eTagSuffix Optional. Defaults to 0. A suffix that will be added to the ETag header. This can be used to invalidate the cache for a specific page.
*/
void handleStaticContent(AsyncWebServerRequest *request, const String &path, int code, const String &contentType, const uint8_t *content, size_t len, bool gzip, uint16_t eTagSuffix) {
if (path != "" && handleFileRead(request, path)) return;
if (handleIfNoneMatchCacheHeader(request, code, eTagSuffix)) return;
AsyncWebServerResponse *response = request->beginResponse_P(code, contentType, content, len);
if (gzip) response->addHeader(FPSTR(s_content_enc), "gzip");
setStaticContentCacheHeaders(response, code, eTagSuffix);
request->send(response); request->send(response);
} }
String msgProcessor(const String& var) String msgProcessor(const String& var)
{ {
if (var == "MSG") { if (var == "MSG") {
@ -536,13 +525,11 @@ void serveSettingsJS(AsyncWebServerRequest* request)
} }
void serveSettings(AsyncWebServerRequest* request, bool post) void serveSettings(AsyncWebServerRequest* request, bool post) {
{
byte subPage = 0, originalSubPage = 0; byte subPage = 0, originalSubPage = 0;
const String& url = request->url(); const String& url = request->url();
if (url.indexOf("sett") >= 0) if (url.indexOf("sett") >= 0) {
{
if (url.indexOf(".js") > 0) subPage = SUBPAGE_JS; if (url.indexOf(".js") > 0) subPage = SUBPAGE_JS;
else if (url.indexOf(".css") > 0) subPage = SUBPAGE_CSS; else if (url.indexOf(".css") > 0) subPage = SUBPAGE_CSS;
else if (url.indexOf("wifi") > 0) subPage = SUBPAGE_WIFI; else if (url.indexOf("wifi") > 0) subPage = SUBPAGE_WIFI;
@ -603,22 +590,25 @@ void serveSettings(AsyncWebServerRequest* request, bool post)
} }
} }
AsyncWebServerResponse *response; int code = 200;
switch (subPage) String contentType = "text/html";
{ const uint8_t* content;
case SUBPAGE_WIFI : response = request->beginResponse_P(200, "text/html", PAGE_settings_wifi, PAGE_settings_wifi_length); break; size_t len;
case SUBPAGE_LEDS : response = request->beginResponse_P(200, "text/html", PAGE_settings_leds, PAGE_settings_leds_length); break;
case SUBPAGE_UI : response = request->beginResponse_P(200, "text/html", PAGE_settings_ui, PAGE_settings_ui_length); break; switch (subPage) {
case SUBPAGE_SYNC : response = request->beginResponse_P(200, "text/html", PAGE_settings_sync, PAGE_settings_sync_length); break; case SUBPAGE_WIFI : content = PAGE_settings_wifi; len = PAGE_settings_wifi_length; break;
case SUBPAGE_TIME : response = request->beginResponse_P(200, "text/html", PAGE_settings_time, PAGE_settings_time_length); break; case SUBPAGE_LEDS : content = PAGE_settings_leds; len = PAGE_settings_leds_length; break;
case SUBPAGE_SEC : response = request->beginResponse_P(200, "text/html", PAGE_settings_sec, PAGE_settings_sec_length); break; case SUBPAGE_UI : content = PAGE_settings_ui; len = PAGE_settings_ui_length; break;
case SUBPAGE_SYNC : content = PAGE_settings_sync; len = PAGE_settings_sync_length; break;
case SUBPAGE_TIME : content = PAGE_settings_time; len = PAGE_settings_time_length; break;
case SUBPAGE_SEC : content = PAGE_settings_sec; len = PAGE_settings_sec_length; break;
#ifdef WLED_ENABLE_DMX #ifdef WLED_ENABLE_DMX
case SUBPAGE_DMX : response = request->beginResponse_P(200, "text/html", PAGE_settings_dmx, PAGE_settings_dmx_length); break; case SUBPAGE_DMX : content = PAGE_settings_dmx; len = PAGE_settings_dmx_length; break;
#endif #endif
case SUBPAGE_UM : response = request->beginResponse_P(200, "text/html", PAGE_settings_um, PAGE_settings_um_length); break; case SUBPAGE_UM : content = PAGE_settings_um; len = PAGE_settings_um_length; break;
case SUBPAGE_UPDATE : response = request->beginResponse_P(200, "text/html", PAGE_update, PAGE_update_length); break; case SUBPAGE_UPDATE : content = PAGE_update; len = PAGE_update_length; break;
#ifndef WLED_DISABLE_2D #ifndef WLED_DISABLE_2D
case SUBPAGE_2D : response = request->beginResponse_P(200, "text/html", PAGE_settings_2D, PAGE_settings_2D_length); break; case SUBPAGE_2D : content = PAGE_settings_2D; len = PAGE_settings_2D_length; break;
#endif #endif
case SUBPAGE_LOCK : { case SUBPAGE_LOCK : {
correctPIN = !strlen(settingsPIN); // lock if a pin is set correctPIN = !strlen(settingsPIN); // lock if a pin is set
@ -626,13 +616,11 @@ void serveSettings(AsyncWebServerRequest* request, bool post)
serveMessage(request, 200, strlen(settingsPIN) > 0 ? PSTR("Settings locked") : PSTR("No PIN set"), FPSTR(s_redirecting), 1); serveMessage(request, 200, strlen(settingsPIN) > 0 ? PSTR("Settings locked") : PSTR("No PIN set"), FPSTR(s_redirecting), 1);
return; return;
} }
case SUBPAGE_PINREQ : response = request->beginResponse_P(401, "text/html", PAGE_settings_pin, PAGE_settings_pin_length); break; case SUBPAGE_PINREQ : content = PAGE_settings_pin; len = PAGE_settings_pin_length; code = 401; break;
case SUBPAGE_CSS : response = request->beginResponse_P(200, "text/css", PAGE_settingsCss, PAGE_settingsCss_length); break; case SUBPAGE_CSS : content = PAGE_settingsCss; len = PAGE_settingsCss_length; contentType = "text/css"; break;
case SUBPAGE_JS : serveSettingsJS(request); return; case SUBPAGE_JS : serveSettingsJS(request); return;
case SUBPAGE_WELCOME : response = request->beginResponse_P(200, "text/html", PAGE_welcome, PAGE_welcome_length); break; case SUBPAGE_WELCOME : content = PAGE_welcome; len = PAGE_welcome_length; break;
default: response = request->beginResponse_P(200, "text/html", PAGE_settings, PAGE_settings_length); break; default: content = PAGE_settings; len = PAGE_settings_length; break;
} }
response->addHeader(FPSTR(s_content_enc),"gzip"); handleStaticContent(request, "", code, contentType, content, len);
setStaticContentCacheHeaders(response);
request->send(response);
} }