From 85ea4e995a75abe061f6fc375ea0481084dddd43 Mon Sep 17 00:00:00 2001 From: schererleander Date: Tue, 20 Jan 2026 08:34:54 +0100 Subject: initial commit --- .../AsyncResponseStream/AsyncResponseStream.ino | 47 ++++ .../ESP_Async_WebServer/examples/Auth/Auth.ino | 157 +++++++++++++ .../ESP_Async_WebServer/examples/CORS/CORS.ino | 60 +++++ .../examples/CaptivePortal/CaptivePortal.ino | 60 +++++ .../examples/CatchAllHandler/CatchAllHandler.ino | 133 +++++++++++ .../examples/ChunkResponse/ChunkResponse.ino | 140 ++++++++++++ .../ChunkRetryResponse/ChunkRetryResponse.ino | 216 ++++++++++++++++++ .../examples/EndBegin/EndBegin.ino | 49 +++++ .../examples/Filters/Filters.ino | 136 ++++++++++++ .../examples/FlashResponse/FlashResponse.ino | 107 +++++++++ .../HeaderManipulation/HeaderManipulation.ino | 88 ++++++++ .../examples/Headers/Headers.ino | 69 ++++++ .../ESP_Async_WebServer/examples/Json/Json.ino | 90 ++++++++ .../examples/Logging/Logging.ino | 49 +++++ .../examples/MessagePack/MessagePack.ino | 88 ++++++++ .../examples/Middleware/Middleware.ino | 82 +++++++ .../ESP_Async_WebServer/examples/Params/Params.ino | 122 +++++++++++ .../PartitionDownloader/PartitionDownloader.ino | 130 +++++++++++ .../examples/PerfTests/PerfTests.ino | 243 +++++++++++++++++++++ .../examples/RateLimit/RateLimit.ino | 64 ++++++ .../examples/Redirect/Redirect.ino | 48 ++++ .../RequestContinuation/RequestContinuation.ino | 91 ++++++++ .../RequestContinuationComplete.ino | 165 ++++++++++++++ .../ResumableDownload/ResumableDownload.ino | 61 ++++++ .../examples/Rewrite/Rewrite.ino | 52 +++++ .../examples/ServerSentEvents/ServerSentEvents.ino | 105 +++++++++ .../ServerSentEvents_PR156.ino | 141 ++++++++++++ .../examples/ServerState/ServerState.ino | 66 ++++++ .../SkipServerMiddleware/SkipServerMiddleware.ino | 73 +++++++ .../SlowChunkResponse/SlowChunkResponse.ino | 152 +++++++++++++ .../examples/StaticFile/StaticFile.ino | 144 ++++++++++++ .../examples/Templates/Templates.ino | 99 +++++++++ .../ESP_Async_WebServer/examples/Upload/Upload.ino | 171 +++++++++++++++ .../examples/WebSocket/WebSocket.ino | 115 ++++++++++ .../examples/WebSocketEasy/WebSocketEasy.ino | 124 +++++++++++ 35 files changed, 3737 insertions(+) create mode 100644 libraries/ESP_Async_WebServer/examples/AsyncResponseStream/AsyncResponseStream.ino create mode 100644 libraries/ESP_Async_WebServer/examples/Auth/Auth.ino create mode 100644 libraries/ESP_Async_WebServer/examples/CORS/CORS.ino create mode 100644 libraries/ESP_Async_WebServer/examples/CaptivePortal/CaptivePortal.ino create mode 100644 libraries/ESP_Async_WebServer/examples/CatchAllHandler/CatchAllHandler.ino create mode 100644 libraries/ESP_Async_WebServer/examples/ChunkResponse/ChunkResponse.ino create mode 100644 libraries/ESP_Async_WebServer/examples/ChunkRetryResponse/ChunkRetryResponse.ino create mode 100644 libraries/ESP_Async_WebServer/examples/EndBegin/EndBegin.ino create mode 100644 libraries/ESP_Async_WebServer/examples/Filters/Filters.ino create mode 100644 libraries/ESP_Async_WebServer/examples/FlashResponse/FlashResponse.ino create mode 100644 libraries/ESP_Async_WebServer/examples/HeaderManipulation/HeaderManipulation.ino create mode 100644 libraries/ESP_Async_WebServer/examples/Headers/Headers.ino create mode 100644 libraries/ESP_Async_WebServer/examples/Json/Json.ino create mode 100644 libraries/ESP_Async_WebServer/examples/Logging/Logging.ino create mode 100644 libraries/ESP_Async_WebServer/examples/MessagePack/MessagePack.ino create mode 100644 libraries/ESP_Async_WebServer/examples/Middleware/Middleware.ino create mode 100644 libraries/ESP_Async_WebServer/examples/Params/Params.ino create mode 100644 libraries/ESP_Async_WebServer/examples/PartitionDownloader/PartitionDownloader.ino create mode 100644 libraries/ESP_Async_WebServer/examples/PerfTests/PerfTests.ino create mode 100644 libraries/ESP_Async_WebServer/examples/RateLimit/RateLimit.ino create mode 100644 libraries/ESP_Async_WebServer/examples/Redirect/Redirect.ino create mode 100644 libraries/ESP_Async_WebServer/examples/RequestContinuation/RequestContinuation.ino create mode 100644 libraries/ESP_Async_WebServer/examples/RequestContinuationComplete/RequestContinuationComplete.ino create mode 100644 libraries/ESP_Async_WebServer/examples/ResumableDownload/ResumableDownload.ino create mode 100644 libraries/ESP_Async_WebServer/examples/Rewrite/Rewrite.ino create mode 100644 libraries/ESP_Async_WebServer/examples/ServerSentEvents/ServerSentEvents.ino create mode 100644 libraries/ESP_Async_WebServer/examples/ServerSentEvents_PR156/ServerSentEvents_PR156.ino create mode 100644 libraries/ESP_Async_WebServer/examples/ServerState/ServerState.ino create mode 100644 libraries/ESP_Async_WebServer/examples/SkipServerMiddleware/SkipServerMiddleware.ino create mode 100644 libraries/ESP_Async_WebServer/examples/SlowChunkResponse/SlowChunkResponse.ino create mode 100644 libraries/ESP_Async_WebServer/examples/StaticFile/StaticFile.ino create mode 100644 libraries/ESP_Async_WebServer/examples/Templates/Templates.ino create mode 100644 libraries/ESP_Async_WebServer/examples/Upload/Upload.ino create mode 100644 libraries/ESP_Async_WebServer/examples/WebSocket/WebSocket.ino create mode 100644 libraries/ESP_Async_WebServer/examples/WebSocketEasy/WebSocketEasy.ino (limited to 'libraries/ESP_Async_WebServer/examples') diff --git a/libraries/ESP_Async_WebServer/examples/AsyncResponseStream/AsyncResponseStream.ino b/libraries/ESP_Async_WebServer/examples/AsyncResponseStream/AsyncResponseStream.ino new file mode 100644 index 0000000..62fa799 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/AsyncResponseStream/AsyncResponseStream.ino @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // Shows how to use AsyncResponseStream. + // The internal buffer will be allocated and data appended to it, + // until the response is sent, then this buffer is read and committed on the network. + // + // curl -v http://192.168.4.1/ + // + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + AsyncResponseStream *response = request->beginResponseStream("plain/text", 40 * 1024); + for (int i = 0; i < 32 * 1024; i++) { + response->write('a'); + } + request->send(response); + }); + + server.begin(); +} + +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/Auth/Auth.ino b/libraries/ESP_Async_WebServer/examples/Auth/Auth.ino new file mode 100644 index 0000000..c3751e0 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/Auth/Auth.ino @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Authentication and authorization middlewares +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +// basicAuth +static AsyncAuthenticationMiddleware basicAuth; +static AsyncAuthenticationMiddleware basicAuthHash; + +// simple digest authentication +static AsyncAuthenticationMiddleware digestAuth; +static AsyncAuthenticationMiddleware digestAuthHash; + +// complex authentication which adds request attributes for the next middlewares and handler +static AsyncMiddlewareFunction complexAuth([](AsyncWebServerRequest *request, ArMiddlewareNext next) { + if (!request->authenticate("user", "password")) { + return request->requestAuthentication(); + } + + // add attributes to the request for the next middlewares and handler + request->setAttribute("user", "Mathieu"); + request->setAttribute("role", "staff"); + if (request->hasParam("token")) { + request->setAttribute("token", request->getParam("token")->value().c_str()); + } + + next(); +}); + +static AsyncAuthorizationMiddleware authz([](AsyncWebServerRequest *request) { + return request->getAttribute("token") == "123"; +}); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // basic authentication + basicAuth.setUsername("admin"); + basicAuth.setPassword("admin"); + basicAuth.setRealm("MyApp"); + basicAuth.setAuthFailureMessage("Authentication failed"); + basicAuth.setAuthType(AsyncAuthType::AUTH_BASIC); + basicAuth.generateHash(); // precompute hash (optional but recommended) + + // basic authentication with hash + basicAuthHash.setUsername("admin"); + basicAuthHash.setPasswordHash("YWRtaW46YWRtaW4="); // BASE64(admin:admin) + basicAuthHash.setRealm("MyApp"); + basicAuthHash.setAuthFailureMessage("Authentication failed"); + basicAuthHash.setAuthType(AsyncAuthType::AUTH_BASIC); + + // digest authentication + digestAuth.setUsername("admin"); + digestAuth.setPassword("admin"); + digestAuth.setRealm("MyApp"); + digestAuth.setAuthFailureMessage("Authentication failed"); + digestAuth.setAuthType(AsyncAuthType::AUTH_DIGEST); + digestAuth.generateHash(); // precompute hash (optional but recommended) + + // digest authentication with hash + digestAuthHash.setUsername("admin"); + digestAuthHash.setPasswordHash("f499b71f9a36d838b79268e145e132f7"); // MD5(user:realm:pass) + digestAuthHash.setRealm("MyApp"); + digestAuthHash.setAuthFailureMessage("Authentication failed"); + digestAuthHash.setAuthType(AsyncAuthType::AUTH_DIGEST); + + // basic authentication method + // curl -v -u admin:admin http://192.168.4.1/auth-basic + server + .on( + "/auth-basic", HTTP_GET, + [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello, world!"); + } + ) + .addMiddleware(&basicAuth); + + // basic authentication method with hash + // curl -v -u admin:admin http://192.168.4.1/auth-basic-hash + server + .on( + "/auth-basic-hash", HTTP_GET, + [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello, world!"); + } + ) + .addMiddleware(&basicAuthHash); + + // digest authentication + // curl -v -u admin:admin --digest http://192.168.4.1/auth-digest + server + .on( + "/auth-digest", HTTP_GET, + [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello, world!"); + } + ) + .addMiddleware(&digestAuth); + + // digest authentication with hash + // curl -v -u admin:admin --digest http://192.168.4.1/auth-digest-hash + server + .on( + "/auth-digest-hash", HTTP_GET, + [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello, world!"); + } + ) + .addMiddleware(&digestAuthHash); + + // test digest auth custom authorization middleware + // curl -v --digest -u user:password http://192.168.4.1/auth-custom?token=123 => OK + // curl -v --digest -u user:password http://192.168.4.1/auth-custom?token=456 => 403 + // curl -v --digest -u user:FAILED http://192.168.4.1/auth-custom?token=456 => 401 + server + .on( + "/auth-custom", HTTP_GET, + [](AsyncWebServerRequest *request) { + String buffer = "Hello "; + buffer.concat(request->getAttribute("user")); + buffer.concat(" with role: "); + buffer.concat(request->getAttribute("role")); + request->send(200, "text/plain", buffer); + } + ) + .addMiddlewares({&complexAuth, &authz}); + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/CORS/CORS.ino b/libraries/ESP_Async_WebServer/examples/CORS/CORS.ino new file mode 100644 index 0000000..3be46fd --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/CORS/CORS.ino @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// How to use CORS middleware +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); +static AsyncCorsMiddleware cors; + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + cors.setOrigin("http://192.168.4.1"); + cors.setMethods("POST, GET, OPTIONS, DELETE"); + cors.setHeaders("X-Custom-Header"); + cors.setAllowCredentials(false); + cors.setMaxAge(600); + + server.addMiddleware(&cors); + + // Test CORS preflight request + // curl -v -X OPTIONS -H "origin: http://192.168.4.1" http://192.168.4.1/cors + // + // Test CORS request + // curl -v -H "origin: http://192.168.4.1" http://192.168.4.1/cors + // + // Test non-CORS request + // curl -v http://192.168.4.1/cors + // + server.on("/cors", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello, world!"); + }); + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/CaptivePortal/CaptivePortal.ino b/libraries/ESP_Async_WebServer/examples/CaptivePortal/CaptivePortal.ino new file mode 100644 index 0000000..a872a9b --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/CaptivePortal/CaptivePortal.ino @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif +#include "ESPAsyncWebServer.h" + +static DNSServer dnsServer; +static AsyncWebServer server(80); + +class CaptiveRequestHandler : public AsyncWebHandler { +public: + bool canHandle(__unused AsyncWebServerRequest *request) const override { + return true; + } + + void handleRequest(AsyncWebServerRequest *request) { + AsyncResponseStream *response = request->beginResponseStream("text/html"); + response->print("Captive Portal"); + response->print("

