From 9088651e53c0ec8acc912bd30c5add89a18d5fa5 Mon Sep 17 00:00:00 2001 From: proddy Date: Fri, 23 Jan 2026 19:35:53 +0100 Subject: [PATCH] asyncwebserver update, improved caching --- lib_standalone/AsyncTCP.h | 156 +++++++++++++++++++++++++++++++++- platformio.ini | 2 +- src/ESP32React/ESP32React.cpp | 31 ++++--- 3 files changed, 170 insertions(+), 19 deletions(-) diff --git a/lib_standalone/AsyncTCP.h b/lib_standalone/AsyncTCP.h index 58307baab..67d733238 100644 --- a/lib_standalone/AsyncTCP.h +++ b/lib_standalone/AsyncTCP.h @@ -19,12 +19,164 @@ class AsyncClient { class AsyncServer { public: AsyncServer(uint16_t port) - : _port(port){}; - ~AsyncServer(){}; + : _port(port) {}; + ~AsyncServer() {}; protected: uint16_t _port; }; +namespace asyncsrv { + +static constexpr const char empty[] = ""; + +static constexpr const char T__opaque[] = "\", opaque=\""; +static constexpr const char T_100_CONTINUE[] = "100-continue"; +static constexpr const char T_13[] = "13"; +static constexpr const char T_ACCEPT[] = "Accept"; +static constexpr const char T_Accept_Ranges[] = "Accept-Ranges"; +static constexpr const char T_attachment[] = "attachment; filename=\""; +static constexpr const char T_AUTH[] = "Authorization"; +static constexpr const char T_auth_nonce[] = "\", qop=\"auth\", nonce=\""; +static constexpr const char T_BASIC[] = "Basic"; +static constexpr const char T_BASIC_REALM[] = "Basic realm=\""; +static constexpr const char T_BEARER[] = "Bearer"; +static constexpr const char T_BODY[] = "body"; +static constexpr const char T_Cache_Control[] = "Cache-Control"; +static constexpr const char T_chunked[] = "chunked"; +static constexpr const char T_close[] = "close"; +static constexpr const char T_cnonce[] = "cnonce"; +static constexpr const char T_Connection[] = "Connection"; +static constexpr const char T_Content_Disposition[] = "Content-Disposition"; +static constexpr const char T_Content_Encoding[] = "Content-Encoding"; +static constexpr const char T_Content_Length[] = "Content-Length"; +static constexpr const char T_Content_Type[] = "Content-Type"; +static constexpr const char T_Content_Location[] = "Content-Location"; +static constexpr const char T_Cookie[] = "Cookie"; +static constexpr const char T_CORS_ACAC[] = "Access-Control-Allow-Credentials"; +static constexpr const char T_CORS_ACAH[] = "Access-Control-Allow-Headers"; +static constexpr const char T_CORS_ACAM[] = "Access-Control-Allow-Methods"; +static constexpr const char T_CORS_ACAO[] = "Access-Control-Allow-Origin"; +static constexpr const char T_CORS_ACMA[] = "Access-Control-Max-Age"; +static constexpr const char T_CORS_O[] = "Origin"; +static constexpr const char T_data_[] = "data: "; +static constexpr const char T_Date[] = "Date"; +static constexpr const char T_DIGEST[] = "Digest"; +static constexpr const char T_DIGEST_[] = "Digest "; +static constexpr const char T_ETag[] = "ETag"; +static constexpr const char T_event_[] = "event: "; +static constexpr const char T_EXPECT[] = "Expect"; +static constexpr const char T_FALSE[] = "false"; +static constexpr const char T_filename[] = "filename"; +static constexpr const char T_gzip[] = "gzip"; +static constexpr const char T_Host[] = "host"; +static constexpr const char T_HTTP_1_0[] = "HTTP/1.0"; +static constexpr const char T_HTTP_100_CONT[] = "HTTP/1.1 100 Continue\r\n\r\n"; +static constexpr const char T_id__[] = "id: "; +static constexpr const char T_IMS[] = "If-Modified-Since"; +static constexpr const char T_INM[] = "If-None-Match"; +static constexpr const char T_inline[] = "inline"; +static constexpr const char T_keep_alive[] = "keep-alive"; +static constexpr const char T_Last_Event_ID[] = "Last-Event-ID"; +static constexpr const char T_Last_Modified[] = "Last-Modified"; +static constexpr const char T_LOCATION[] = "Location"; +static constexpr const char T_LOGIN_REQ[] = "Login Required"; +static constexpr const char T_MULTIPART_[] = "multipart/"; +static constexpr const char T_name[] = "name"; +static constexpr const char T_nc[] = "nc"; +static constexpr const char T_no_cache[] = "no-cache"; +static constexpr const char T_nonce[] = "nonce"; +static constexpr const char T_none[] = "none"; +static constexpr const char T_opaque[] = "opaque"; +static constexpr const char T_qop[] = "qop"; +static constexpr const char T_realm[] = "realm"; +static constexpr const char T_realm__[] = "realm=\""; +static constexpr const char T_response[] = "response"; +static constexpr const char T_retry_[] = "retry: "; +static constexpr const char T_retry_after[] = "Retry-After"; +static constexpr const char T_nn[] = "\n\n"; +static constexpr const char T_rn[] = "\r\n"; +static constexpr const char T_rnrn[] = "\r\n\r\n"; +static constexpr const char T_Server[] = "Server"; +static constexpr const char T_Transfer_Encoding[] = "Transfer-Encoding"; +static constexpr const char T_TRUE[] = "true"; +static constexpr const char T_UPGRADE[] = "Upgrade"; +static constexpr const char T_uri[] = "uri"; +static constexpr const char T_username[] = "username"; +static constexpr const char T_WS[] = "websocket"; +static constexpr const char T_WWW_AUTH[] = "WWW-Authenticate"; + +// HTTP Methods +static constexpr const char T_ANY[] = "ANY"; +static constexpr const char T_GET[] = "GET"; +static constexpr const char T_POST[] = "POST"; +static constexpr const char T_PUT[] = "PUT"; +static constexpr const char T_DELETE[] = "DELETE"; +static constexpr const char T_PATCH[] = "PATCH"; +static constexpr const char T_HEAD[] = "HEAD"; +static constexpr const char T_OPTIONS[] = "OPTIONS"; +static constexpr const char T_UNKNOWN[] = "UNKNOWN"; + +// Req content types +static constexpr const char T_RCT_NOT_USED[] = "RCT_NOT_USED"; +static constexpr const char T_RCT_DEFAULT[] = "RCT_DEFAULT"; +static constexpr const char T_RCT_HTTP[] = "RCT_HTTP"; +static constexpr const char T_RCT_WS[] = "RCT_WS"; +static constexpr const char T_RCT_EVENT[] = "RCT_EVENT"; +static constexpr const char T_ERROR[] = "ERROR"; + +// extensions & MIME-Types +static constexpr const char T__avif[] = ".avif"; // AVIF: Highly compressed images. Compatible with all modern browsers. +static constexpr const char T__csv[] = ".csv"; // CSV: Data logging and configuration +static constexpr const char T__css[] = ".css"; // CSS: Styling for web interfaces +static constexpr const char T__gif[] = ".gif"; // GIF: Simple animations. Legacy support +static constexpr const char T__gz[] = ".gz"; // GZ: compressed files +static constexpr const char T__htm[] = ".htm"; // HTM: Web interface files +static constexpr const char T__html[] = ".html"; // HTML: Web interface files +static constexpr const char T__ico[] = ".ico"; // ICO: Favicons, system icons. Legacy support +static constexpr const char T__jpg[] = ".jpg"; // JPEG/JPG: Photos. Legacy support +static constexpr const char T__js[] = ".js"; // JavaScript: Interactive functionality +static constexpr const char T__json[] = ".json"; // JSON: Data exchange format +static constexpr const char T__mp4[] = ".mp4"; // MP4: Proprietary format. Worse compression than WEBM. +static constexpr const char T__mjs[] = ".mjs"; // MJS: JavaScript module format +static constexpr const char T__opus[] = ".opus"; // OPUS: High compression audio format +static constexpr const char T__pdf[] = ".pdf"; // PDF: Universal document format +static constexpr const char T__png[] = ".png"; // PNG: Icons, logos, transparency. Legacy support +static constexpr const char T__svg[] = ".svg"; // SVG: Vector graphics, icons (scalable, tiny file sizes) +static constexpr const char T__ttf[] = ".ttf"; // TTF: Font file. Legacy support +static constexpr const char T__txt[] = ".txt"; // TXT: Plain text files +static constexpr const char T__webm[] = ".webm"; // WebM: Video. Open source, optimized for web. Compatible with all modern browsers. +static constexpr const char T__webp[] = ".webp"; // WebP: Highly compressed images. Compatible with all modern browsers. +static constexpr const char T__woff[] = ".woff"; // WOFF: Font file. Legacy support +static constexpr const char T__woff2[] = ".woff2"; // WOFF2: Better compression. Compatible with all modern browsers. +static constexpr const char T__xml[] = ".xml"; // XML: Configuration and data files +static constexpr const char T_application_javascript[] = "application/javascript"; // Obsolete type for JavaScript +static constexpr const char T_application_json[] = "application/json"; +static constexpr const char T_application_msgpack[] = "application/msgpack"; +static constexpr const char T_application_octet_stream[] = "application/octet-stream"; +static constexpr const char T_application_pdf[] = "application/pdf"; +static constexpr const char T_app_xform_urlencoded[] = "application/x-www-form-urlencoded"; +static constexpr const char T_audio_opus[] = "audio/opus"; +static constexpr const char T_font_ttf[] = "font/ttf"; +static constexpr const char T_font_woff[] = "font/woff"; +static constexpr const char T_font_woff2[] = "font/woff2"; +static constexpr const char T_image_avif[] = "image/avif"; +static constexpr const char T_image_gif[] = "image/gif"; +static constexpr const char T_image_jpeg[] = "image/jpeg"; +static constexpr const char T_image_png[] = "image/png"; +static constexpr const char T_image_svg_xml[] = "image/svg+xml"; +static constexpr const char T_image_webp[] = "image/webp"; +static constexpr const char T_image_x_icon[] = "image/x-icon"; +static constexpr const char T_text_css[] = "text/css"; +static constexpr const char T_text_csv[] = "text/csv"; +static constexpr const char T_text_event_stream[] = "text/event-stream"; +static constexpr const char T_text_html[] = "text/html"; +static constexpr const char T_text_javascript[] = "text/javascript"; +static constexpr const char T_text_plain[] = "text/plain"; +static constexpr const char T_text_xml[] = "text/xml"; +static constexpr const char T_video_mp4[] = "video/mp4"; +static constexpr const char T_video_webm[] = "video/webm"; +} // namespace asyncsrv + #endif diff --git a/platformio.ini b/platformio.ini index 31ac85d60..b031b9a8f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -106,7 +106,7 @@ board_build.filesystem = littlefs lib_deps = bblanchon/ArduinoJson @ 7.4.2 ESP32Async/AsyncTCP @ 3.4.10 - ESP32Async/ESPAsyncWebServer @ 3.9.4 + ESP32Async/ESPAsyncWebServer @ 3.9.5 https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.8 diff --git a/src/ESP32React/ESP32React.cpp b/src/ESP32React/ESP32React.cpp index 72bd28a2d..41536153f 100644 --- a/src/ESP32React/ESP32React.cpp +++ b/src/ESP32React/ESP32React.cpp @@ -2,6 +2,8 @@ #include "WWWData.h" // include auto-generated static web resources +static constexpr const char CACHE_CONTROL[] = "public,max-age=60"; + ESP32React::ESP32React(AsyncWebServer * server, FS * fs) : _securitySettingsService(server, fs) , _networkSettingsService(server, fs, &_securitySettingsService) @@ -22,21 +24,18 @@ ESP32React::ESP32React(AsyncWebServer * server, FS * fs) ArRequestHandlerFunction indexHtmlHandler = nullptr; WWWData::registerRoutes([server, &indexHtmlHandler](const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash) { - ArRequestHandlerFunction requestHandler = [contentType, content, len, hash](AsyncWebServerRequest * request) { - AsyncWebServerResponse * response; + String etag = "\"" + hash + "\""; // RFC9110: ETag must be enclosed in double quotes - // Check if the client already has the same version and respond with a 304 (Not modified) - if (request->header("If-None-Match").equals(hash)) { - response = request->beginResponse(304); - } else { - response = request->beginResponse(200, contentType, content, len); - response->addHeader("Content-Encoding", "gzip"); // not br for brotlin only works over HTTPS + ArRequestHandlerFunction requestHandler = [contentType, content, len, etag](AsyncWebServerRequest * request) { + if (request->header(asyncsrv::T_INM) == etag) { + request->send(304); + return; } - // always send these headers - see https://datatracker.ietf.org/doc/html/rfc7232#section-4.1 - response->addHeader("ETag", hash); - response->addHeader("Cache-Control", "no-cache"); // Requires revalidation before using cached content (ETags enable 304 responses) - + AsyncWebServerResponse * response = request->beginResponse(200, contentType, content, len); + response->addHeader(asyncsrv::T_Content_Encoding, asyncsrv::T_gzip, false); + response->addHeader(asyncsrv::T_ETag, etag, false); + response->addHeader(asyncsrv::T_Cache_Control, CACHE_CONTROL, false); request->send(response); }; @@ -69,11 +68,11 @@ void ESP32React::begin() { _networkSettingsService.read([&](NetworkSettings & networkSettings) { DefaultHeaders & defaultHeaders = DefaultHeaders::Instance(); if (networkSettings.enableCORS) { - defaultHeaders.addHeader("Access-Control-Allow-Origin", networkSettings.CORSOrigin); - defaultHeaders.addHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization"); - defaultHeaders.addHeader("Access-Control-Allow-Credentials", "true"); + defaultHeaders.addHeader(asyncsrv::T_CORS_ACAO, networkSettings.CORSOrigin); + defaultHeaders.addHeader(asyncsrv::T_CORS_ACAH, "Accept, Content-Type, Authorization"); + defaultHeaders.addHeader(asyncsrv::T_CORS_ACAC, "true"); } - defaultHeaders.addHeader("Server", networkSettings.hostname); + defaultHeaders.addHeader(asyncsrv::T_Server, networkSettings.hostname); }); _apSettingsService.begin(); _ntpSettingsService.begin();