mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-05-01 11:45:13 +00:00
single static-content handler serving all assets
This commit is contained in:
@@ -2,10 +2,75 @@
|
|||||||
|
|
||||||
#include "WWWData.h" // include auto-generated static web resources
|
#include "WWWData.h" // include auto-generated static web resources
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
static constexpr const char CACHE_CONTROL[] = "public,max-age=60";
|
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)
|
ESP32React::ESP32React(AsyncWebServer * server, FS * fs)
|
||||||
: _securitySettingsService(server, fs)
|
: _server(server)
|
||||||
|
, _securitySettingsService(server, fs)
|
||||||
, _networkSettingsService(server, fs, &_securitySettingsService)
|
, _networkSettingsService(server, fs, &_securitySettingsService)
|
||||||
, _wifiScanner(server, &_securitySettingsService)
|
, _wifiScanner(server, &_securitySettingsService)
|
||||||
, _networkStatus(server, &_securitySettingsService)
|
, _networkStatus(server, &_securitySettingsService)
|
||||||
@@ -17,50 +82,6 @@ ESP32React::ESP32React(AsyncWebServer * server, FS * fs)
|
|||||||
, _mqttSettingsService(server, fs, &_securitySettingsService)
|
, _mqttSettingsService(server, fs, &_securitySettingsService)
|
||||||
, _mqttStatus(server, &_mqttSettingsService, &_securitySettingsService)
|
, _mqttStatus(server, &_mqttSettingsService, &_securitySettingsService)
|
||||||
, _authenticationService(server, &_securitySettingsService) {
|
, _authenticationService(server, &_securitySettingsService) {
|
||||||
//
|
|
||||||
// Serve static web resources
|
|
||||||
//
|
|
||||||
|
|
||||||
ArRequestHandlerFunction indexHtmlHandler = nullptr;
|
|
||||||
|
|
||||||
WWWData::registerRoutes([server, &indexHtmlHandler](const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash) {
|
|
||||||
String etag = "\"" + hash + "\""; // RFC9110: ETag must be enclosed in double quotes
|
|
||||||
|
|
||||||
ArRequestHandlerFunction requestHandler = [contentType, content, len, etag](AsyncWebServerRequest * request) {
|
|
||||||
if (request->header(asyncsrv::T_INM) == etag) {
|
|
||||||
request->send(304);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
server->on(uri, HTTP_GET, requestHandler);
|
|
||||||
|
|
||||||
// Capture index.html handler to set onNotFound once after all routes are registered
|
|
||||||
if (strcmp(uri, "/index.html") == 0) {
|
|
||||||
indexHtmlHandler = requestHandler;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set onNotFound handler once after all routes are registered
|
|
||||||
// Serving non matching get requests with "/index.html"
|
|
||||||
// OPTIONS get a straight up 200 response
|
|
||||||
if (indexHtmlHandler != nullptr) {
|
|
||||||
server->onNotFound([indexHtmlHandler](AsyncWebServerRequest * request) {
|
|
||||||
if (request->method() == HTTP_GET) {
|
|
||||||
indexHtmlHandler(request);
|
|
||||||
} else if (request->method() == HTTP_OPTIONS) {
|
|
||||||
request->send(200);
|
|
||||||
} else {
|
|
||||||
request->send(404); // not found
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESP32React::begin() {
|
void ESP32React::begin() {
|
||||||
@@ -78,10 +99,11 @@ void ESP32React::begin() {
|
|||||||
_ntpSettingsService.begin();
|
_ntpSettingsService.begin();
|
||||||
_mqttSettingsService.begin();
|
_mqttSettingsService.begin();
|
||||||
_securitySettingsService.begin();
|
_securitySettingsService.begin();
|
||||||
|
_server->addHandler(new StaticContentHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESP32React::loop() {
|
void ESP32React::loop() {
|
||||||
_networkSettingsService.loop();
|
_networkSettingsService.loop();
|
||||||
_apSettingsService.loop();
|
_apSettingsService.loop();
|
||||||
_mqttSettingsService.loop();
|
_mqttSettingsService.loop();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ class ESP32React {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
AsyncWebServer * _server;
|
||||||
SecuritySettingsService _securitySettingsService;
|
SecuritySettingsService _securitySettingsService;
|
||||||
NetworkSettingsService _networkSettingsService;
|
NetworkSettingsService _networkSettingsService;
|
||||||
WiFiScanner _wifiScanner;
|
WiFiScanner _wifiScanner;
|
||||||
|
|||||||
Reference in New Issue
Block a user