This is our captive portal front page.

"); + response->printf("

You were trying to reach: http://%s%s

", request->host().c_str(), request->url().c_str()); +#ifndef CONFIG_IDF_TARGET_ESP32H2 + response->printf("

Try opening this link instead

", WiFi.softAPIP().toString().c_str()); +#endif + response->print(""); + request->send(response); + } +}; + +void setup() { + Serial.begin(115200); + Serial.println(); + Serial.println("Configuring access point..."); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + if (!WiFi.softAP("esp-captive")) { + Serial.println("Soft AP creation failed."); + while (1); + } + + dnsServer.start(53, "*", WiFi.softAPIP()); +#endif + + server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER); // only when requested from AP + // more handlers... + server.begin(); +} + +void loop() { + dnsServer.processNextRequest(); +} diff --git a/libraries/ESP_Async_WebServer/examples/CatchAllHandler/CatchAllHandler.ino b/libraries/ESP_Async_WebServer/examples/CatchAllHandler/CatchAllHandler.ino new file mode 100644 index 0000000..42a3698 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/CatchAllHandler/CatchAllHandler.ino @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Shows how to catch all requests and send a 404 Not Found response +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +static const char *htmlContent PROGMEM = R"( + + + + Sample HTML + + +

Hello, World!

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+ + +)"; + +static const size_t htmlContentLength = strlen_P(htmlContent); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // curl -v http://192.168.4.1/ + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + // need to cast to uint8_t* + // if you do not, the const char* will be copied in a temporary String buffer + request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength); + }); + + // catch any request, and send a 404 Not Found response + // except for /game_log which is handled by onRequestBody + // + // curl -v http://192.168.4.1/foo + // + server.onNotFound([](AsyncWebServerRequest *request) { + if (request->url() == "/game_log") { + return; // response object already created by onRequestBody + } + + request->send(404, "text/plain", "Not found"); + }); + + // See: https://github.com/ESP32Async/ESPAsyncWebServer/issues/6 + // catch any POST request and send a 200 OK response + // + // curl -v -X POST http://192.168.4.1/game_log -H "Content-Type: application/json" -d '{"game": "test"}' + // + server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { + if (request->url() == "/game_log") { + request->send(200, "application/json", "{\"status\":\"OK\"}"); + } + // note that there is no else here: the goal is only to prepare a response based on some body content + // onNotFound will always be called after this, and will not override the response object if `/game_log` is requested + }); + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/ChunkResponse/ChunkResponse.ino b/libraries/ESP_Async_WebServer/examples/ChunkResponse/ChunkResponse.ino new file mode 100644 index 0000000..e7d4838 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/ChunkResponse/ChunkResponse.ino @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Chunk response with caching example +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +static const char *htmlContent PROGMEM = R"( + + + + Sample HTML + + +

Hello, World!

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+ + +)"; + +static const size_t htmlContentLength = strlen_P(htmlContent); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // first time: serves the file and cache headers + // curl -N -v http://192.168.4.1/ --output - + // + // secodn time: serves 304 + // curl -N -v -H "if-none-match: 4272" http://192.168.4.1/ --output - + // + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + String etag = String(htmlContentLength); + + if (request->header(asyncsrv::T_INM) == etag) { + request->send(304); + return; + } + + AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + Serial.printf("%u / %u\n", index, htmlContentLength); + + // finished ? + if (htmlContentLength <= index) { + Serial.println("finished"); + return 0; + } + + // serve a maximum of 256 or maxLen bytes of the remaining content + // this small number is specifically chosen to demonstrate the chunking + // DO NOT USE SUCH SMALL NUMBER IN PRODUCTION + // Reducing the chunk size will increase the response time, thus reducing the server's capacity in processing concurrent requests + const int chunkSize = min((size_t)256, min(maxLen, htmlContentLength - index)); + Serial.printf("sending: %u\n", chunkSize); + + memcpy(buffer, htmlContent + index, chunkSize); + + return chunkSize; + }); + + response->addHeader(asyncsrv::T_Cache_Control, "public,max-age=60"); + response->addHeader(asyncsrv::T_ETag, etag); + + request->send(response); + }); + + server.begin(); +} + +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/ChunkRetryResponse/ChunkRetryResponse.ino b/libraries/ESP_Async_WebServer/examples/ChunkRetryResponse/ChunkRetryResponse.ino new file mode 100644 index 0000000..48772cc --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/ChunkRetryResponse/ChunkRetryResponse.ino @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Shows how to wait in a chunk response for incoming data +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +#if __has_include("ArduinoJson.h") +#include +#include +#include +#endif + +static const char *htmlContent PROGMEM = R"( + + + + Sample HTML + + +

Hello, World!

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+ + +)"; + +static const size_t htmlContentLength = strlen_P(htmlContent); + +static AsyncWebServer server(80); +static AsyncLoggingMiddleware requestLogger; + +static String triggerUART; +static int key = -1; + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // adds some internal request logging for debugging + requestLogger.setEnabled(true); + requestLogger.setOutput(Serial); + + server.addMiddleware(&requestLogger); + +#if __has_include("ArduinoJson.h") + + // + // HOW TO RUN THIS EXAMPLE: + // + // 1. Trigger a request that will be blocked for a long time: + // > time curl -v -X POST http://192.168.4.1/api -H "Content-Type: application/json" -d '{"input": "Please type a key to continue in Serial console..."}' --output - + // + // 2. While waiting, in another terminal, run some concurrent requests: + // > time curl -v http://192.168.4.1/ + // + // 3. Type a key in the Serial console to continue the processing within 30 seconds. + // This should unblock the first request. + // + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + // need to cast to uint8_t* + // if you do not, the const char* will be copied in a temporary String buffer + request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength); + }); + + server.on( + "/api", HTTP_POST, + [](AsyncWebServerRequest *request) { + // request parsing has finished + + // no data ? + if (!((String *)request->_tempObject)->length()) { + request->send(400); + return; + } + + JsonDocument doc; + + // deserialize and check for errors + if (deserializeJson(doc, *(String *)request->_tempObject)) { + request->send(400); + return; + } + + // start UART com: UART will send the data to the Serial console and wait for the key press + triggerUART = doc["input"].as(); + key = -1; + + AsyncWebServerResponse *response = request->beginChunkedResponse("text/plain", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + // still waiting for UARY ? + if (triggerUART.length() && key == -1) { + return RESPONSE_TRY_AGAIN; + } + + // finished ? + if (!triggerUART.length() && key == -1) { + return 0; // 0 means we are done + } + + // log_d("UART answered!"); + + String answer = "You typed: "; + answer.concat((char)key); + + // note: I did not check for maxLen, but you should (see ChunkResponse.ino) + memcpy(buffer, answer.c_str(), answer.length()); + + // finish! + triggerUART = emptyString; + key = -1; + + return answer.length(); + }); + + request->send(response); + }, + NULL, // upload handler is not used so it should be NULL + [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { + // log_d("Body: index: %u, len: %u, total: %u", index, len, total); + + if (!index) { + // log_d("Start body parsing"); + request->_tempObject = new String(); + // cast request->_tempObject pointer to String and reserve total size + ((String *)request->_tempObject)->reserve(total); + // set timeout 30s + request->client()->setRxTimeout(30); + } + + // log_d("Append body data"); + ((String *)request->_tempObject)->concat((const char *)data, len); + } + ); + +#endif + + server.begin(); +} + +void loop() { + if (triggerUART.length() && key == -1) { + Serial.println(triggerUART); + // log_d("Waiting for UART input..."); + while (!Serial.available()) { + delay(100); + } + key = Serial.read(); + Serial.flush(); + // log_d("UART input: %c", key); + triggerUART = emptyString; + } +} diff --git a/libraries/ESP_Async_WebServer/examples/EndBegin/EndBegin.ino b/libraries/ESP_Async_WebServer/examples/EndBegin/EndBegin.ino new file mode 100644 index 0000000..acfc6ff --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/EndBegin/EndBegin.ino @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// https://github.com/ESP32Async/ESPAsyncWebServer/discussions/23 +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello, world"); + }); + + server.begin(); + Serial.println("begin() - run: curl -v http://192.168.4.1/ => should succeed"); + delay(10000); + + Serial.println("end()"); + server.end(); + server.begin(); + Serial.println("begin() - run: curl -v http://192.168.4.1/ => should succeed"); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/Filters/Filters.ino b/libraries/ESP_Async_WebServer/examples/Filters/Filters.ino new file mode 100644 index 0000000..519478c --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/Filters/Filters.ino @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Shows how to use setFilter to route requests to different handlers based on WiFi mode +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif +#include "ESPAsyncWebServer.h" + +static DNSServer dnsServer; +static AsyncWebServer server(80); + +class CaptiveRequestHandler : public AsyncWebHandler { +public: + bool canHandle(__unused AsyncWebServerRequest *request) const override { + return true; + } + + void handleRequest(AsyncWebServerRequest *request) override { + AsyncResponseStream *response = request->beginResponseStream("text/html"); + response->print("Captive Portal"); + response->print("

