Fix WebConfig browser compatibility

This commit is contained in:
Kyle Duren committed 2026-09-30 16:58:36 -04:00
1 parent bb6089b313
commit 3613363674
4 files changed
+9361 -1936

No files matched your search

+46 -17
View File
@@ -154,15 +154,11 @@ public:
if (_in_flight != 0) return 0;
if (_state == RESPONSE_WAIT_ACK) {
// The full Content-Length has now been acknowledged. ESPAsyncWebServer
// otherwise performs a graceful active close for every response, and
// this IDF 4.4 stack retains malloc-backed TCP PCBs in TIME_WAIT for about
// two minutes. Repeated WebConfig loads consume scarce internal heap and
// can also prevent AsyncTCP from retiring close events, wedging both
// infrastructure WiFi and ESP-NOW. Abort only after the final ACK so the
// peer has the complete page, while freeing this PCB immediately. Keep
// RESPONSE_WAIT_ACK: abort synchronously dispatches disconnect and the
// request's scoped owner then destroys this response.
if (client) client->abort();
// otherwise performs a graceful active close for every response. Keep
// the graceful close here too: browsers can report a complete,
// Content-Length-delimited response as a failed transfer when the peer
// force-aborts the TCP PCB immediately after the final ACK.
if (client) client->close();
return 0;
}
return queueNext(request);
@@ -1651,6 +1647,31 @@ void WebConfigServer::handleRoot(AsyncWebServerRequest* req) {
req->send(res);
}
static bool webConfigClientAcceptsBrotli(AsyncWebServerRequest* req) {
if (!req || !req->hasHeader("Accept-Encoding")) return false;
String encodings = req->getHeader("Accept-Encoding")->value();
encodings.toLowerCase();
int start = 0;
while (start < encodings.length()) {
int end = encodings.indexOf(',', start);
if (end < 0) end = encodings.length();
String item = encodings.substring(start, end);
item.trim();
int semi = item.indexOf(';');
String coding = semi < 0 ? item : item.substring(0, semi);
coding.trim();
if (coding == "br") {
if (semi < 0) return true;
String params = item.substring(semi + 1);
params.trim();
int qpos = params.indexOf("q=");
return qpos < 0 || params.substring(qpos + 2).toFloat() > 0.0f;
}
start = end + 1;
}
return false;
}
void WebConfigServer::handleUi(AsyncWebServerRequest* req) {
if (_mode == MODE_OFF) { req->send(503); return; }
_last_activity = millis();
@@ -1664,14 +1685,22 @@ void WebConfigServer::handleUi(AsyncWebServerRequest* req) {
req->send(res);
return;
}
AsyncWebServerResponse* res =
new WebConfigPacedProgmemResponse("text/html; charset=utf-8",
WEBCONFIG_HTML_BR,
WEBCONFIG_HTML_BR_LEN);
// Keep exactly one whole-page asset in flash. Brotli is decoded by the
// browser; the ESP32 only streams these precompressed bytes.
res->addHeader("Content-Encoding", "br");
res->addHeader("Cache-Control", "public, max-age=31536000, immutable");
AsyncWebServerResponse* res = nullptr;
if (webConfigClientAcceptsBrotli(req)) {
res = new WebConfigPacedProgmemResponse("text/html; charset=utf-8",
WEBCONFIG_HTML_BR,
WEBCONFIG_HTML_BR_LEN);
// Brotli is decoded by the browser; the ESP32 only streams these
// precompressed bytes.
res->addHeader("Content-Encoding", "br");
res->addHeader("Cache-Control", "public, max-age=31536000, immutable");
} else {
// Never send Brotli to clients that did not advertise support for it.
res = new WebConfigPacedProgmemResponse("text/html; charset=utf-8",
WEBCONFIG_HTML_RAW,
WEBCONFIG_HTML_RAW_LEN);
res->addHeader("Cache-Control", "no-cache");
}
req->send(res);
}