From 175e182b4c1f28b157eb7f033e5b828dd00c9d36 Mon Sep 17 00:00:00 2001 From: Emil Muratov Date: Thu, 9 Jan 2025 00:19:53 +0900 Subject: [PATCH] rework INM/IMS handling for AsyncStaticWebHandler IMS template must contain GMT timezone, not local - "%a, %d %b %Y %H:%M:%S GMT" create etag based on timestamp + filesize INM header handling should have precedence over IMS --- src/WebHandlerImpl.h | 13 ++++++-- src/WebHandlers.cpp | 79 ++++++++++++++++++++++++++------------------ 2 files changed, 56 insertions(+), 36 deletions(-) diff --git a/src/WebHandlerImpl.h b/src/WebHandlerImpl.h index 0eaec65..134006f 100644 --- a/src/WebHandlerImpl.h +++ b/src/WebHandlerImpl.h @@ -57,12 +57,19 @@ class AsyncStaticWebHandler : public AsyncWebHandler { AsyncStaticWebHandler& setIsDir(bool isDir); AsyncStaticWebHandler& setDefaultFile(const char* filename); AsyncStaticWebHandler& setCacheControl(const char* cache_control); + + /** + * @brief Set the Last-Modified time for the object + * + * @param last_modified + * @return AsyncStaticWebHandler& + */ AsyncStaticWebHandler& setLastModified(const char* last_modified); AsyncStaticWebHandler& setLastModified(struct tm* last_modified); -#ifdef ESP8266 AsyncStaticWebHandler& setLastModified(time_t last_modified); - AsyncStaticWebHandler& setLastModified(); // sets to current time. Make sure sntp is runing and time is updated -#endif + // sets to current time. Make sure sntp is runing and time is updated + AsyncStaticWebHandler& setLastModified(); + AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback); }; diff --git a/src/WebHandlers.cpp b/src/WebHandlers.cpp index 335e1f2..67fcac8 100644 --- a/src/WebHandlers.cpp +++ b/src/WebHandlers.cpp @@ -70,12 +70,12 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir) { } AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename) { - _default_file = String(filename); + _default_file = filename; return *this; } AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control) { - _cache_control = String(cache_control); + _cache_control = cache_control; return *this; } @@ -85,16 +85,20 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_m } AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified) { - auto formatP = PSTR("%a, %d %b %Y %H:%M:%S %Z"); + char result[30]; +#ifdef ESP8266 + auto formatP = PSTR("%a, %d %b %Y %H:%M:%S GMT"); char format[strlen_P(formatP) + 1]; strcpy_P(format, formatP); +#else + static constexpr const char* format = "%a, %d %b %Y %H:%M:%S GMT"; +#endif - char result[30]; strftime(result, sizeof(result), format, last_modified); - return setLastModified((const char*)result); + _last_modified = result; + return *this; } -#ifdef ESP8266 AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified) { return setLastModified((struct tm*)gmtime(&last_modified)); } @@ -105,7 +109,7 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified() { return *this; return setLastModified(last_modified); } -#endif + bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest* request) const { return request->isHTTP() && request->method() == HTTP_GET && request->url().startsWith(_uri) && _getFile(request); } @@ -194,50 +198,59 @@ uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const { void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) { // Get the filename from request->_tempObject and free it - String filename = String((char*)request->_tempObject); + String filename((char*)request->_tempObject); free(request->_tempObject); request->_tempObject = NULL; - if (request->_tempFile == true) { + if (request->_tempFile != true){ + request->send(404); + return; + } + time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS) // set etag to lastmod timestamp if available, otherwise to size String etag; if (lw) { - setLastModified(gmtime(&lw)); + setLastModified(lw); #if defined(TARGET_RP2040) // time_t == long long int - const size_t len = 1 + 8 * sizeof(time_t); + constexpr size_t len = 1 + 8 * sizeof(time_t); char buf[len]; - char* ret = lltoa(lw, buf, len, 10); + char* ret = lltoa(lw ^ request->_tempFile.size(), buf, len, 10); etag = ret ? String(ret) : String(request->_tempFile.size()); #else - etag = String(lw); + etag = lw ^ request->_tempFile.size(); // etag combines file size and lastmod timestamp #endif } else { - etag = String(request->_tempFile.size()); + etag = request->_tempFile.size(); } - if (_last_modified.length() && _last_modified == request->header(T_IMS)) { + + bool not_modified = false; + + // if-none-match has precedence over if-modified-since + if (request->hasHeader(T_INM)) + not_modified = request->header(T_INM).equals(etag); + else if (_last_modified.length()) + not_modified = request->header(T_IMS).equals(_last_modified); + + AsyncWebServerResponse* response; + + if (not_modified){ request->_tempFile.close(); - request->send(304); // Not modified - } else if (_cache_control.length() && request->hasHeader(T_INM) && request->header(T_INM).equals(etag)) { - request->_tempFile.close(); - AsyncWebServerResponse* response = new AsyncBasicResponse(304); // Not modified - response->addHeader(T_Cache_Control, _cache_control.c_str()); - response->addHeader(T_ETag, etag.c_str()); - request->send(response); + response = new AsyncBasicResponse(304); // Not modified } else { - AsyncWebServerResponse* response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback); - if (_last_modified.length()) - response->addHeader(T_Last_Modified, _last_modified.c_str()); - if (_cache_control.length()) { - response->addHeader(T_Cache_Control, _cache_control.c_str()); - response->addHeader(T_ETag, etag.c_str()); - } - request->send(response); + response = new AsyncFileResponse(request->_tempFile, filename, emptyString, false, _callback); } - } else { - request->send(404); - } + + response->addHeader(T_ETag, etag.c_str()); + + if (_last_modified.length()) + response->addHeader(T_Last_Modified, _last_modified.c_str()); + if (_cache_control.length()) + response->addHeader(T_Cache_Control, _cache_control.c_str()); + + request->send(response); + } AsyncStaticWebHandler& AsyncStaticWebHandler::setTemplateProcessor(AwsTemplateProcessor newCallback) {