This is out captive portal front page.

"); + response->printf("

You were trying to reach: http://%s%s

", request->host().c_str(), request->url().c_str()); +#ifndef CONFIG_IDF_TARGET_ESP32H2 + response->printf("

Try opening this link instead

", WiFi.softAPIP().toString().c_str()); +#endif + response->print(""); + request->send(response); + } +}; + +bool hit1 = false; +bool hit2 = false; + +void setup() { + Serial.begin(115200); + + server + .on( + "/", HTTP_GET, + [](AsyncWebServerRequest *request) { + Serial.println("Captive portal request..."); +#ifndef CONFIG_IDF_TARGET_ESP32H2 + Serial.println("WiFi.localIP(): " + WiFi.localIP().toString()); +#endif + Serial.println("request->client()->localIP(): " + request->client()->localIP().toString()); +#if ESP_IDF_VERSION_MAJOR >= 5 +#ifndef CONFIG_IDF_TARGET_ESP32H2 + Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type())); +#endif + Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type())); +#endif +#ifndef CONFIG_IDF_TARGET_ESP32H2 + Serial.println(WiFi.localIP() == request->client()->localIP() ? "should be: ON_STA_FILTER" : "should be: ON_AP_FILTER"); + Serial.println(WiFi.localIP() == request->client()->localIP()); + Serial.println(WiFi.localIP().toString() == request->client()->localIP().toString()); +#endif + request->send(200, "text/plain", "This is the captive portal"); + hit1 = true; + } + ) + .setFilter(ON_AP_FILTER); + + server + .on( + "/", HTTP_GET, + [](AsyncWebServerRequest *request) { + Serial.println("Website request..."); +#ifndef CONFIG_IDF_TARGET_ESP32H2 + Serial.println("WiFi.localIP(): " + WiFi.localIP().toString()); +#endif + Serial.println("request->client()->localIP(): " + request->client()->localIP().toString()); +#if ESP_IDF_VERSION_MAJOR >= 5 +#ifndef CONFIG_IDF_TARGET_ESP32H2 + Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type())); +#endif + Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type())); +#endif +#ifndef CONFIG_IDF_TARGET_ESP32H2 + Serial.println(WiFi.localIP() == request->client()->localIP() ? "should be: ON_STA_FILTER" : "should be: ON_AP_FILTER"); + Serial.println(WiFi.localIP() == request->client()->localIP()); + Serial.println(WiFi.localIP().toString() == request->client()->localIP().toString()); +#endif + request->send(200, "text/plain", "This is the website"); + hit2 = true; + } + ) + .setFilter(ON_STA_FILTER); + + // assert(WiFi.softAP("esp-captive-portal")); + // dnsServer.start(53, "*", WiFi.softAPIP()); + // server.begin(); + // Serial.println("Captive portal started!"); + + // while (!hit1) { + // dnsServer.processNextRequest(); + // yield(); + // } + // delay(1000); // Wait for the client to process the response + + // Serial.println("Captive portal opened, stopping it and connecting to WiFi..."); + // dnsServer.stop(); + // WiFi.softAPdisconnect(); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.persistent(false); + WiFi.begin("IoT"); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + } + Serial.println("Connected to WiFi with IP address: " + WiFi.localIP().toString()); +#endif + + server.begin(); + + // while (!hit2) { + // delay(10); + // } + // delay(1000); // Wait for the client to process the response + // ESP.restart(); +} + +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/FlashResponse/FlashResponse.ino b/libraries/ESP_Async_WebServer/examples/FlashResponse/FlashResponse.ino new file mode 100644 index 0000000..6948cd2 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/FlashResponse/FlashResponse.ino @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Shows how to serve a large HTML page from flash memory without copying it to heap in a temporary buffer +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +static const char *htmlContent PROGMEM = R"( + + + + Sample HTML + + +

Hello, World!

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+ + +)"; + +static const size_t htmlContentLength = strlen_P(htmlContent); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // curl -v http://192.168.4.1/ + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + // need to cast to uint8_t* + // if you do not, the const char* will be copied in a temporary String buffer + request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength); + }); + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/HeaderManipulation/HeaderManipulation.ino b/libraries/ESP_Async_WebServer/examples/HeaderManipulation/HeaderManipulation.ino new file mode 100644 index 0000000..4fe34dc --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/HeaderManipulation/HeaderManipulation.ino @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Show how to manipulate headers in the request / response +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +// request logger +static AsyncLoggingMiddleware requestLogger; + +// filter out specific headers from the incoming request +static AsyncHeaderFilterMiddleware headerFilter; + +// remove all headers from the incoming request except the ones provided in the constructor +AsyncHeaderFreeMiddleware headerFree; + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + requestLogger.setEnabled(true); + requestLogger.setOutput(Serial); + + headerFilter.filter("X-Remove-Me"); + + headerFree.keep("X-Keep-Me"); + headerFree.keep("host"); + + server.addMiddlewares({&requestLogger, &headerFilter}); + + // x-remove-me header will be removed + // + // curl -v -H "X-Header: Foo" -H "x-remove-me: value" http://192.168.4.1/remove + // + server.on("/remove", HTTP_GET, [](AsyncWebServerRequest *request) { + // print all headers + for (size_t i = 0; i < request->headers(); i++) { + const AsyncWebHeader *h = request->getHeader(i); + Serial.printf("Header[%s]: %s\n", h->name().c_str(), h->value().c_str()); + } + request->send(200, "text/plain", "Hello, world!"); + }); + + // Only headers x-keep-me and host will be kept + // + // curl -v -H "x-keep-me: value" -H "x-remove-me: value" http://192.168.4.1/keep + // + server + .on( + "/keep", HTTP_GET, + [](AsyncWebServerRequest *request) { + // print all headers + for (size_t i = 0; i < request->headers(); i++) { + const AsyncWebHeader *h = request->getHeader(i); + Serial.printf("Header[%s]: %s\n", h->name().c_str(), h->value().c_str()); + } + request->send(200, "text/plain", "Hello, world!"); + } + ) + .addMiddleware(&headerFree); + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/Headers/Headers.ino b/libraries/ESP_Async_WebServer/examples/Headers/Headers.ino new file mode 100644 index 0000000..e07c515 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/Headers/Headers.ino @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Query and send headers +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // + // curl -v http://192.168.4.1 + // + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + //List all collected headers + int headers = request->headers(); + int i; + for (i = 0; i < headers; i++) { + const AsyncWebHeader *h = request->getHeader(i); + Serial.printf("HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str()); + } + + AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Hello World!"); + + //Add header to the response + response->addHeader("Server", "ESP Async Web Server"); + + //Add multiple headers with the same name + response->addHeader("Set-Cookie", "sessionId=38afes7a8", false); + response->addHeader("Set-Cookie", "id=a3fWa; Max-Age=2592000", false); + response->addHeader("Set-Cookie", "qwerty=219ffwef9w0f; Domain=example.com", false); + + //Remove specific header + response->removeHeader("Set-Cookie", "sessionId=38afes7a8"); + + //Remove all headers with the same name + response->removeHeader("Set-Cookie"); + + request->send(response); + }); + + server.begin(); +} + +void loop() { + //Sleep in the loop task to not keep the CPU busy + delay(1000); +} diff --git a/libraries/ESP_Async_WebServer/examples/Json/Json.ino b/libraries/ESP_Async_WebServer/examples/Json/Json.ino new file mode 100644 index 0000000..0ea8892 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/Json/Json.ino @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Shows how to send and receive Json data +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +#if __has_include("ArduinoJson.h") +#include +#include +#include +#endif + +static AsyncWebServer server(80); + +#if __has_include("ArduinoJson.h") +static AsyncCallbackJsonWebHandler *handler = new AsyncCallbackJsonWebHandler("/json2"); +#endif + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + +#if __has_include("ArduinoJson.h") + // + // sends JSON using AsyncJsonResponse + // + // curl -v http://192.168.4.1/json1 + // + server.on("/json1", HTTP_GET, [](AsyncWebServerRequest *request) { + AsyncJsonResponse *response = new AsyncJsonResponse(); + JsonObject root = response->getRoot().to(); + root["hello"] = "world"; + response->setLength(); + request->send(response); + }); + + // Send JSON using AsyncResponseStream + // + // curl -v http://192.168.4.1/json2 + // + server.on("/json2", HTTP_GET, [](AsyncWebServerRequest *request) { + AsyncResponseStream *response = request->beginResponseStream("application/json"); + JsonDocument doc; + JsonObject root = doc.to(); + root["foo"] = "bar"; + serializeJson(root, *response); + request->send(response); + }); + + // curl -v -X POST -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/json2 + // curl -v -X PUT -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/json2 + handler->setMethod(HTTP_POST | HTTP_PUT); + handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) { + serializeJson(json, Serial); + AsyncJsonResponse *response = new AsyncJsonResponse(); + JsonObject root = response->getRoot().to(); + root["hello"] = json.as()["name"]; + response->setLength(); + request->send(response); + }); + + server.addHandler(handler); +#endif + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/Logging/Logging.ino b/libraries/ESP_Async_WebServer/examples/Logging/Logging.ino new file mode 100644 index 0000000..6485185 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/Logging/Logging.ino @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Show how to log the incoming request and response as a curl-like syntax +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); +static AsyncLoggingMiddleware requestLogger; + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + requestLogger.setEnabled(true); + requestLogger.setOutput(Serial); + + server.addMiddleware(&requestLogger); + + // curl -v -H "X-Header:Foo" http://192.168.4.1/ + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello, world!"); + }); + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/MessagePack/MessagePack.ino b/libraries/ESP_Async_WebServer/examples/MessagePack/MessagePack.ino new file mode 100644 index 0000000..4fea247 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/MessagePack/MessagePack.ino @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Shows how to send and receive Message Pack data +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +#if __has_include("ArduinoJson.h") +#include +#include +#include +#endif + +static AsyncWebServer server(80); + +#if __has_include("ArduinoJson.h") +static AsyncCallbackMessagePackWebHandler *handler = new AsyncCallbackMessagePackWebHandler("/msgpack2"); +#endif + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + +#if __has_include("ArduinoJson.h") + // + // sends MessagePack using AsyncMessagePackResponse + // + // curl -v http://192.168.4.1/msgpack1 + // + server.on("/msgpack1", HTTP_GET, [](AsyncWebServerRequest *request) { + AsyncMessagePackResponse *response = new AsyncMessagePackResponse(); + JsonObject root = response->getRoot().to(); + root["hello"] = "world"; + response->setLength(); + request->send(response); + }); + + // Send MessagePack using AsyncResponseStream + // + // curl -v http://192.168.4.1/msgpack2 + // + server.on("/msgpack2", HTTP_GET, [](AsyncWebServerRequest *request) { + AsyncResponseStream *response = request->beginResponseStream("application/msgpack"); + JsonDocument doc; + JsonObject root = doc.to(); + root["foo"] = "bar"; + serializeMsgPack(root, *response); + request->send(response); + }); + + handler->setMethod(HTTP_POST | HTTP_PUT); + handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) { + serializeJson(json, Serial); + AsyncMessagePackResponse *response = new AsyncMessagePackResponse(); + JsonObject root = response->getRoot().to(); + root["hello"] = json.as()["name"]; + response->setLength(); + request->send(response); + }); + + server.addHandler(handler); +#endif + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/Middleware/Middleware.ino b/libraries/ESP_Async_WebServer/examples/Middleware/Middleware.ino new file mode 100644 index 0000000..c52f949 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/Middleware/Middleware.ino @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Show how to sue Middleware +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +// New middleware classes can be created! +class MyMiddleware : public AsyncMiddleware { +public: + void run(AsyncWebServerRequest *request, ArMiddlewareNext next) override { + Serial.printf("Before handler: %s %s\n", request->methodToString(), request->url().c_str()); + next(); // continue middleware chain + Serial.printf("After handler: response code=%d\n", request->getResponse()->code()); + } +}; + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // add a global middleware to the server + server.addMiddleware(new MyMiddleware()); + + // Test with: + // + // - curl -v http://192.168.4.1/ => 200 OK + // - curl -v http://192.168.4.1/?user=anon => 403 Forbidden + // - curl -v http://192.168.4.1/?user=foo => 200 OK + // - curl -v http://192.168.4.1/?user=error => 400 ERROR + // + AsyncCallbackWebHandler &handler = server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + Serial.printf("In Handler: %s %s\n", request->methodToString(), request->url().c_str()); + request->send(200, "text/plain", "Hello, world!"); + }); + + // add a middleware to this handler only to send 403 if the user is anon + handler.addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) { + Serial.println("Checking user=anon"); + if (request->hasParam("user") && request->getParam("user")->value() == "anon") { + request->send(403, "text/plain", "Forbidden"); + } else { + next(); + } + }); + + // add a middleware to this handler that will replace the previously created response by another one + handler.addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) { + next(); + Serial.println("Checking user=error"); + if (request->hasParam("user") && request->getParam("user")->value() == "error") { + request->send(400, "text/plain", "ERROR"); + } + }); + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/Params/Params.ino b/libraries/ESP_Async_WebServer/examples/Params/Params.ino new file mode 100644 index 0000000..2c438a5 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/Params/Params.ino @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Query parameters and body parameters +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +static const char *htmlContent PROGMEM = R"( + + + + POST Request with Multiple Parameters + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + +)"; + +static const size_t htmlContentLength = strlen_P(htmlContent); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // Get query parameters + // + // curl -v http://192.168.4.1/?who=Bob + // + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + if (request->hasParam("who")) { + Serial.printf("Who? %s\n", request->getParam("who")->value().c_str()); + } + + request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength); + }); + + // Get form body parameters + // + // curl -v -H "Content-Type: application/x-www-form-urlencoded" -d "who=Carl" -d "param=value" http://192.168.4.1/ + // + server.on("/", HTTP_POST, [](AsyncWebServerRequest *request) { + // display params + size_t count = request->params(); + for (size_t i = 0; i < count; i++) { + const AsyncWebParameter *p = request->getParam(i); + Serial.printf("PARAM[%u]: %s = %s\n", i, p->name().c_str(), p->value().c_str()); + } + + // get who param + String who; + if (request->hasParam("who", true)) { + who = request->getParam("who", true)->value(); + } else { + who = "No message sent"; + } + request->send(200, "text/plain", "Hello " + who + "!"); + }); + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/PartitionDownloader/PartitionDownloader.ino b/libraries/ESP_Async_WebServer/examples/PartitionDownloader/PartitionDownloader.ino new file mode 100644 index 0000000..3c76366 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/PartitionDownloader/PartitionDownloader.ino @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// - Download ESP32 partition by name and/or type and/or subtype +// - Support encrypted and non-encrypted partitions +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include +#include + +#ifndef ESP32 +// this example is only for the ESP32 +void setup() {} +void loop() {} +#else + +#include + +static AsyncWebServer server(80); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + LittleFS.begin(true); + + // To upload the FS partition, run: + // > pio run -e arduino-3 -t buildfs + // > pio run -e arduino-3 -t uploadfs + // + // Examples: + // + // - Download the partition named "spiffs": http://192.168.4.1/partition?label=spiffs + // - Download the partition named "spiffs" with type "data": http://192.168.4.1/partition?label=spiffs&type=1 + // - Download the partition named "spiffs" with type "data" and subtype "spiffs": http://192.168.4.1/partition?label=spiffs&type=1&subtype=130 + // - Download the partition with subtype "nvs": http://192.168.4.1/partition?type=1&subtype=2 + // + // "type" and "subtype" IDs can be found in esp_partition.h header file. + // + // Add "&raw=false" parameter to download the partition unencrypted (for encrypted partitions). + // By default, the raw partition is downloaded, so if a partition is encrypted, the encrypted data will be downloaded. + // + // To browse a downloaded LittleFS partition, you can use https://tniessen.github.io/littlefs-disk-img-viewer/ (block size is 4096) + // + server.on("/partition", HTTP_GET, [](AsyncWebServerRequest *request) { + const AsyncWebParameter *pLabel = request->getParam("label"); + const AsyncWebParameter *pType = request->getParam("type"); + const AsyncWebParameter *pSubtype = request->getParam("subtype"); + const AsyncWebParameter *pRaw = request->getParam("raw"); + + if (!pLabel && !pType && !pSubtype) { + request->send(400, "text/plain", "Bad request: missing parameter"); + return; + } + + esp_partition_type_t type = ESP_PARTITION_TYPE_ANY; + esp_partition_subtype_t subtype = ESP_PARTITION_SUBTYPE_ANY; + const char *label = nullptr; + bool raw = true; + + if (pLabel) { + label = pLabel->value().c_str(); + } + + if (pType) { + type = (esp_partition_type_t)pType->value().toInt(); + } + + if (pSubtype) { + subtype = (esp_partition_subtype_t)pSubtype->value().toInt(); + } + + if (pRaw && pRaw->value() == "false") { + raw = false; + } + + const esp_partition_t *partition = esp_partition_find_first(type, subtype, label); + + if (!partition) { + request->send(404, "text/plain", "Partition not found"); + return; + } + + AsyncWebServerResponse *response = + request->beginChunkedResponse("application/octet-stream", [partition, raw](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + const size_t remaining = partition->size - index; + if (!remaining) { + return 0; + } + const size_t len = std::min(maxLen, remaining); + if (raw && esp_partition_read_raw(partition, index, buffer, len) == ESP_OK) { + return len; + } + if (!raw && esp_partition_read(partition, index, buffer, len) == ESP_OK) { + return len; + } + return 0; + }); + + response->addHeader("Content-Disposition", "attachment; filename=" + String(partition->label) + ".bin"); + response->setContentLength(partition->size); + + request->send(response); + }); + + server.begin(); +} + +void loop() { + delay(100); +} + +#endif diff --git a/libraries/ESP_Async_WebServer/examples/PerfTests/PerfTests.ino b/libraries/ESP_Async_WebServer/examples/PerfTests/PerfTests.ino new file mode 100644 index 0000000..6467d2c --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/PerfTests/PerfTests.ino @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Perf tests +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static const char *htmlContent PROGMEM = R"( + + + + Sample HTML + + +

