From f77530983da890e5a1620a267772eb65cf4e0a93 Mon Sep 17 00:00:00 2001 From: Jan Kolena Date: Mon, 25 May 2020 21:13:47 +0200 Subject: [PATCH 1/3] Readme update * "Respond with file content using a callback and extra headers" section * "Serving static files by custom handling" section --- README.md | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/README.md b/README.md index d6dd3206a..75456ed80 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ To use this library you might need to have the latest git versions of [ESP32](ht - [Respond with content coming from a File containing templates](#respond-with-content-coming-from-a-file-containing-templates) - [Respond with content using a callback](#respond-with-content-using-a-callback) - [Respond with content using a callback and extra headers](#respond-with-content-using-a-callback-and-extra-headers) + - [Respond with file content using a callback and extra headers](#respond-with-file-content-using-a-callback-and-extra-headers) - [Respond with content using a callback containing templates](#respond-with-content-using-a-callback-containing-templates) - [Respond with content using a callback containing templates and extra headers](#respond-with-content-using-a-callback-containing-templates-and-extra-headers) - [Chunked Response](#chunked-response) @@ -67,6 +68,7 @@ To use this library you might need to have the latest git versions of [ESP32](ht - [Specifying Cache-Control header](#specifying-cache-control-header) - [Specifying Date-Modified header](#specifying-date-modified-header) - [Specifying Template Processor callback](#specifying-template-processor-callback) + - [Serving static files by custom handling](#serving-static-files-by-custom-handling) - [Param Rewrite With Matching](#param-rewrite-with-matching) - [Using filters](#using-filters) - [Serve different site files in AP mode](#serve-different-site-files-in-ap-mode) @@ -602,6 +604,45 @@ response->addHeader("Server","ESP Async Web Server"); request->send(response); ``` +### Respond with file content using a callback and extra headers + +With this code your ESP is able to serve even large (large in terms of ESP, e.g. 100kB) files +without memory problems. + +You need to create a file handler in outer function (to have a single one for request) but use +it in a lambda. The catch is that the lambda has it's own lifecycle which may/will cause it's +called after the original function is over thus the original file handle is destroyed. Using the +file handle in the lambda then causes segfault (Hello, Exception 9!) and the whole ESP crashes. +By using this code, you tell the compiler to move the handle into the lambda so it won't be +destroyed when outer function (that one where you call `request->send(response)`) ends. + +```cpp +const File srcFile = ... // e.g. SPIFFS.open(path, "r"); + +const contentType = "application/javascript"; + +AsyncWebServerResponse *response = request->beginResponse( + contentType, + srcFile.size(), + [src_file](uint8_t *buffer, size_t maxLen, size_t total) -> size_t { + File file = src_file; // local copy of file pointer + + int bytes = file.read(buffer, maxLen); + + // close file at the end + if (bytes + total == file.size()) file.close(); + + return max(0, bytes); // return 0 even when no bytes were loaded + } +); + +if (gzipped) { + response->addHeader(F("Content-Encoding"), F("gzip")); +} + +request->send(response); +``` + ### Respond with content using a callback containing templates ```cpp String processor(const String& var) @@ -858,6 +899,53 @@ String processor(const String& var) server.serveStatic("/", SPIFFS, "/www/").setTemplateProcessor(processor); ``` +### Serving static files by custom handling + +It may happen your static files are too big and the ESP will crash the request before it sends the whole file. +In that case, you can handle static files with custom file serving through not found handler. + +This code below is more-or-less equivalent to this: +```cpp +webServer.serveStatic("/", SPIFFS, STATIC_FILES_PREFIX).setDefaultFile("index.html") +``` + +First, declare the handling function: +```cpp +bool handleStaticFile(AsyncWebServerRequest *request) { + String path = STATIC_FILES_PREFIX + request->url(); + + if (path.endsWith("/")) path += F("index.html"); + + String contentType = getContentType(path); + String pathWithGz = path + ".gz"; + + if (SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) { + bool gzipped = false; + if (SPIFFS.exists(pathWithGz)) { + gzipped = true; + path += ".gz"; + } + + // TODO serve the file + + return true; + } + + return false; +} +``` +And then configure your webserver: +```cpp +webServer.onNotFound([](AsyncWebServerRequest *request) { + if (handleStaticFile(request)) return; + + request->send(404); +}); +``` + +You may want to try [Respond with file content using a callback and extra headers](#respond-with-file-content-using-a-callback-and-extra-headers) +For actual serving the file. + ## Param Rewrite With Matching It is possible to rewrite the request url with parameter matchg. Here is an example with one parameter: Rewrite for example "/radio/{frequence}" -> "/radio?f={frequence}" From 59ece3612aae51ec7e54bd30c4a0ddc8fa0202fb Mon Sep 17 00:00:00 2001 From: Jan Kolena Date: Tue, 26 May 2020 12:07:42 +0200 Subject: [PATCH 2/3] fixup! Readme update Typo fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 75456ed80..038a25eed 100644 --- a/README.md +++ b/README.md @@ -625,7 +625,7 @@ AsyncWebServerResponse *response = request->beginResponse( contentType, srcFile.size(), [src_file](uint8_t *buffer, size_t maxLen, size_t total) -> size_t { - File file = src_file; // local copy of file pointer + File file = srcFile; // local copy of file pointer int bytes = file.read(buffer, maxLen); From 48a85c242fca7786225e1412d4ec1d50cb5ae4f4 Mon Sep 17 00:00:00 2001 From: Jan Kolena Date: Tue, 26 May 2020 12:45:53 +0200 Subject: [PATCH 3/3] fixup! Readme update Code impr --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 038a25eed..84cfb328f 100644 --- a/README.md +++ b/README.md @@ -612,21 +612,19 @@ without memory problems. You need to create a file handler in outer function (to have a single one for request) but use it in a lambda. The catch is that the lambda has it's own lifecycle which may/will cause it's called after the original function is over thus the original file handle is destroyed. Using the -file handle in the lambda then causes segfault (Hello, Exception 9!) and the whole ESP crashes. +captured `&file` in the lambda then causes segfault (Hello, Exception 9!) and the whole ESP crashes. By using this code, you tell the compiler to move the handle into the lambda so it won't be destroyed when outer function (that one where you call `request->send(response)`) ends. ```cpp -const File srcFile = ... // e.g. SPIFFS.open(path, "r"); +const File file = ... // e.g. SPIFFS.open(path, "r"); const contentType = "application/javascript"; AsyncWebServerResponse *response = request->beginResponse( contentType, - srcFile.size(), - [src_file](uint8_t *buffer, size_t maxLen, size_t total) -> size_t { - File file = srcFile; // local copy of file pointer - + file.size(), + [file](uint8_t *buffer, size_t maxLen, size_t total) mutable -> size_t { int bytes = file.read(buffer, maxLen); // close file at the end