Web server string optimisation

(saves 140B of RAM on ESP8266)
ETag bugfix
This commit is contained in:
Blaz Kristan 2024-02-08 19:32:23 +01:00
parent 6dcd9596a2
commit f6206d4c30

View File

@ -24,6 +24,13 @@ static const char s_redirecting[] PROGMEM = "Redirecting...";
static const char s_content_enc[] PROGMEM = "Content-Encoding";
static const char s_unlock_ota [] PROGMEM = "Please unlock OTA in security settings!";
static const char s_unlock_cfg [] PROGMEM = "Please unlock settings using PIN code!";
static const char s_notimplemented[] PROGMEM = "Not implemented";
static const char s_accessdenied[] PROGMEM = "Access Denied";
static const char s_javascript[] PROGMEM = "application/javascript";
static const char s_json[] PROGMEM = "application/json";
static const char s_html[] PROGMEM = "text/html";
static const char s_plain[] PROGMEM = "text/plain";
static const char s_css[] PROGMEM = "text/css";
//Is this an IP?
bool isIp(String str) {
@ -38,7 +45,7 @@ bool isIp(String str) {
void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {
if (!correctPIN) {
if (final) request->send(401, "text/plain", FPSTR(s_unlock_cfg));
if (final) request->send(401, FPSTR(s_plain), FPSTR(s_unlock_cfg));
return;
}
if (!index) {
@ -50,7 +57,7 @@ void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t
request->_tempFile = WLED_FS.open(finalname, "w");
DEBUG_PRINT(F("Uploading "));
DEBUG_PRINTLN(finalname);
if (finalname.equals("/presets.json")) presetsModifiedTime = toki.second();
if (finalname.equals(F("/presets.json"))) presetsModifiedTime = toki.second();
}
if (len) {
request->_tempFile.write(data,len);
@ -59,10 +66,10 @@ void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t
request->_tempFile.close();
if (filename.indexOf(F("cfg.json")) >= 0) { // check for filename with or without slash
doReboot = true;
request->send(200, "text/plain", F("Configuration restore successful.\nRebooting..."));
request->send(200, FPSTR(s_plain), F("Configuration restore successful.\nRebooting..."));
} else {
if (filename.indexOf(F("palette")) >= 0 && filename.indexOf(F(".json")) >= 0) strip.loadCustomPalettes();
request->send(200, "text/plain", F("File Uploaded!"));
request->send(200, FPSTR(s_plain), F("File Uploaded!"));
}
cacheInvalidate++;
}
@ -79,25 +86,24 @@ void createEditHandler(bool enable) {
#endif
#else
editHandler = &server.on("/edit", HTTP_GET, [](AsyncWebServerRequest *request){
serveMessage(request, 501, "Not implemented", F("The FS editor is disabled in this build."), 254);
serveMessage(request, 501, FPSTR(s_notimplemented), F("The FS editor is disabled in this build."), 254);
});
#endif
} else {
editHandler = &server.on("/edit", HTTP_ANY, [](AsyncWebServerRequest *request){
serveMessage(request, 401, "Access Denied", FPSTR(s_unlock_cfg), 254);
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254);
});
}
}
bool captivePortal(AsyncWebServerRequest *request)
{
if (ON_STA_FILTER(request)) return false; //only serve captive in AP mode
String hostH;
if (!apActive) return false; //only serve captive in AP mode
if (!request->hasHeader("Host")) return false;
hostH = request->getHeader("Host")->value();
if (!isIp(hostH) && hostH.indexOf("wled.me") < 0 && hostH.indexOf(cmDNS) < 0) {
DEBUG_PRINTLN("Captive portal");
String hostH = request->getHeader("Host")->value();
if (!isIp(hostH) && hostH.indexOf(F("wled.me")) < 0 && hostH.indexOf(cmDNS) < 0 && hostH.indexOf(':') < 0) {
DEBUG_PRINTLN(F("Captive portal"));
AsyncWebServerResponse *response = request->beginResponse(302);
response->addHeader(F("Location"), F("http://4.3.2.1"));
request->send(response);
@ -116,12 +122,12 @@ void initServer()
#ifdef WLED_ENABLE_WEBSOCKETS
#ifndef WLED_DISABLE_2D
server.on("/liveview2D", HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, "", 200, "text/html", PAGE_liveviewws2D, PAGE_liveviewws2D_length);
handleStaticContent(request, "", 200, FPSTR(s_html), PAGE_liveviewws2D, PAGE_liveviewws2D_length);
});
#endif
#endif
server.on("/liveview", HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, "", 200, "text/html", PAGE_liveview, PAGE_liveview_length);
handleStaticContent(request, "", 200, FPSTR(s_html), PAGE_liveview, PAGE_liveview_length);
});
//settings page
@ -132,7 +138,7 @@ void initServer()
// "/settings/settings.js&p=x" request also handled by serveSettings()
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, "/style.css", 200, "text/css", PAGE_settingsCss, PAGE_settingsCss_length);
handleStaticContent(request, "/style.css", 200, FPSTR(s_css), PAGE_settingsCss, PAGE_settingsCss_length);
});
server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request) {
@ -141,7 +147,7 @@ void initServer()
server.on("/skin.css", HTTP_GET, [](AsyncWebServerRequest *request) {
if (handleFileRead(request, "/skin.css")) return;
AsyncWebServerResponse *response = request->beginResponse(200, "text/css");
AsyncWebServerResponse *response = request->beginResponse(200, FPSTR(s_css));
request->send(response);
});
@ -207,25 +213,25 @@ void initServer()
doSerializeConfig = true; //serializeConfig(); //Save new settings to FS
}
}
request->send(200, "application/json", F("{\"success\":true}"));
request->send(200, FPSTR(s_json), F("{\"success\":true}"));
}, JSON_BUFFER_SIZE);
server.addHandler(handler);
server.on("/version", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", (String)VERSION);
request->send(200, FPSTR(s_plain), (String)VERSION);
});
server.on("/uptime", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", (String)millis());
request->send(200, FPSTR(s_plain), (String)millis());
});
server.on("/freeheap", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", (String)ESP.getFreeHeap());
request->send(200, FPSTR(s_plain), (String)ESP.getFreeHeap());
});
#ifdef WLED_ENABLE_USERMOD_PAGE
server.on("/u", HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, "", 200, "text/html", PAGE_usermod, PAGE_usermod_length);
handleStaticContent(request, "", 200, FPSTR(s_html), PAGE_usermod, PAGE_usermod_length);
});
#endif
@ -244,7 +250,7 @@ void initServer()
//init ota page
server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){
if (otaLock) {
serveMessage(request, 401, "Access Denied", FPSTR(s_unlock_ota), 254);
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254);
} else
serveSettings(request); // checks for "upd" in URL and handles PIN
});
@ -255,7 +261,7 @@ void initServer()
return;
}
if (otaLock) {
serveMessage(request, 401, "Access Denied", FPSTR(s_unlock_ota), 254);
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254);
return;
}
if (Update.hasError()) {
@ -296,25 +302,25 @@ void initServer()
});
#else
server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){
serveMessage(request, 501, "Not implemented", F("OTA updating is disabled in this build."), 254);
serveMessage(request, 501, FPSTR(s_notimplemented), F("OTA updating is disabled in this build."), 254);
});
#endif
#ifdef WLED_ENABLE_DMX
#ifdef WLED_ENABLE_DMX
server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", PAGE_dmxmap , dmxProcessor);
request->send_P(200, FPSTR(s_html), PAGE_dmxmap , dmxProcessor);
});
#else
#else
server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){
serveMessage(request, 501, "Not implemented", F("DMX support is not enabled in this build."), 254);
serveMessage(request, 501, FPSTR(s_notimplemented), F("DMX support is not enabled in this build."), 254);
});
#endif
#endif
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
if (captivePortal(request)) return;
if (!showWelcomePage || request->hasArg(F("sliders"))) {
handleStaticContent(request, "/index.htm", 200, "text/html", PAGE_index, PAGE_index_L);
handleStaticContent(request, "/index.htm", 200, FPSTR(s_html), PAGE_index, PAGE_index_L);
} else {
serveSettings(request);
}
@ -322,28 +328,27 @@ void initServer()
#ifdef WLED_ENABLE_PIXART
server.on("/pixart.htm", HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, "/pixart.htm", 200, "text/html", PAGE_pixart, PAGE_pixart_L);
handleStaticContent(request, "/pixart.htm", 200, FPSTR(s_html), PAGE_pixart, PAGE_pixart_L);
});
#endif
#endif
#ifndef WLED_DISABLE_PXMAGIC
server.on("/pxmagic.htm", HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, "/pxmagic.htm", 200, "text/html", PAGE_pxmagic, PAGE_pxmagic_L);
handleStaticContent(request, "/pxmagic.htm", 200, FPSTR(s_html), PAGE_pxmagic, PAGE_pxmagic_L);
});
#endif
server.on("/cpal.htm", HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, "/cpal.htm", 200, "text/html", PAGE_cpal, PAGE_cpal_L);
handleStaticContent(request, "/cpal.htm", 200, FPSTR(s_html), PAGE_cpal, PAGE_cpal_L);
});
#ifdef WLED_ENABLE_WEBSOCKETS
#ifdef WLED_ENABLE_WEBSOCKETS
server.addHandler(&ws);
#endif
#endif
//called when the url is not defined here, ajax-in; get-settings
server.onNotFound([](AsyncWebServerRequest *request){
DEBUG_PRINTLN("Not-Found HTTP call:");
DEBUG_PRINTLN("URI: " + request->url());
DEBUG_PRINT(F("Not-Found HTTP call: ")); DEBUG_PRINTLN(request->url());
if (captivePortal(request)) return;
//make API CORS compatible
@ -359,7 +364,7 @@ void initServer()
#ifndef WLED_DISABLE_ALEXA
if(espalexa.handleAlexaApiCall(request)) return;
#endif
handleStaticContent(request, request->url(), 404, "text/html", PAGE_404, PAGE_404_length);
handleStaticContent(request, request->url(), 404, FPSTR(s_html), PAGE_404, PAGE_404_length);
});
}
@ -371,8 +376,8 @@ bool handleIfNoneMatchCacheHeader(AsyncWebServerRequest *request, int code, uint
// Only send 304 (Not Modified) if response code is 200 (OK)
if (code != 200) return false;
AsyncWebHeader *header = request->getHeader("If-None-Match");
char etag[14];
AsyncWebHeader *header = request->getHeader(F("If-None-Match"));
char etag[32];
generateEtag(etag, eTagSuffix);
if (header && header->value() == etag) {
AsyncWebServerResponse *response = request->beginResponse(304);
@ -391,11 +396,11 @@ void setStaticContentCacheHeaders(AsyncWebServerResponse *response, int code, ui
#ifndef WLED_DEBUG
// this header name is misleading, "no-cache" will not disable cache,
// it just revalidates on every load using the "If-None-Match" header with the last ETag value
response->addHeader(F("Cache-Control"), "no-cache");
response->addHeader(F("Cache-Control"), F("no-cache"));
#else
response->addHeader(F("Cache-Control"), "no-store,max-age=0"); // prevent caching if debug build
response->addHeader(F("Cache-Control"), F("no-store,max-age=0")); // prevent caching if debug build
#endif
char etag[14];
char etag[32];
generateEtag(etag, eTagSuffix);
response->addHeader(F("ETag"), etag);
}
@ -419,7 +424,7 @@ void handleStaticContent(AsyncWebServerRequest *request, const String &path, int
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");
if (gzip) response->addHeader(FPSTR(s_content_enc), F("gzip"));
setStaticContentCacheHeaders(response, code, eTagSuffix);
request->send(response);
}
@ -466,15 +471,15 @@ void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& h
messageSub = subl;
optionType = optionT;
request->send_P(code, "text/html", PAGE_msg, msgProcessor);
request->send_P(code, FPSTR(s_html), PAGE_msg, msgProcessor);
}
void serveJsonError(AsyncWebServerRequest* request, uint16_t code, uint16_t error)
{
AsyncJsonResponse *response = new AsyncJsonResponse(64);
if (error < ERR_NOT_IMPL) response->addHeader("Retry-After", "1");
response->setContentType("application/json");
if (error < ERR_NOT_IMPL) response->addHeader(F("Retry-After"), F("1"));
response->setContentType(FPSTR(s_json));
response->setCode(code);
JsonObject obj = response->getRoot();
obj[F("error")] = error;
@ -487,16 +492,16 @@ String dmxProcessor(const String& var)
{
String mapJS;
#ifdef WLED_ENABLE_DMX
if (var == "DMXVARS") {
mapJS += "\nCN=" + String(DMXChannels) + ";\n";
mapJS += "CS=" + String(DMXStart) + ";\n";
mapJS += "CG=" + String(DMXGap) + ";\n";
mapJS += "LC=" + String(strip.getLengthTotal()) + ";\n";
mapJS += "var CH=[";
if (var == F("DMXVARS")) {
mapJS += F("\nCN=") + String(DMXChannels) + ";\n";
mapJS += F("CS=" )+ String(DMXStart) + ";\n";
mapJS += F("CG=") + String(DMXGap) + ";\n";
mapJS += F("LC=") + String(strip.getLengthTotal()) + ";\n";
mapJS += F("var CH=[");
for (int i=0;i<15;i++) {
mapJS += String(DMXFixtureMap[i]) + ",";
mapJS += String(DMXFixtureMap[i]) + ',';
}
mapJS += "0];";
mapJS += F("0];");
}
#endif
@ -512,12 +517,12 @@ void serveSettingsJS(AsyncWebServerRequest* request)
byte subPage = request->arg(F("p")).toInt();
if (subPage > 10) {
strcpy_P(buf, PSTR("alert('Settings for this request are not implemented.');"));
request->send(501, "application/javascript", buf);
request->send(501, FPSTR(s_javascript), buf);
return;
}
if (subPage > 0 && !correctPIN && strlen(settingsPIN)>0) {
strcpy_P(buf, PSTR("alert('PIN incorrect.');"));
request->send(401, "application/javascript", buf);
request->send(401, FPSTR(s_javascript), buf);
return;
}
strcat_P(buf,PSTR("function GetV(){var d=document;"));
@ -525,9 +530,9 @@ void serveSettingsJS(AsyncWebServerRequest* request)
strcat_P(buf,PSTR("}"));
AsyncWebServerResponse *response;
response = request->beginResponse(200, "application/javascript", buf);
response->addHeader(F("Cache-Control"),"no-store");
response->addHeader(F("Expires"),"0");
response = request->beginResponse(200, FPSTR(s_javascript), buf);
response->addHeader(F("Cache-Control"), F("no-store"));
response->addHeader(F("Expires"), F("0"));
request->send(response);
}
@ -562,7 +567,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
// if OTA locked or too frequent PIN entry requests fail hard
if ((subPage == SUBPAGE_WIFI && wifiLock && otaLock) || (post && !correctPIN && millis()-lastEditTime < PIN_RETRY_COOLDOWN))
{
serveMessage(request, 401, "Access Denied", FPSTR(s_unlock_ota), 254); return;
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254); return;
}
if (post) { //settings/set POST request, saving
@ -598,7 +603,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
}
int code = 200;
String contentType = "text/html";
String contentType = FPSTR(s_html);
const uint8_t* content;
size_t len;
@ -623,8 +628,8 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
serveMessage(request, 200, strlen(settingsPIN) > 0 ? PSTR("Settings locked") : PSTR("No PIN set"), FPSTR(s_redirecting), 1);
return;
}
case SUBPAGE_PINREQ : content = PAGE_settings_pin; len = PAGE_settings_pin_length; code = 401; break;
case SUBPAGE_CSS : content = PAGE_settingsCss; len = PAGE_settingsCss_length; contentType = "text/css"; break;
case SUBPAGE_PINREQ : content = PAGE_settings_pin; len = PAGE_settings_pin_length; code = 401; break;
case SUBPAGE_CSS : content = PAGE_settingsCss; len = PAGE_settingsCss_length; contentType = FPSTR(s_css); break;
case SUBPAGE_JS : serveSettingsJS(request); return;
case SUBPAGE_WELCOME : content = PAGE_welcome; len = PAGE_welcome_length; break;
default: content = PAGE_settings; len = PAGE_settings_length; break;