Hello, World!

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+ + +)"; + +static const size_t htmlContentLength = strlen_P(htmlContent); +static constexpr char characters[] = "0123456789ABCDEF"; +static size_t charactersIndex = 0; + +static AsyncWebServer server(80); +static AsyncEventSource events("/events"); + +static volatile size_t requests = 0; + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // Pauses in the request parsing phase + // + // autocannon -c 32 -w 32 -a 96 -t 30 --renderStatusCodes -m POST -H "Content-Type: application/json" -b '{"foo": "bar"}' http://192.168.4.1/delay + // + // curl -v -X POST -H "Content-Type: application/json" -d '{"game": "test"}' http://192.168.4.1/delay + // + server.onNotFound([](AsyncWebServerRequest *request) { + requests = requests + 1; + if (request->url() == "/delay") { + request->send(200, "application/json", "{\"status\":\"OK\"}"); + } else { + request->send(404, "text/plain", "Not found"); + } + }); + server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { + if (request->url() == "/delay") { + delay(3000); + } + }); + + // HTTP endpoint + // + // > brew install autocannon + // > autocannon -c 10 -w 10 -d 20 http://192.168.4.1 + // > autocannon -c 16 -w 16 -d 20 http://192.168.4.1 + // + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + // need to cast to uint8_t* + // if you do not, the const char* will be copied in a temporary String buffer + requests = requests + 1; + request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength); + }); + + // IMPORTANT - DO NOT WRITE SUCH CODE IN PRODUCTON ! + // + // This example simulates the slowdown that can happen when: + // - downloading a huge file from sdcard + // - doing some file listing on SDCard because it is horribly slow to get a file listing with file stats on SDCard. + // So in both cases, ESP would deadlock or TWDT would trigger. + // + // This example simulats that by slowing down the chunk callback: + // - d=2000 is the delay in ms in the callback + // - l=10000 is the length of the response + // + // time curl -N -v -G -d 'd=2000' -d 'l=10000' http://192.168.4.1/slow.html --output - + // + server.on("/slow.html", HTTP_GET, [](AsyncWebServerRequest *request) { + requests = requests + 1; + uint32_t d = request->getParam("d")->value().toInt(); + uint32_t l = request->getParam("l")->value().toInt(); + Serial.printf("d = %" PRIu32 ", l = %" PRIu32 "\n", d, l); + AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [d, l](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + Serial.printf("%u\n", index); + // finished ? + if (index >= l) { + return 0; + } + + // slow down the task to simulate some heavy processing, like SD card reading + delay(d); + + memset(buffer, characters[charactersIndex], 256); + charactersIndex = (charactersIndex + 1) % sizeof(characters); + return 256; + }); + + request->send(response); + }); + + // SSS endpoint + // + // launch 16 concurrent workers for 30 seconds + // > for i in {1..10}; do ( count=$(gtimeout 30 curl -s -N -H "Accept: text/event-stream" http://192.168.4.1/events 2>&1 | grep -c "^data:"); echo "Total: $count events, $(echo "$count / 4" | bc -l) events / second" ) & done; + // > for i in {1..16}; do ( count=$(gtimeout 30 curl -s -N -H "Accept: text/event-stream" http://192.168.4.1/events 2>&1 | grep -c "^data:"); echo "Total: $count events, $(echo "$count / 4" | bc -l) events / second" ) & done; + // + // With AsyncTCP, with 16 workers: a lot of "Event message queue overflow: discard message", no crash + // + // Total: 1711 events, 427.75 events / second + // Total: 1711 events, 427.75 events / second + // Total: 1626 events, 406.50 events / second + // Total: 1562 events, 390.50 events / second + // Total: 1706 events, 426.50 events / second + // Total: 1659 events, 414.75 events / second + // Total: 1624 events, 406.00 events / second + // Total: 1706 events, 426.50 events / second + // Total: 1487 events, 371.75 events / second + // Total: 1573 events, 393.25 events / second + // Total: 1569 events, 392.25 events / second + // Total: 1559 events, 389.75 events / second + // Total: 1560 events, 390.00 events / second + // Total: 1562 events, 390.50 events / second + // Total: 1626 events, 406.50 events / second + // + // With AsyncTCP, with 10 workers: + // + // Total: 2038 events, 509.50 events / second + // Total: 2120 events, 530.00 events / second + // Total: 2119 events, 529.75 events / second + // Total: 2038 events, 509.50 events / second + // Total: 2037 events, 509.25 events / second + // Total: 2119 events, 529.75 events / second + // Total: 2119 events, 529.75 events / second + // Total: 2120 events, 530.00 events / second + // Total: 2038 events, 509.50 events / second + // Total: 2038 events, 509.50 events / second + // + // With AsyncTCPSock, with 16 workers: ESP32 CRASH !!! + // + // With AsyncTCPSock, with 10 workers: + // + // Total: 1242 events, 310.50 events / second + // Total: 1242 events, 310.50 events / second + // Total: 1242 events, 310.50 events / second + // Total: 1242 events, 310.50 events / second + // Total: 1181 events, 295.25 events / second + // Total: 1182 events, 295.50 events / second + // Total: 1240 events, 310.00 events / second + // Total: 1181 events, 295.25 events / second + // Total: 1181 events, 295.25 events / second + // Total: 1183 events, 295.75 events / second + // + server.addHandler(&events); + + server.begin(); +} + +static uint32_t lastSSE = 0; +static uint32_t deltaSSE = 10; + +static uint32_t lastHeap = 0; + +void loop() { + uint32_t now = millis(); + if (now - lastSSE >= deltaSSE) { + events.send(String("ping-") + now, "heartbeat", now); + lastSSE = millis(); + } + +#ifdef ESP32 + if (now - lastHeap >= 2000) { + Serial.printf("Uptime: %3lu s, requests: %3u, Free heap: %" PRIu32 "\n", millis() / 1000, requests, ESP.getFreeHeap()); + lastHeap = now; + } +#endif +} diff --git a/libraries/ESP_Async_WebServer/examples/RateLimit/RateLimit.ino b/libraries/ESP_Async_WebServer/examples/RateLimit/RateLimit.ino new file mode 100644 index 0000000..89d6090 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/RateLimit/RateLimit.ino @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Show how to rate limit the server or some endpoints +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); +static AsyncRateLimitMiddleware rateLimit; + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // maximum 5 requests per 10 seconds + rateLimit.setMaxRequests(5); + rateLimit.setWindowSize(10); + + // run quickly several times: + // + // curl -v http://192.168.4.1/ + // + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello, world!"); + }); + + // run quickly several times: + // + // curl -v http://192.168.4.1/rate-limited + // + server + .on( + "/rate-limited", HTTP_GET, + [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello, world!"); + } + ) + .addMiddleware(&rateLimit); // only rate limit this endpoint, but could be applied globally to the server + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/Redirect/Redirect.ino b/libraries/ESP_Async_WebServer/examples/Redirect/Redirect.ino new file mode 100644 index 0000000..ce1b9fb --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/Redirect/Redirect.ino @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Shows how to redirect +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // curl -v http://192.168.4.1/ + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + request->redirect("/index.txt"); + }); + + // curl -v http://192.168.4.1/index.txt + server.on("/index.txt", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello, world!"); + }); + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/RequestContinuation/RequestContinuation.ino b/libraries/ESP_Async_WebServer/examples/RequestContinuation/RequestContinuation.ino new file mode 100644 index 0000000..0584cf1 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/RequestContinuation/RequestContinuation.ino @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Shows how to use request continuation to pause a request for a long processing task, and be able to resume it later. +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include +#include +#include + +static AsyncWebServer server(80); + +// request handler that is saved from the paused request to communicate with Serial +static String message; +static AsyncWebServerRequestPtr serialRequest; + +// request handler that is saved from the paused request to communicate with GPIO +static uint8_t pin = 35; +static AsyncWebServerRequestPtr gpioRequest; + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // Post a message that will be sent to the Serial console, and pause the request until the user types a key + // + // curl -v -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "question=Name%3F%20" http://192.168.4.1/serial + // + // curl output should show "Answer: [y/n]" as the response + server.on("/serial", HTTP_POST, [](AsyncWebServerRequest *request) { + message = request->getParam("question", true)->value(); + serialRequest = request->pause(); + }); + + // Wait for a GPIO to be high + // + // curl -v http://192.168.4.1/gpio + // + // curl output should show "GPIO is high!" as the response + server.on("/gpio", HTTP_GET, [](AsyncWebServerRequest *request) { + gpioRequest = request->pause(); + }); + + pinMode(pin, INPUT); + + server.begin(); +} + +void loop() { + delay(500); + + // Check for a high voltage on the RX1 pin + if (digitalRead(pin) == HIGH) { + if (auto request = gpioRequest.lock()) { + request->send(200, "text/plain", "GPIO is high!"); + } + } + + // check for an incoming message from the Serial console + if (message.length()) { + Serial.printf("%s", message.c_str()); + // drops buffer + while (Serial.available()) { + Serial.read(); + } + Serial.setTimeout(10000); + String response = Serial.readStringUntil('\n'); // waits for a key to be pressed + Serial.println(); + message = emptyString; + if (auto request = serialRequest.lock()) { + request->send(200, "text/plain", "Answer: " + response); + } + } +} diff --git a/libraries/ESP_Async_WebServer/examples/RequestContinuationComplete/RequestContinuationComplete.ino b/libraries/ESP_Async_WebServer/examples/RequestContinuationComplete/RequestContinuationComplete.ino new file mode 100644 index 0000000..ccd16fd --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/RequestContinuationComplete/RequestContinuationComplete.ino @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Shows how to use request continuation to pause a request for a long processing task, and be able to resume it later. +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +#include +#include +#include + +static AsyncWebServer server(80); + +// =============================================================== +// The code below is used to simulate some long running operations +// =============================================================== + +typedef struct { + size_t id; + AsyncWebServerRequestPtr requestPtr; + uint8_t data; +} LongRunningOperation; + +static std::list> longRunningOperations; +static size_t longRunningOperationsCount = 0; +#ifdef ESP32 +static std::mutex longRunningOperationsMutex; +#endif + +static void startLongRunningOperation(AsyncWebServerRequestPtr &&requestPtr) { +#ifdef ESP32 + std::lock_guard lock(longRunningOperationsMutex); +#endif + + // LongRunningOperation *op = new LongRunningOperation(); + std::unique_ptr op(new LongRunningOperation()); + op->id = ++longRunningOperationsCount; + op->data = 10; + + // you need to hold the AsyncWebServerRequestPtr returned by pause(); + // This object is authorized to leave the scope of the request handler. + op->requestPtr = std::move(requestPtr); + + Serial.printf("[%u] Start long running operation for %" PRIu8 " seconds...\n", op->id, op->data); + longRunningOperations.push_back(std::move(op)); +} + +static bool processLongRunningOperation(LongRunningOperation *op) { + // request was deleted ? + if (op->requestPtr.expired()) { + Serial.printf("[%u] Request was deleted - stopping long running operation\n", op->id); + return true; // operation finished + } + + // processing the operation + Serial.printf("[%u] Long running operation processing... %" PRIu8 " seconds left\n", op->id, op->data); + + // check if we have finished ? + op->data--; + if (op->data) { + // not finished yet + return false; + } + + // Try to get access to the request pointer if it is still exist. + // If there has been a disconnection during that time, the pointer won't be valid anymore + if (auto request = op->requestPtr.lock()) { + Serial.printf("[%u] Long running operation finished! Sending back response...\n", op->id); + request->send(200, "text/plain", String(op->id) + " "); + + } else { + Serial.printf("[%u] Long running operation finished, but request was deleted!\n", op->id); + } + + return true; // operation finished +} + +/// ========================================================== + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // Add a middleware to see how pausing a request affects the middleware chain + server.addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) { + Serial.printf("Middleware chain start\n"); + + // continue to the next middleware, and at the end the request handler + next(); + + // we can check the request pause state after the handler was executed + if (request->isPaused()) { + Serial.printf("Request was paused!\n"); + } + + Serial.printf("Middleware chain ends\n"); + }); + + // HOW TO RUN THIS EXAMPLE: + // + // 1. Open several terminals to trigger some requests concurrently that will be paused with: + // > time curl -v http://192.168.4.1/ + // + // 2. Look at the output of the Serial console to see how the middleware chain is executed + // and to see the long running operations being processed and resume the requests. + // + // 3. You can try close your curl command to cancel the request and check that the request is deleted. + // Note: in case the network is disconnected, the request will be deleted. + // + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + // Print a message in case the request is disconnected (network disconnection, client close, etc.) + request->onDisconnect([]() { + Serial.printf("Request was disconnected!\n"); + }); + + // Instruct ESPAsyncWebServer to pause the request and get a AsyncWebServerRequestPtr to be able to access the request later. + // The AsyncWebServerRequestPtr is the ONLY object authorized to leave the scope of the request handler. + // The Middleware chain will continue to run until the end after this handler exit, but the request will be paused and will not + // be sent to the client until send() is called later. + Serial.printf("Pausing request...\n"); + AsyncWebServerRequestPtr requestPtr = request->pause(); + + // start our long operation... + startLongRunningOperation(std::move(requestPtr)); + }); + + server.begin(); +} + +static uint32_t lastTime = 0; + +void loop() { + if (millis() - lastTime >= 1000) { + +#ifdef ESP32 + Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap()); + std::lock_guard lock(longRunningOperationsMutex); +#endif + + // process all long running operations + longRunningOperations.remove_if([](const std::unique_ptr &op) { + return processLongRunningOperation(op.get()); + }); + + lastTime = millis(); + } +} diff --git a/libraries/ESP_Async_WebServer/examples/ResumableDownload/ResumableDownload.ino b/libraries/ESP_Async_WebServer/examples/ResumableDownload/ResumableDownload.ino new file mode 100644 index 0000000..373ca24 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/ResumableDownload/ResumableDownload.ino @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Make sure resumable downloads can be implemented (HEAD request / response and Range header) +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + /* + ❯ curl -I -X HEAD http://192.168.4.1/download + HTTP/1.1 200 OK + Content-Length: 1024 + Content-Type: application/octet-stream + Connection: close + Accept-Ranges: bytes + */ + // Ref: https://github.com/mathieucarbou/ESPAsyncWebServer/pull/80 + server.on("/download", HTTP_HEAD | HTTP_GET, [](AsyncWebServerRequest *request) { + if (request->method() == HTTP_HEAD) { + AsyncWebServerResponse *response = request->beginResponse(200, "application/octet-stream"); + response->addHeader(asyncsrv::T_Accept_Ranges, "bytes"); + response->addHeader(asyncsrv::T_Content_Length, 10); + response->setContentLength(1024); // make sure we can overrides previously set content length + response->addHeader(asyncsrv::T_Content_Type, "foo"); + response->setContentType("application/octet-stream"); // make sure we can overrides previously set content type + // ... + request->send(response); + } else { + // ... + } + }); + + server.begin(); +} + +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/Rewrite/Rewrite.ino b/libraries/ESP_Async_WebServer/examples/Rewrite/Rewrite.ino new file mode 100644 index 0000000..6981b11 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/Rewrite/Rewrite.ino @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Shows how to rewrite URLs +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // curl -v http://192.168.4.1/index.txt + server.on("/index.txt", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello, world!"); + }); + + // curl -v http://192.168.4.1/index.txt + server.on("/index.html", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/html", "

