#include "ESP32React.h" #include "WWWData.h" // include auto-generated static web resources #include static constexpr const char CACHE_CONTROL[] = "public,max-age=60"; // Single static-content handler serving all assets embedded in WWWData.h class StaticContentHandler : public AsyncWebHandler { public: bool canHandle(AsyncWebServerRequest * request) const override { const auto method = request->method(); return method == HTTP_GET || method == HTTP_HEAD || method == HTTP_OPTIONS; } void handleRequest(AsyncWebServerRequest * request) override { // OPTIONS is handled generically - the server-level CORS headers are attached via DefaultHeaders in ESP32React::begin(). if (request->method() == HTTP_OPTIONS) { request->send(200); return; } const char * url = request->url().c_str(); const WWWAsset * found = lookup(url); const WWWAsset * asset = found ? found : index_asset(); if (asset == nullptr) { request->send(404); return; } // If the client already has this exact ETag, respond 304 Not Modified without sending the body. const String & inm = request->header(asyncsrv::T_INM); if (inm.length() != 0 && strcmp(inm.c_str(), asset->etag) == 0) { request->send(304); return; } AsyncWebServerResponse * response = request->beginResponse(200, asset->contentType, asset->content, asset->len); response->addHeader(asyncsrv::T_Content_Encoding, asyncsrv::T_gzip, false); response->addHeader(asyncsrv::T_ETag, asset->etag, false); response->addHeader(asyncsrv::T_Cache_Control, CACHE_CONTROL, false); request->send(response); } private: // Exact-match lookup in the asset table static const WWWAsset * lookup(const char * url) { for (size_t i = 0; i < WWW_ASSETS_COUNT; i++) { if (strcmp(WWW_ASSETS[i].uri, url) == 0) { return &WWW_ASSETS[i]; } } return nullptr; } // Returns the /index.html asset, used as the SPA fallback for any GET // that didn't match an embedded asset (React Router handles routing on the client side). static const WWWAsset * index_asset() { static const WWWAsset * cached = nullptr; if (cached == nullptr) { cached = lookup("/index.html"); } return cached; } }; ESP32React::ESP32React(AsyncWebServer * server, FS * fs) : _server(server) , _securitySettingsService(server, fs) , _networkSettingsService(server, fs, &_securitySettingsService) , _wifiScanner(server, &_securitySettingsService) , _networkStatus(server, &_securitySettingsService) , _apSettingsService(server, fs, &_securitySettingsService) , _apStatus(server, &_securitySettingsService, &_apSettingsService) , _ntpSettingsService(server, fs, &_securitySettingsService) , _ntpStatus(server, &_securitySettingsService) , _uploadFileService(server, &_securitySettingsService) , _mqttSettingsService(server, fs, &_securitySettingsService) , _mqttStatus(server, &_mqttSettingsService, &_securitySettingsService) , _authenticationService(server, &_securitySettingsService) { } void ESP32React::begin() { _networkSettingsService.begin(); _networkSettingsService.read([&](NetworkSettings & networkSettings) { DefaultHeaders & defaultHeaders = DefaultHeaders::Instance(); if (networkSettings.enableCORS) { 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(asyncsrv::T_Server, networkSettings.hostname); }); _apSettingsService.begin(); _ntpSettingsService.begin(); _mqttSettingsService.begin(); _securitySettingsService.begin(); _server->addHandler(new StaticContentHandler()); } void ESP32React::loop() { _networkSettingsService.loop(); _apSettingsService.loop(); _mqttSettingsService.loop(); _ntpSettingsService.loop(); }