Hello, world!

"); + }); + + // curl -v http://192.168.4.1/ + server.rewrite("/", "/index.html"); + server.rewrite("/index.txt", "/index.html"); // will hide the .txt file + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/ServerSentEvents/ServerSentEvents.ino b/libraries/ESP_Async_WebServer/examples/ServerSentEvents/ServerSentEvents.ino new file mode 100644 index 0000000..91e2c1d --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/ServerSentEvents/ServerSentEvents.ino @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// SSE example +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static const char *htmlContent PROGMEM = R"( + + + + Server-Sent Events + + + +

Open your browser console!

+ + +)"; + +static const size_t htmlContentLength = strlen_P(htmlContent); + +static AsyncWebServer server(80); +static AsyncEventSource events("/events"); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // curl -v http://192.168.4.1/ + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + // need to cast to uint8_t* + // if you do not, the const char* will be copied in a temporary String buffer + request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength); + }); + + events.onConnect([](AsyncEventSourceClient *client) { + Serial.printf("SSE Client connected! ID: %" PRIu32 "\n", client->lastId()); + client->send("hello!", NULL, millis(), 1000); + }); + + events.onDisconnect([](AsyncEventSourceClient *client) { + Serial.printf("SSE Client disconnected! ID: %" PRIu32 "\n", client->lastId()); + }); + + server.addHandler(&events); + + server.begin(); +} + +static uint32_t lastSSE = 0; +static uint32_t deltaSSE = 3000; + +static uint32_t lastHeap = 0; + +void loop() { + uint32_t now = millis(); + if (now - lastSSE >= deltaSSE) { + events.send(String("ping-") + now, "heartbeat", now); + lastSSE = millis(); + } + +#ifdef ESP32 + if (now - lastHeap >= 2000) { + Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap()); + lastHeap = now; + } +#endif +} diff --git a/libraries/ESP_Async_WebServer/examples/ServerSentEvents_PR156/ServerSentEvents_PR156.ino b/libraries/ESP_Async_WebServer/examples/ServerSentEvents_PR156/ServerSentEvents_PR156.ino new file mode 100644 index 0000000..928c9ad --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/ServerSentEvents_PR156/ServerSentEvents_PR156.ino @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// SSE example +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static const char *htmlContent PROGMEM = R"( + + + + Server-Sent Events + + + +

Open your browser console!

+ + +)"; + +static const size_t htmlContentLength = strlen_P(htmlContent); + +static AsyncWebServer server(80); +static AsyncEventSource events("/events"); + +static volatile size_t connectionCount = 0; +static volatile uint32_t timestampConnected = 0; +static constexpr uint32_t timeoutClose = 15000; + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // curl -v http://192.168.4.1/ + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + // need to cast to uint8_t* + // if you do not, the const char* will be copied in a temporary String buffer + request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength); + }); + + events.onConnect([](AsyncEventSourceClient *client) { + /** + * @brief: Purpose for a test case: count() function + * Task watchdog shall be triggered due to a self-deadlock by mutex handling of the AsyncEventSource. + * + * E (61642) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time: + * E (61642) task_wdt: - async_tcp (CPU 0/1) + * + * Resolve: using recursive_mutex insteads of mutex. + */ + connectionCount = events.count(); + + timestampConnected = millis(); + Serial.printf("SSE Client connected! ID: %" PRIu32 "\n", client->lastId()); + client->send("hello!", NULL, millis(), 1000); + Serial.printf("Number of connected clients: %u\n", connectionCount); + }); + + events.onDisconnect([](AsyncEventSourceClient *client) { + connectionCount = events.count(); + Serial.printf("SSE Client disconnected! ID: %" PRIu32 "\n", client->lastId()); + Serial.printf("Number of connected clients: %u\n", connectionCount); + }); + + server.addHandler(&events); + + server.begin(); +} + +static constexpr uint32_t deltaSSE = 3000; +static uint32_t lastSSE = 0; +static uint32_t lastHeap = 0; + +void loop() { + uint32_t now = millis(); + if (connectionCount > 0) { + if (now - lastSSE >= deltaSSE) { + events.send(String("ping-") + now, "heartbeat", now); + lastSSE = millis(); + } + + /** + * @brief: Purpose for a test case: close() function + * Task watchdog shall be triggered due to a self-deadlock by mutex handling of the AsyncEventSource. + * + * E (61642) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time: + * E (61642) task_wdt: - async_tcp (CPU 0/1) + * + * Resolve: using recursive_mutex insteads of mutex. + */ + if (now - timestampConnected >= timeoutClose) { + Serial.printf("SSE Clients close\n"); + events.close(); + } + } + +#ifdef ESP32 + if (now - lastHeap >= 2000) { + Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap()); + lastHeap = now; + } +#endif +} diff --git a/libraries/ESP_Async_WebServer/examples/ServerState/ServerState.ino b/libraries/ESP_Async_WebServer/examples/ServerState/ServerState.ino new file mode 100644 index 0000000..8501758 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/ServerState/ServerState.ino @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Server state example +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server1(80); +static AsyncWebServer server2(80); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // server state returns one of the tcp_state enum values: + // enum tcp_state { + // CLOSED = 0, + // LISTEN = 1, + // SYN_SENT = 2, + // SYN_RCVD = 3, + // ESTABLISHED = 4, + // FIN_WAIT_1 = 5, + // FIN_WAIT_2 = 6, + // CLOSE_WAIT = 7, + // CLOSING = 8, + // LAST_ACK = 9, + // TIME_WAIT = 10 + // }; + + assert(server1.state() == tcp_state::CLOSED); + assert(server2.state() == tcp_state::CLOSED); + + server1.begin(); + + assert(server1.state() == tcp_state::LISTEN); + assert(server2.state() == tcp_state::CLOSED); + + server2.begin(); + + assert(server1.state() == tcp_state::LISTEN); + assert(server2.state() == tcp_state::CLOSED); + + Serial.println("Done!"); +} + +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/SkipServerMiddleware/SkipServerMiddleware.ino b/libraries/ESP_Async_WebServer/examples/SkipServerMiddleware/SkipServerMiddleware.ino new file mode 100644 index 0000000..d232c71 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/SkipServerMiddleware/SkipServerMiddleware.ino @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Authentication and authorization middlewares +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +static AsyncAuthenticationMiddleware basicAuth; +static AsyncLoggingMiddleware logging; + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // basic authentication + basicAuth.setUsername("admin"); + basicAuth.setPassword("admin"); + basicAuth.setRealm("MyApp"); + basicAuth.setAuthFailureMessage("Authentication failed"); + basicAuth.setAuthType(AsyncAuthType::AUTH_BASIC); + basicAuth.generateHash(); // precompute hash (optional but recommended) + + // logging middleware + logging.setEnabled(true); + logging.setOutput(Serial); + + // we apply auth middleware to the server globally + server.addMiddleware(&basicAuth); + + // protected endpoint: requires basic authentication + // curl -v -u admin:admin http://192.168.4.1/ + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello, world!"); + }); + + // we skip all global middleware from the catchall handler + server.catchAllHandler().skipServerMiddlewares(); + // we apply a specific middleware to the catchall handler only to log requests without a handler defined + server.catchAllHandler().addMiddleware(&logging); + + // standard 404 handler: will display the request in the console i na curl-like style + // curl -v -H "Foo: Bar" http://192.168.4.1/foo + server.onNotFound([](AsyncWebServerRequest *request) { + request->send(404, "text/plain", "Not found"); + }); + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/SlowChunkResponse/SlowChunkResponse.ino b/libraries/ESP_Async_WebServer/examples/SlowChunkResponse/SlowChunkResponse.ino new file mode 100644 index 0000000..bbf70b6 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/SlowChunkResponse/SlowChunkResponse.ino @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Simulate a slow response in a chunk response (like file download from SD Card) +// poll events will be throttled. +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +static const char *htmlContent PROGMEM = R"( + + + + Sample HTML + + +

Hello, World!

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+ + +)"; + +static const size_t htmlContentLength = strlen_P(htmlContent); +static constexpr char characters[] = "0123456789ABCDEF"; +static size_t charactersIndex = 0; + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // curl -v http://192.168.4.1/ + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + // need to cast to uint8_t* + // if you do not, the const char* will be copied in a temporary String buffer + request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength); + }); + + // IMPORTANT - DO NOT WRITE SUCH CODE IN PRODUCTON ! + // + // This example simulates the slowdown that can happen when: + // - downloading a huge file from sdcard + // - doing some file listing on SDCard because it is horribly slow to get a file listing with file stats on SDCard. + // So in both cases, ESP would deadlock or TWDT would trigger. + // + // This example simulats that by slowing down the chunk callback: + // - d=2000 is the delay in ms in the callback + // - l=10000 is the length of the response + // + // time curl -N -v -G -d 'd=2000' -d 'l=10000' http://192.168.4.1/slow.html --output - + // + server.on("/slow.html", HTTP_GET, [](AsyncWebServerRequest *request) { + uint32_t d = request->getParam("d")->value().toInt(); + uint32_t l = request->getParam("l")->value().toInt(); + Serial.printf("d = %" PRIu32 ", l = %" PRIu32 "\n", d, l); + AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [d, l](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + Serial.printf("%u\n", index); + // finished ? + if (index >= l) { + return 0; + } + + // slow down the task to simulate some heavy processing, like SD card reading + delay(d); + + memset(buffer, characters[charactersIndex], 256); + charactersIndex = (charactersIndex + 1) % sizeof(characters); + return 256; + }); + + request->send(response); + }); + + server.begin(); +} + +static uint32_t lastHeap = 0; + +void loop() { +#ifdef ESP32 + uint32_t now = millis(); + if (now - lastHeap >= 2000) { + Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap()); + lastHeap = now; + } +#endif +} diff --git a/libraries/ESP_Async_WebServer/examples/StaticFile/StaticFile.ino b/libraries/ESP_Async_WebServer/examples/StaticFile/StaticFile.ino new file mode 100644 index 0000000..331f287 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/StaticFile/StaticFile.ino @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Shows how to serve a static file +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include +#include + +static AsyncWebServer server(80); + +static const char *htmlContent PROGMEM = R"( + + + + Sample HTML + + +

Hello, World!

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+ + +)"; + +static const size_t htmlContentLength = strlen_P(htmlContent); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + +#ifdef ESP32 + LittleFS.begin(true); +#else + LittleFS.begin(); +#endif + + { + File f = LittleFS.open("/index.html", "w"); + assert(f); + f.print(htmlContent); + f.close(); + } + + LittleFS.mkdir("/files"); + + { + File f = LittleFS.open("/files/a.txt", "w"); + assert(f); + f.print("Hello from a.txt"); + f.close(); + } + + { + File f = LittleFS.open("/files/b.txt", "w"); + assert(f); + f.print("Hello from b.txt"); + f.close(); + } + + // curl -v http://192.168.4.1/ + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + request->redirect("/index.html"); + }); + + // curl -v http://192.168.4.1/index.html + server.serveStatic("/index.html", LittleFS, "/index.html"); + + // Example to serve a directory content + // curl -v http://192.168.4.1/base/ => serves a.txt + // curl -v http://192.168.4.1/base/a.txt => serves a.txt + // curl -v http://192.168.4.1/base/b.txt => serves b.txt + server.serveStatic("/base", LittleFS, "/files").setDefaultFile("a.txt"); + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/Templates/Templates.ino b/libraries/ESP_Async_WebServer/examples/Templates/Templates.ino new file mode 100644 index 0000000..edc02c2 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/Templates/Templates.ino @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Shows how to serve a static and dynamic template +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include +#include + +static AsyncWebServer server(80); + +static const char *htmlContent PROGMEM = R"( + + + +

Hello, %USER%

+ + +)"; + +static const size_t htmlContentLength = strlen_P(htmlContent); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + +#ifdef ESP32 + LittleFS.begin(true); +#else + LittleFS.begin(); +#endif + + { + File f = LittleFS.open("/template.html", "w"); + assert(f); + f.print(htmlContent); + f.close(); + } + + // Serve the static template file + // + // curl -v http://192.168.4.1/template.html + server.serveStatic("/template.html", LittleFS, "/template.html"); + + // Serve the static template with a template processor + // + // ServeStatic static is used to serve static output which never changes over time. + // This special endpoints automatically adds caching headers. + // If a template processor is used, it must ensure that the outputted content will always be the same over time and never changes. + // Otherwise, do not use serveStatic. + // Example below: IP never changes. + // + // curl -v http://192.168.4.1/index.html + server.serveStatic("/index.html", LittleFS, "/template.html").setTemplateProcessor([](const String &var) -> String { + if (var == "USER") { + return "Bob"; + } + return emptyString; + }); + + // Serve a template with dynamic content + // + // to serve a template with dynamic content (output changes over time), use normal + // Example below: content changes over tinme do not use serveStatic. + // + // curl -v http://192.168.4.1/dynamic.html + server.on("/dynamic.html", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(LittleFS, "/template.html", "text/html", false, [](const String &var) -> String { + if (var == "USER") { + return String("Bob ") + millis(); + } + return emptyString; + }); + }); + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/Upload/Upload.ino b/libraries/ESP_Async_WebServer/examples/Upload/Upload.ino new file mode 100644 index 0000000..ceac47d --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/Upload/Upload.ino @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Demo text, binary and file upload +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include +#include +#include + +static AsyncWebServer server(80); + +void setup() { + Serial.begin(115200); + + if (!LittleFS.begin()) { + LittleFS.format(); + LittleFS.begin(); + } + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // 1. Generate a Lorem_ipsum.txt file of about 20KB of text + // + // 3. Run: curl -v -F "data=@Lorem_ipsum.txt" http://192.168.4.1/upload/text + // + server.on( + "/upload/text", HTTP_POST, + [](AsyncWebServerRequest *request) { + if (!request->_tempObject) { + return request->send(400, "text/plain", "Nothing uploaded"); + } + StreamString *buffer = reinterpret_cast(request->_tempObject); + Serial.printf("Text uploaded:\n%s\n", buffer->c_str()); + delete buffer; + request->_tempObject = nullptr; + request->send(200, "text/plain", "OK"); + }, + [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + Serial.printf("Upload[%s]: start=%u, len=%u, final=%d\n", filename.c_str(), index, len, final); + + if (!index) { + // first pass + StreamString *buffer = new StreamString(); + size_t size = std::max(4094l, request->header("Content-Length").toInt()); + Serial.printf("Allocating string buffer of %u bytes\n", size); + if (!buffer->reserve(size)) { + delete buffer; + request->abort(); + } + request->_tempObject = buffer; + } + + if (len) { + reinterpret_cast(request->_tempObject)->write(data, len); + } + } + ); + + // 1. Generate a Lorem_ipsum.txt file of about 20KB of text + // + // 3. Run: curl -v -F "data=@Lorem_ipsum.txt" http://192.168.4.1/upload/file + // + server.on( + "/upload/file", HTTP_POST, + [](AsyncWebServerRequest *request) { + if (request->getResponse()) { + // 400 File not available for writing + return; + } + + if (!LittleFS.exists("/my_file.txt")) { + return request->send(400, "text/plain", "Nothing uploaded"); + } + + // sends back the uploaded file + request->send(LittleFS, "/my_file.txt", "text/plain"); + }, + [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + Serial.printf("Upload[%s]: start=%u, len=%u, final=%d\n", filename.c_str(), index, len, final); + + if (!index) { + request->_tempFile = LittleFS.open("/my_file.txt", "w"); + + if (!request->_tempFile) { + request->send(400, "text/plain", "File not available for writing"); + } + } + if (len) { + request->_tempFile.write(data, len); + } + if (final) { + request->_tempFile.close(); + } + } + ); + + // + // Upload a binary file: curl -v -F "data=@file.mp3" http://192.168.4.1/upload/binary + // + server.on( + "/upload/binary", HTTP_POST, + [](AsyncWebServerRequest *request) { + // response already set ? + if (request->getResponse()) { + // 400 No Content-Length + return; + } + + // nothing uploaded ? + if (!request->_tempObject) { + return request->send(400, "text/plain", "Nothing uploaded"); + } + + uint8_t *buffer = reinterpret_cast(request->_tempObject); + // process the buffer + + delete buffer; + request->_tempObject = nullptr; + + request->send(200, "text/plain", "OK"); + }, + [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + Serial.printf("Upload[%s]: start=%u, len=%u, final=%d\n", filename.c_str(), index, len, final); + + // first pass ? + if (!index) { + size_t size = request->header("Content-Length").toInt(); + if (!size) { + request->send(400, "text/plain", "No Content-Length"); + } else { + Serial.printf("Allocating buffer of %u bytes\n", size); + uint8_t *buffer = new (std::nothrow) uint8_t[size]; + if (!buffer) { + // not enough memory + request->abort(); + } else { + request->_tempObject = buffer; + } + } + } + + if (len) { + memcpy(reinterpret_cast(request->_tempObject) + index, data, len); + } + } + ); + + server.begin(); +} + +// not needed +void loop() { + delay(100); +} diff --git a/libraries/ESP_Async_WebServer/examples/WebSocket/WebSocket.ino b/libraries/ESP_Async_WebServer/examples/WebSocket/WebSocket.ino new file mode 100644 index 0000000..8c5e5a5 --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/WebSocket/WebSocket.ino @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// WebSocket example +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); +static AsyncWebSocket ws("/ws"); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // + // Run in terminal 1: websocat ws://192.168.4.1/ws => should stream data + // Run in terminal 2: websocat ws://192.168.4.1/ws => should stream data + // Run in terminal 3: websocat ws://192.168.4.1/ws => should fail: + // + // To send a message to the WebSocket server: + // + // echo "Hello!" | websocat ws://192.168.4.1/ws + // + ws.onEvent([](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { + (void)len; + + if (type == WS_EVT_CONNECT) { + ws.textAll("new client connected"); + Serial.println("ws connect"); + client->setCloseClientOnQueueFull(false); + client->ping(); + + } else if (type == WS_EVT_DISCONNECT) { + ws.textAll("client disconnected"); + Serial.println("ws disconnect"); + + } else if (type == WS_EVT_ERROR) { + Serial.println("ws error"); + + } else if (type == WS_EVT_PONG) { + Serial.println("ws pong"); + + } else if (type == WS_EVT_DATA) { + AwsFrameInfo *info = (AwsFrameInfo *)arg; + Serial.printf("index: %" PRIu64 ", len: %" PRIu64 ", final: %" PRIu8 ", opcode: %" PRIu8 "\n", info->index, info->len, info->final, info->opcode); + String msg = ""; + if (info->final && info->index == 0 && info->len == len) { + if (info->opcode == WS_TEXT) { + data[len] = 0; + Serial.printf("ws text: %s\n", (char *)data); + } + } + } + }); + + // shows how to prevent a third WS client to connect + server.addHandler(&ws).addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) { + // ws.count() is the current count of WS clients: this one is trying to upgrade its HTTP connection + if (ws.count() > 1) { + // if we have 2 clients or more, prevent the next one to connect + request->send(503, "text/plain", "Server is busy"); + } else { + // process next middleware and at the end the handler + next(); + } + }); + + server.addHandler(&ws); + + server.begin(); +} + +static uint32_t lastWS = 0; +static uint32_t deltaWS = 100; + +static uint32_t lastHeap = 0; + +void loop() { + uint32_t now = millis(); + + if (now - lastWS >= deltaWS) { + ws.printfAll("kp%.4f", (10.0 / 3.0)); + lastWS = millis(); + } + + if (now - lastHeap >= 2000) { + Serial.printf("Connected clients: %u / %u total\n", ws.count(), ws.getClients().size()); + + // this can be called to also set a soft limit on the number of connected clients + ws.cleanupClients(2); // no more than 2 clients + +#ifdef ESP32 + Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap()); +#endif + lastHeap = now; + } +} diff --git a/libraries/ESP_Async_WebServer/examples/WebSocketEasy/WebSocketEasy.ino b/libraries/ESP_Async_WebServer/examples/WebSocketEasy/WebSocketEasy.ino new file mode 100644 index 0000000..12b03ce --- /dev/null +++ b/libraries/ESP_Async_WebServer/examples/WebSocketEasy/WebSocketEasy.ino @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// WebSocket example using the easy to use AsyncWebSocketMessageHandler handler that only supports unfragmented messages +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +// create an easy-to-use handler +static AsyncWebSocketMessageHandler wsHandler; + +// add it to the websocket server +static AsyncWebSocket ws("/ws", wsHandler.eventHandler()); + +// alternatively you can do as usual: +// +// static AsyncWebSocket ws("/ws"); +// ws.onEvent(wsHandler.eventHandler()); + +static const char *htmlContent PROGMEM = R"( + + + + WebSocket + + +

WebSocket Example

+ <>Open your browser console!

+ + + + + + )"; +static const size_t htmlContentLength = strlen_P(htmlContent); + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // serves root html page + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/html", (const uint8_t *)htmlContent, htmlContentLength); + }); + + wsHandler.onConnect([](AsyncWebSocket *server, AsyncWebSocketClient *client) { + Serial.printf("Client %" PRIu32 " connected\n", client->id()); + server->textAll("New client: " + String(client->id())); + }); + + wsHandler.onDisconnect([](AsyncWebSocket *server, uint32_t clientId) { + Serial.printf("Client %" PRIu32 " disconnected\n", clientId); + server->textAll("Client " + String(clientId) + " disconnected"); + }); + + wsHandler.onError([](AsyncWebSocket *server, AsyncWebSocketClient *client, uint16_t errorCode, const char *reason, size_t len) { + Serial.printf("Client %" PRIu32 " error: %" PRIu16 ": %s\n", client->id(), errorCode, reason); + }); + + wsHandler.onMessage([](AsyncWebSocket *server, AsyncWebSocketClient *client, const uint8_t *data, size_t len) { + Serial.printf("Client %" PRIu32 " data: %s\n", client->id(), (const char *)data); + }); + + wsHandler.onFragment([](AsyncWebSocket *server, AsyncWebSocketClient *client, const AwsFrameInfo *frameInfo, const uint8_t *data, size_t len) { + Serial.printf("Client %" PRIu32 " fragment %" PRIu32 ": %s\n", client->id(), frameInfo->num, (const char *)data); + }); + + server.addHandler(&ws); + server.begin(); +} + +static uint32_t lastWS = 0; +static uint32_t deltaWS = 2000; + +void loop() { + uint32_t now = millis(); + + if (now - lastWS >= deltaWS) { + ws.cleanupClients(); + ws.printfAll("now: %" PRIu32 "\n", now); + lastWS = millis(); +#ifdef ESP32 + Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap()); +#endif + } +} -- cgit v1.3.1