From 2879ac9abcf1c4440240aefe92ddb52ec97f7472 Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Thu, 30 Jan 2025 00:01:08 +0100 Subject: [PATCH] Split SimpleServer in multiple examples --- .github/workflows/ci.yml | 25 +- examples/Auth/Auth.ino | 156 ++++ examples/CORS/CORS.ino | 59 ++ examples/CaptivePortal/CaptivePortal.ino | 4 +- examples/CatchAllHandler/CatchAllHandler.ino | 130 +++ examples/ChunkResponse/ChunkResponse.ino | 148 +++ examples/EndBegin/EndBegin.ino | 48 + examples/Filters/Filters.ino | 8 +- examples/FlashResponse/FlashResponse.ino | 106 +++ .../HeaderManipulation/HeaderManipulation.ino | 87 ++ examples/Issue162/Issue162.ino | 102 --- examples/Issue85/Issue85.ino | 138 --- examples/Json/Json.ino | 89 ++ examples/Logging/Logging.ino | 48 + examples/MessagePack/MessagePack.ino | 87 ++ examples/Middleware/Middleware.ino | 81 ++ examples/Params/Params.ino | 65 ++ .../PerfTests.ino} | 234 ++--- examples/RateLimit/RateLimit.ino | 63 ++ examples/Redirect/Redirect.ino | 47 + .../ResumableDownload/ResumableDownload.ino | 69 ++ examples/Rewrite/Rewrite.ino | 51 ++ .../ServerSentEvents/ServerSentEvents.ino | 105 +++ examples/SimpleServer/SimpleServer.ino | 842 ------------------ .../SlowChunkResponse/SlowChunkResponse.ino | 142 +++ examples/StaticFile/StaticFile.ino | 121 +++ examples/StreamFiles/StreamConcat.h | 46 - examples/StreamFiles/StreamFiles.ino | 92 -- examples/Templates/Templates.ino | 98 ++ examples/WebSocket/WebSocket.ino | 112 +++ platformio.ini | 35 +- 31 files changed, 2029 insertions(+), 1409 deletions(-) create mode 100644 examples/Auth/Auth.ino create mode 100644 examples/CORS/CORS.ino create mode 100644 examples/CatchAllHandler/CatchAllHandler.ino create mode 100644 examples/ChunkResponse/ChunkResponse.ino create mode 100644 examples/EndBegin/EndBegin.ino create mode 100644 examples/FlashResponse/FlashResponse.ino create mode 100644 examples/HeaderManipulation/HeaderManipulation.ino delete mode 100644 examples/Issue162/Issue162.ino delete mode 100644 examples/Issue85/Issue85.ino create mode 100644 examples/Json/Json.ino create mode 100644 examples/Logging/Logging.ino create mode 100644 examples/MessagePack/MessagePack.ino create mode 100644 examples/Middleware/Middleware.ino create mode 100644 examples/Params/Params.ino rename examples/{SSE_perftest/SSE_perftest.ino => PerfTests/PerfTests.ino} (50%) create mode 100644 examples/RateLimit/RateLimit.ino create mode 100644 examples/Redirect/Redirect.ino create mode 100644 examples/ResumableDownload/ResumableDownload.ino create mode 100644 examples/Rewrite/Rewrite.ino create mode 100644 examples/ServerSentEvents/ServerSentEvents.ino delete mode 100644 examples/SimpleServer/SimpleServer.ino create mode 100644 examples/SlowChunkResponse/SlowChunkResponse.ino create mode 100644 examples/StaticFile/StaticFile.ino delete mode 100644 examples/StreamFiles/StreamConcat.h delete mode 100644 examples/StreamFiles/StreamFiles.ino create mode 100644 examples/Templates/Templates.ino create mode 100644 examples/WebSocket/WebSocket.ino diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c399ad6..22231d79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,17 +68,11 @@ jobs: if: ${{ matrix.core == 'rp2040:rp2040' }} run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/khoih-prog/AsyncTCP_RP2040W#v1.2.0 - - name: Build CaptivePortal - run: arduino-cli compile --library . --warnings none -b ${{ matrix.board }} "examples/CaptivePortal/CaptivePortal.ino" - - - name: Build SimpleServer - run: arduino-cli compile --library . --warnings none -b ${{ matrix.board }} "examples/SimpleServer/SimpleServer.ino" - - - name: Build Filters - run: arduino-cli compile --library . --warnings none -b ${{ matrix.board }} "examples/Filters/Filters.ino" - - - name: Build StreamFiles - run: arduino-cli compile --library . --warnings none -b ${{ matrix.board }} "examples/StreamFiles/StreamFiles.ino" + - run: | + for i in `ls examples`; do + echo "Building examples/$i..." + arduino-cli compile --library . --warnings none -b ${{ matrix.board }} "examples/$i/$i.ino" + done platformio: name: "pio:${{ matrix.env }}:${{ matrix.board }}" @@ -147,7 +141,8 @@ jobs: python -m pip install --upgrade pip pip install --upgrade platformio - - run: PLATFORMIO_SRC_DIR=examples/CaptivePortal PIO_BOARD=${{ matrix.board }} pio run -e ${{ matrix.env }} - - run: PLATFORMIO_SRC_DIR=examples/SimpleServer PIO_BOARD=${{ matrix.board }} pio run -e ${{ matrix.env }} - - run: PLATFORMIO_SRC_DIR=examples/Filters PIO_BOARD=${{ matrix.board }} pio run -e ${{ matrix.env }} - - run: PLATFORMIO_SRC_DIR=examples/StreamFiles PIO_BOARD=${{ matrix.board }} pio run -e ${{ matrix.env }} + - run: | + for i in `ls examples`; do + echo "Building examples/$i..." + PLATFORMIO_SRC_DIR=examples/$i PIO_BOARD=${{ matrix.board }} pio run -e ${{ matrix.env }} + done diff --git a/examples/Auth/Auth.ino b/examples/Auth/Auth.ino new file mode 100644 index 00000000..aec27757 --- /dev/null +++ b/examples/Auth/Auth.ino @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Authentification and authorization middlewares +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) +#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() { +} diff --git a/examples/CORS/CORS.ino b/examples/CORS/CORS.ino new file mode 100644 index 00000000..a2066040 --- /dev/null +++ b/examples/CORS/CORS.ino @@ -0,0 +1,59 @@ +// 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) +#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() { +} diff --git a/examples/CaptivePortal/CaptivePortal.ino b/examples/CaptivePortal/CaptivePortal.ino index f12ad901..c7877638 100644 --- a/examples/CaptivePortal/CaptivePortal.ino +++ b/examples/CaptivePortal/CaptivePortal.ino @@ -14,8 +14,8 @@ #endif #include "ESPAsyncWebServer.h" -DNSServer dnsServer; -AsyncWebServer server(80); +static DNSServer dnsServer; +static AsyncWebServer server(80); class CaptiveRequestHandler : public AsyncWebHandler { public: diff --git a/examples/CatchAllHandler/CatchAllHandler.ino b/examples/CatchAllHandler/CatchAllHandler.ino new file mode 100644 index 00000000..f4f880e8 --- /dev/null +++ b/examples/CatchAllHandler/CatchAllHandler.ino @@ -0,0 +1,130 @@ +// 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) +#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\"}"); + } + }); + + server.begin(); +} + +// not needed +void loop() { +} diff --git a/examples/ChunkResponse/ChunkResponse.ino b/examples/ChunkResponse/ChunkResponse.ino new file mode 100644 index 00000000..49fd09ef --- /dev/null +++ b/examples/ChunkResponse/ChunkResponse.ino @@ -0,0 +1,148 @@ +// 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) +#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(); +} + +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/examples/EndBegin/EndBegin.ino b/examples/EndBegin/EndBegin.ino new file mode 100644 index 00000000..d85fca6d --- /dev/null +++ b/examples/EndBegin/EndBegin.ino @@ -0,0 +1,48 @@ +// 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) +#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() { +} diff --git a/examples/Filters/Filters.ino b/examples/Filters/Filters.ino index 3494e333..c35be60d 100644 --- a/examples/Filters/Filters.ino +++ b/examples/Filters/Filters.ino @@ -1,7 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov -// Reproduced issue https://github.com/ESP32Async/ESPAsyncWebServer/issues/26 +// +// Shows how to use setFilter to route requests to different handlers based on WiFi mode +// #include #ifdef ESP32 @@ -16,8 +18,8 @@ #endif #include "ESPAsyncWebServer.h" -DNSServer dnsServer; -AsyncWebServer server(80); +static DNSServer dnsServer; +static AsyncWebServer server(80); class CaptiveRequestHandler : public AsyncWebHandler { public: diff --git a/examples/FlashResponse/FlashResponse.ino b/examples/FlashResponse/FlashResponse.ino new file mode 100644 index 00000000..0a6c4b4d --- /dev/null +++ b/examples/FlashResponse/FlashResponse.ino @@ -0,0 +1,106 @@ +// 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) +#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() { +} diff --git a/examples/HeaderManipulation/HeaderManipulation.ino b/examples/HeaderManipulation/HeaderManipulation.ino new file mode 100644 index 00000000..5d77f9e9 --- /dev/null +++ b/examples/HeaderManipulation/HeaderManipulation.ino @@ -0,0 +1,87 @@ +// 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) +#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() { +} diff --git a/examples/Issue162/Issue162.ino b/examples/Issue162/Issue162.ino deleted file mode 100644 index 48b8929b..00000000 --- a/examples/Issue162/Issue162.ino +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov - -/** - * - * Connect to AP and run in bash: - * - * > while true; do echo "PING"; sleep 0.1; done | websocat ws://192.168.4.1/ws - * - */ -#include -#ifdef ESP8266 -#include -#endif -#ifdef ESP32 -#include -#endif -#include - -#include -AsyncWebServer server(80); -AsyncWebSocket ws("/ws"); - -void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { - if (type == WS_EVT_CONNECT) { - Serial.printf("Client #%" PRIu32 " connected.\n", client->id()); - } else if (type == WS_EVT_DISCONNECT) { - Serial.printf("Client #%" PRIu32 " disconnected.\n", client->id()); - } else if (type == WS_EVT_ERROR) { - Serial.printf("Client #%" PRIu32 " error.\n", client->id()); - } else if (type == WS_EVT_DATA) { - Serial.printf("Client #%" PRIu32 " len: %u\n", client->id(), len); - } else if (type == WS_EVT_PONG) { - Serial.printf("Client #%" PRIu32 " pong.\n", client->id()); - } else if (type == WS_EVT_PING) { - Serial.printf("Client #%" PRIu32 " ping.\n", client->id()); - } -} - -void setup() { - Serial.begin(115200); - - WiFi.mode(WIFI_AP); - WiFi.softAP("esp-captive"); - - ws.onEvent(onWsEvent); - server.addHandler(&ws); - - server.on("/close_all_ws_clients", HTTP_GET | HTTP_POST, [](AsyncWebServerRequest *request) { - Serial.println("Closing all WebSocket clients..."); - ws.closeAll(); - request->send(200, "application/json", "{\"status\":\"all clients closed\"}"); - }); - - server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(200, "text/html", R"rawliteral( - - - - - -

- - - )rawliteral"); - }); - - server.begin(); -} - -uint32_t lastTime = 0; -void loop() { - if (millis() - lastTime > 5000) { - lastTime = millis(); - Serial.printf("Client count: %u\n", ws.count()); - } - ws.cleanupClients(); -} diff --git a/examples/Issue85/Issue85.ino b/examples/Issue85/Issue85.ino deleted file mode 100644 index 4d0bc226..00000000 --- a/examples/Issue85/Issue85.ino +++ /dev/null @@ -1,138 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov - -/** - * - * Connect to AP and run in bash: - * - * > while true; do echo "PING"; sleep 0.1; done | websocat ws://192.168.4.1/ws - * - */ -#include -#ifdef ESP8266 -#include -#endif -#ifdef ESP32 -#include -#endif -#include - -#include - -size_t msgCount = 0; -size_t window = 100; -std::list times; - -void connect_wifi() { - WiFi.mode(WIFI_AP); - WiFi.softAP("esp-captive"); - - // Serial.print("Connecting"); - // while (WiFi.status() != WL_CONNECTED) { - // delay(500); - // Serial.print("."); - // } - // Serial.println(); - - // Serial.print("Connected, IP address: "); - // Serial.println(WiFi.localIP()); -} - -void notFound(AsyncWebServerRequest *request) { - request->send(404, "text/plain", "Not found"); -} - -AsyncWebServer server(80); -AsyncWebSocket ws("/ws"); - -// initial stack -char *stack_start; - -void printStackSize() { - char stack; - Serial.print(F("stack size ")); - Serial.print(stack_start - &stack); - Serial.print(F(" | Heap free:")); - Serial.print(ESP.getFreeHeap()); -#ifdef ESP8266 - Serial.print(F(" frag:")); - Serial.print(ESP.getHeapFragmentation()); - Serial.print(F(" maxFreeBlock:")); - Serial.print(ESP.getMaxFreeBlockSize()); -#endif - Serial.println(); -} - -void onWsEventEmpty(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { - msgCount++; - Serial.printf("count: %d\n", msgCount); - - times.push_back(millis()); - while (times.size() > window) { - times.pop_front(); - } - if (times.size() == window) { - Serial.printf("%f req/s\n", 1000.0 * window / (times.back() - times.front())); - } - - printStackSize(); - - client->text("PONG"); -} - -void serve_upload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) { - Serial.print("> onUpload "); - Serial.print("index: "); - Serial.print(index); - Serial.print(" len:"); - Serial.print(len); - Serial.print(" final:"); - Serial.print(final); - Serial.println(); -} - -void setup() { - char stack; - stack_start = &stack; - - Serial.begin(115200); - Serial.println("\n\n\nStart"); - Serial.printf("stack_start: %p\n", stack_start); - - connect_wifi(); - - server.onNotFound(notFound); - - ws.onEvent(onWsEventEmpty); - server.addHandler(&ws); - - server.on( - "/upload", HTTP_POST, - [](AsyncWebServerRequest *request) { - request->send(200); - }, - serve_upload - ); - - server.begin(); - Serial.println("Server started"); -} - -String msg = ""; -uint32_t count = 0; -void loop() { - // ws.cleanupClients(); - // count += 1; - // // Concatenate some string, and clear it after some time - // static unsigned long millis_string = millis(); - // if (millis() - millis_string > 1) { - // millis_string += 100; - // if (count % 100 == 0) { - // // printStackSize(); - // // Serial.print(msg); - // msg = String(); - // } else { - // msg += 'T'; - // } - // } -} diff --git a/examples/Json/Json.ino b/examples/Json/Json.ino new file mode 100644 index 00000000..b8f1fa91 --- /dev/null +++ b/examples/Json/Json.ino @@ -0,0 +1,89 @@ +// 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) +#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() { +} diff --git a/examples/Logging/Logging.ino b/examples/Logging/Logging.ino new file mode 100644 index 00000000..6a1a9c55 --- /dev/null +++ b/examples/Logging/Logging.ino @@ -0,0 +1,48 @@ +// 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) +#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() { +} diff --git a/examples/MessagePack/MessagePack.ino b/examples/MessagePack/MessagePack.ino new file mode 100644 index 00000000..2fbc31a8 --- /dev/null +++ b/examples/MessagePack/MessagePack.ino @@ -0,0 +1,87 @@ +// 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) +#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() { +} diff --git a/examples/Middleware/Middleware.ino b/examples/Middleware/Middleware.ino new file mode 100644 index 00000000..5cc23c39 --- /dev/null +++ b/examples/Middleware/Middleware.ino @@ -0,0 +1,81 @@ +// 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) +#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() { +} diff --git a/examples/Params/Params.ino b/examples/Params/Params.ino new file mode 100644 index 00000000..2fd7736c --- /dev/null +++ b/examples/Params/Params.ino @@ -0,0 +1,65 @@ +// 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) +#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 + + // Get query parameters + // + // curl -v http://192.168.4.1/?message=bob + // + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + String message; + if (request->hasParam("message")) { + message = request->getParam("message")->value(); + } else { + message = "No message sent"; + } + request->send(200, "text/plain", "Hello, GET: " + message); + }); + + // Get form body parameters + // + // curl -v -H "Content-Type: application/x-www-form-urlencoded" -d "message=carl" http://192.168.4.1/ + // + server.on("/", HTTP_POST, [](AsyncWebServerRequest *request) { + String message; + if (request->hasParam("message", true)) { + message = request->getParam("message", true)->value(); + } else { + message = "No message sent"; + } + request->send(200, "text/plain", "Hello, POST: " + message); + }); + + server.begin(); +} + +// not needed +void loop() { +} diff --git a/examples/SSE_perftest/SSE_perftest.ino b/examples/PerfTests/PerfTests.ino similarity index 50% rename from examples/SSE_perftest/SSE_perftest.ino rename to examples/PerfTests/PerfTests.ino index c218187e..5b5933e1 100644 --- a/examples/SSE_perftest/SSE_perftest.ino +++ b/examples/PerfTests/PerfTests.ino @@ -1,10 +1,9 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov -// SSE server with a load generator -// it will auto adjust message push rate to minimize discards across all connected clients -// per second stats is printed to a serial console and also published as SSE ping message -// open /sse URL to start events generator +// +// Perf tests +// #include #ifdef ESP32 @@ -20,15 +19,7 @@ #include -#if __has_include("ArduinoJson.h") -#include -#include -#include -#endif - -#include - -const char *htmlContent PROGMEM = R"( +static const char *htmlContent PROGMEM = R"( @@ -88,175 +79,104 @@ const char *htmlContent PROGMEM = R"( )"; -const char *staticContent PROGMEM = R"( - - - - Sample HTML - - -

Hello, %IP%

- - -)"; - -AsyncWebServer server(80); -AsyncEventSource events("/events"); - -///////////////////////////////////////////////////////////////////////////////////////////////////// +static const size_t htmlContentLength = strlen_P(htmlContent); -const char *PARAM_MESSAGE PROGMEM = "message"; -const char *SSE_HTLM PROGMEM = R"( - - - - Server-Sent Events - - - -

Open your browser console!

- - -)"; - -static const char *SSE_MSG = - R"(Alice felt that this could not be denied, so she tried another question. 'What sort of people live about here?' 'In THAT direction,' the Cat said, waving its right paw round, 'lives a Hatter: and in THAT direction,' waving the other paw, 'lives a March Hare. Visit either you like: they're both mad.' -'But I don't want to go among mad people,' Alice remarked. 'Oh, you can't help that,' said the Cat: 'we're all mad here. I'm mad. You're mad.' 'How do you know I'm mad?' said Alice. -'You must be,' said the Cat, `or you wouldn't have come here.' Alice didn't think that proved it at all; however, she went on 'And how do you know that you're mad?' 'To begin with,' said the Cat, 'a dog's not mad. You grant that?' -)"; - -void notFound(AsyncWebServerRequest *request) { - request->send(404, "text/plain", "Not found"); -} - -static const char characters[] = "0123456789ABCDEF"; -static size_t charactersIndex = 0; +static AsyncWebServer server(80); +static AsyncEventSource events("/events"); void setup() { - Serial.begin(115200); #ifndef CONFIG_IDF_TARGET_ESP32H2 - /* - WiFi.mode(WIFI_STA); - WiFi.begin("SSID", "passwd"); - if (WiFi.waitForConnectResult() != WL_CONNECTED) { - Serial.printf("WiFi Failed!\n"); - return; - } - Serial.print("IP Address: "); - Serial.println(WiFi.localIP()); - */ - WiFi.mode(WIFI_AP); WiFi.softAP("esp-captive"); #endif + // 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) { - request->send(200, "text/html", staticContent); - }); - - events.onConnect([](AsyncEventSourceClient *client) { - if (client->lastId()) { - Serial.printf("SSE Client reconnected! Last message ID that it gat is: %" PRIu32 "\n", client->lastId()); - } - client->send("hello!", NULL, millis(), 1000); - }); - - server.on("/sse", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(200, "text/html", SSE_HTLM); + // 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); }); - // go to http://192.168.4.1/sse + // 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.onNotFound(notFound); - server.begin(); } -uint32_t lastSSE = 0; -uint32_t deltaSSE = 25; -uint32_t messagesSSE = 4; // how many messages to q each time -uint32_t sse_disc{0}, sse_enq{0}, sse_penq{0}, sse_second{0}; +static uint32_t lastSSE = 0; +static uint32_t deltaSSE = 10; -AsyncEventSource::SendStatus enqueue() { - AsyncEventSource::SendStatus state = events.send(SSE_MSG, "message"); - if (state == AsyncEventSource::SendStatus::DISCARDED) { - ++sse_disc; - } else if (state == AsyncEventSource::SendStatus::ENQUEUED) { - ++sse_enq; - } else { - ++sse_penq; - } - - return state; -} +static uint32_t lastHeap = 0; void loop() { uint32_t now = millis(); if (now - lastSSE >= deltaSSE) { - // enqueue messages - for (uint32_t i = 0; i != messagesSSE; ++i) { - auto err = enqueue(); - if (err == AsyncEventSource::SendStatus::DISCARDED || err == AsyncEventSource::SendStatus::PARTIALLY_ENQUEUED) { - // throttle messaging a bit - lastSSE = now + deltaSSE; - break; - } - } - + events.send(String("ping-") + now, "heartbeat", now); lastSSE = millis(); } - if (now - sse_second > 1000) { - String s; - s.reserve(100); - s = "Ping:"; - s += now / 1000; - s += " clients:"; - s += events.count(); - s += " disc:"; - s += sse_disc; - s += " enq:"; - s += sse_enq; - s += " partial:"; - s += sse_penq; - s += " avg wait:"; - s += events.avgPacketsWaiting(); - s += " heap:"; - s += ESP.getFreeHeap() / 1024; - - events.send(s, "heartbeat", now); - Serial.println(); - Serial.println(s); - - // if we see discards or partial enqueues, let's decrease message rate, else - increase. So that we can come to a max sustained message rate - if (sse_disc || sse_penq) { - ++deltaSSE; - } else if (deltaSSE > 5) { - --deltaSSE; - } - - sse_disc = sse_enq = sse_penq = 0; - sse_second = now; +#ifdef ESP32 + if (now - lastHeap >= 2000) { + Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap()); + lastHeap = now; } +#endif } diff --git a/examples/RateLimit/RateLimit.ino b/examples/RateLimit/RateLimit.ino new file mode 100644 index 00000000..7cd8761b --- /dev/null +++ b/examples/RateLimit/RateLimit.ino @@ -0,0 +1,63 @@ +// 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) +#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() { +} diff --git a/examples/Redirect/Redirect.ino b/examples/Redirect/Redirect.ino new file mode 100644 index 00000000..4d533045 --- /dev/null +++ b/examples/Redirect/Redirect.ino @@ -0,0 +1,47 @@ +// 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) +#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() { +} diff --git a/examples/ResumableDownload/ResumableDownload.ino b/examples/ResumableDownload/ResumableDownload.ino new file mode 100644 index 00000000..4d951edc --- /dev/null +++ b/examples/ResumableDownload/ResumableDownload.ino @@ -0,0 +1,69 @@ +// 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) +#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(); +} + +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/examples/Rewrite/Rewrite.ino b/examples/Rewrite/Rewrite.ino new file mode 100644 index 00000000..11fe490e --- /dev/null +++ b/examples/Rewrite/Rewrite.ino @@ -0,0 +1,51 @@ +// 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) +#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() { +} diff --git a/examples/ServerSentEvents/ServerSentEvents.ino b/examples/ServerSentEvents/ServerSentEvents.ino new file mode 100644 index 00000000..47482423 --- /dev/null +++ b/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) +#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/examples/SimpleServer/SimpleServer.ino b/examples/SimpleServer/SimpleServer.ino deleted file mode 100644 index 250e4543..00000000 --- a/examples/SimpleServer/SimpleServer.ino +++ /dev/null @@ -1,842 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov - -// A simple server implementation showing how to: -// * serve static messages -// * read GET and POST parameters -// * handle missing pages / 404s - -#include -#ifdef ESP32 -#include -#include -#elif defined(ESP8266) -#include -#include -#elif defined(TARGET_RP2040) -#include -#include -#endif - -#include - -#if __has_include("ArduinoJson.h") -#include -#include -#include -#endif - -#include - -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.

- - -)"; - -const size_t htmlContentLength = strlen_P(htmlContent); - -const char *staticContent PROGMEM = R"( - - - - Sample HTML - - -

Hello, %IP%

- - -)"; - -AsyncWebServer server(80); -AsyncEventSource events("/events"); -AsyncWebSocket ws("/ws"); - -///////////////////////////////////////////////////////////////////////////////////////////////////// -// Middlewares -///////////////////////////////////////////////////////////////////////////////////////////////////// - -// log incoming requests -AsyncLoggingMiddleware requestLogger; - -// CORS -AsyncCorsMiddleware cors; - -// maximum 5 requests per 10 seconds -AsyncRateLimitMiddleware rateLimit; - -// filter out specific headers from the incoming request -AsyncHeaderFilterMiddleware headerFilter; - -// remove all headers from the incoming request except the ones provided in the constructor -AsyncHeaderFreeMiddleware headerFree; - -// basicAuth -AsyncAuthenticationMiddleware basicAuth; -AsyncAuthenticationMiddleware basicAuthHash; - -// simple digest authentication -AsyncAuthenticationMiddleware digestAuth; -AsyncAuthenticationMiddleware digestAuthHash; - -// complex authentication which adds request attributes for the next middlewares and handler -AsyncMiddlewareFunction complexAuth([](AsyncWebServerRequest *request, ArMiddlewareNext next) { - if (!request->authenticate("user", "password")) { - return request->requestAuthentication(); - } - request->setAttribute("user", "Mathieu"); - request->setAttribute("role", "staff"); - - next(); - - request->getResponse()->addHeader("X-Rate-Limit", "200"); -}); - -AsyncAuthorizationMiddleware authz([](AsyncWebServerRequest *request) { - return request->getAttribute("role") == "staff"; -}); - -int wsClients = 0; - -///////////////////////////////////////////////////////////////////////////////////////////////////// - -const char *PARAM_MESSAGE PROGMEM = "message"; -const char *SSE_HTLM PROGMEM = R"( - - - - Server-Sent Events - - - -

Open your browser console!

- - -)"; - -#if __has_include("ArduinoJson.h") -AsyncCallbackJsonWebHandler *jsonHandler = new AsyncCallbackJsonWebHandler("/json2"); -AsyncCallbackMessagePackWebHandler *msgPackHandler = new AsyncCallbackMessagePackWebHandler("/msgpack2"); -#endif - -static const char characters[] = "0123456789ABCDEF"; -static size_t charactersIndex = 0; - -void setup() { - - Serial.begin(115200); - -#ifndef CONFIG_IDF_TARGET_ESP32H2 - // WiFi.mode(WIFI_STA); - // WiFi.begin("YOUR_SSID", "YOUR_PASSWORD"); - // if (WiFi.waitForConnectResult() != WL_CONNECTED) { - // Serial.printf("WiFi Failed!\n"); - // return; - // } - // Serial.print("IP Address: "); - // Serial.println(WiFi.localIP()); - - WiFi.mode(WIFI_AP); - WiFi.softAP("esp-captive"); -#endif - -#ifdef ESP32 - LittleFS.begin(true); -#else - LittleFS.begin(); -#endif - - { - File f = LittleFS.open("/index.txt", "w"); - if (f) { - for (size_t c = 0; c < sizeof(characters); c++) { - for (size_t i = 0; i < 1024; i++) { - f.print(characters[c]); - } - } - f.close(); - } - } - - { - File f = LittleFS.open("/index.html", "w"); - if (f) { - f.print(staticContent); - f.close(); - } - } - - // curl -v -X GET http://192.168.4.1/handler-not-sending-response - server.on("/handler-not-sending-response", HTTP_GET, [](AsyncWebServerRequest *request) { - // handler forgot to send a response to the client => 501 Not Implemented - }); - - // This is possible to replace a response. - // the previous one will be deleted. - // response sending happens when the handler returns. - // curl -v -X GET http://192.168.4.1/replace - server.on("/replace", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(200, "text/plain", "Hello, world"); - // oups! finally we want to send a different response - request->send(400, "text/plain", "validation error"); -#ifndef TARGET_RP2040 - Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap()); -#endif - }); - - /////////////////////////////////////////////////////////////////////// - // Request header manipulations - /////////////////////////////////////////////////////////////////////// - - // curl -v -X GET -H "x-remove-me: value" http://192.168.4.1/headers - server.on("/headers", HTTP_GET, [](AsyncWebServerRequest *request) { - Serial.printf("Request Headers:\n"); - for (auto &h : request->getHeaders()) { - Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str()); - } - - // remove x-remove-me header - request->removeHeader("x-remove-me"); - Serial.printf("Request Headers:\n"); - for (auto &h : request->getHeaders()) { - Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str()); - } - - std::vector headers; - request->getHeaderNames(headers); - for (auto &h : headers) { - Serial.printf("Request Header Name: %s\n", h); - } - - request->send(200); - }); - - /////////////////////////////////////////////////////////////////////// - // Middlewares at server level (will apply to all requests) - /////////////////////////////////////////////////////////////////////// - - requestLogger.setOutput(Serial); - - basicAuth.setUsername("admin"); - basicAuth.setPassword("admin"); - basicAuth.setRealm("MyApp"); - basicAuth.setAuthFailureMessage("Authentication failed"); - basicAuth.setAuthType(AsyncAuthType::AUTH_BASIC); - basicAuth.generateHash(); - - basicAuthHash.setUsername("admin"); - basicAuthHash.setPasswordHash("YWRtaW46YWRtaW4="); // BASE64(admin:admin) - basicAuthHash.setRealm("MyApp"); - basicAuthHash.setAuthFailureMessage("Authentication failed"); - basicAuthHash.setAuthType(AsyncAuthType::AUTH_BASIC); - - digestAuth.setUsername("admin"); - digestAuth.setPassword("admin"); - digestAuth.setRealm("MyApp"); - digestAuth.setAuthFailureMessage("Authentication failed"); - digestAuth.setAuthType(AsyncAuthType::AUTH_DIGEST); - digestAuth.generateHash(); - - digestAuthHash.setUsername("admin"); - digestAuthHash.setPasswordHash("f499b71f9a36d838b79268e145e132f7"); // MD5(user:realm:pass) - digestAuthHash.setRealm("MyApp"); - digestAuthHash.setAuthFailureMessage("Authentication failed"); - digestAuthHash.setAuthType(AsyncAuthType::AUTH_DIGEST); - - rateLimit.setMaxRequests(5); - rateLimit.setWindowSize(10); - - headerFilter.filter("X-Remove-Me"); - headerFree.keep("X-Keep-Me"); - headerFree.keep("host"); - - cors.setOrigin("http://192.168.4.1"); - cors.setMethods("POST, GET, OPTIONS, DELETE"); - cors.setHeaders("X-Custom-Header"); - cors.setAllowCredentials(false); - cors.setMaxAge(600); - -#ifndef PERF_TEST - // global middleware - server.addMiddleware(&requestLogger); - server.addMiddlewares({&rateLimit, &cors, &headerFilter}); -#endif - - // Test CORS preflight request - // curl -v -X OPTIONS -H "origin: http://192.168.4.1" http://192.168.4.1/middleware/cors - server.on("/middleware/cors", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(200, "text/plain", "Hello, world!"); - }); - - // curl -v -X GET -H "x-remove-me: value" http://192.168.4.1/middleware/test-header-filter - // - requestLogger will log the incoming headers (including x-remove-me) - // - headerFilter will remove x-remove-me header - // - handler will log the remaining headers - server.on("/middleware/test-header-filter", HTTP_GET, [](AsyncWebServerRequest *request) { - for (auto &h : request->getHeaders()) { - Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str()); - } - request->send(200); - }); - - // curl -v -X GET -H "x-keep-me: value" http://192.168.4.1/middleware/test-header-free - // - requestLogger will log the incoming headers (including x-keep-me) - // - headerFree will remove all headers except x-keep-me and host - // - handler will log the remaining headers (x-keep-me and host) - server - .on( - "/middleware/test-header-free", HTTP_GET, - [](AsyncWebServerRequest *request) { - for (auto &h : request->getHeaders()) { - Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str()); - } - request->send(200); - } - ) - .addMiddleware(&headerFree); - - // basic authentication method - // curl -v -X GET -H "origin: http://192.168.4.1" -u admin:admin http://192.168.4.1/middleware/auth-basic - server - .on( - "/middleware/auth-basic", HTTP_GET, - [](AsyncWebServerRequest *request) { - request->send(200, "text/plain", "Hello, world!"); - } - ) - .addMiddleware(&basicAuth); - - // basic authentication method with hash - // curl -v -X GET -H "origin: http://192.168.4.1" -u admin:admin http://192.168.4.1/middleware/auth-basic-hash - server - .on( - "/middleware/auth-basic-hash", HTTP_GET, - [](AsyncWebServerRequest *request) { - request->send(200, "text/plain", "Hello, world!"); - } - ) - .addMiddleware(&basicAuthHash); - - // digest authentication - // curl -v -X GET -H "origin: http://192.168.4.1" -u admin:admin --digest http://192.168.4.1/middleware/auth-digest - server - .on( - "/middleware/auth-digest", HTTP_GET, - [](AsyncWebServerRequest *request) { - request->send(200, "text/plain", "Hello, world!"); - } - ) - .addMiddleware(&digestAuth); - - // digest authentication with hash - // curl -v -X GET -H "origin: http://192.168.4.1" -u admin:admin --digest http://192.168.4.1/middleware/auth-digest-hash - server - .on( - "/middleware/auth-digest-hash", HTTP_GET, - [](AsyncWebServerRequest *request) { - request->send(200, "text/plain", "Hello, world!"); - } - ) - .addMiddleware(&digestAuthHash); - - // test digest auth with cors - // curl -v -X GET -H "origin: http://192.168.4.1" --digest -u user:password http://192.168.4.1/middleware/auth-custom - server - .on( - "/middleware/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}); - - /////////////////////////////////////////////////////////////////////// - - // curl -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/redirect - // curl -v -X POST -H "origin: http://192.168.4.1" http://192.168.4.1/redirect - server.on("/redirect", HTTP_GET | HTTP_POST, [](AsyncWebServerRequest *request) { - request->redirect("/"); - }); - - // PERF TEST: - // > 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) { - request->send(200, "text/html", htmlContent); - }); - - // curl -v -X GET http://192.168.4.1/index.txt - server.serveStatic("/index.txt", LittleFS, "/index.txt"); - - // curl -v -X GET http://192.168.4.1/index-private.txt - server.serveStatic("/index-private.txt", LittleFS, "/index.txt").setAuthentication("admin", "admin"); - - // ServeStatic static is used to serve static output which never changes over time. - // This special endpoints automatyically 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 -X GET http://192.168.4.1/index-static.html - server.serveStatic("/index-static.html", LittleFS, "/index.html").setTemplateProcessor([](const String &var) -> String { - if (var == "IP") { - // for CI, commented out since H2 board does not support WiFi - // return WiFi.localIP().toString(); - // return WiFi.softAPIP().toString(); - return "127.0.0..1"; - } - return emptyString; - }); - - // 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 -X GET http://192.168.4.1/index-dynamic.html - server.on("/index-dynamic.html", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(LittleFS, "/index.html", "text/html", false, [](const String &var) -> String { - if (var == "IP") { - return String(random(0, 1000)); - } - return emptyString; - }); - }); - - // Issue #14: assert failed: tcp_update_rcv_ann_wnd (needs help to test fix) - // > curl -v http://192.168.4.1/issue-14 - pinMode(4, OUTPUT); - server.on("/issue-14", HTTP_GET, [](AsyncWebServerRequest *request) { - digitalWrite(4, HIGH); - request->send(LittleFS, "/index.txt", "text/pain"); - delay(500); - digitalWrite(4, LOW); - }); - - /* - Chunked encoding test: sends 16k of characters. - ❯ curl -N -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/chunk - */ - server.on("/chunk", HTTP_HEAD | HTTP_GET, [](AsyncWebServerRequest *request) { - AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { - if (index >= 16384) { - return 0; - } - memset(buffer, characters[charactersIndex], maxLen); - charactersIndex = (charactersIndex + 1) % sizeof(characters); - return maxLen; - }); - request->send(response); - }); - - // curl -N -v -X GET http://192.168.4.1/chunked.html --output - - // curl -N -v -X GET -H "if-none-match: 4272" http://192.168.4.1/chunked.html --output - - server.on("/chunked.html", HTTP_GET, [](AsyncWebServerRequest *request) { - String len = String(htmlContentLength); - - if (request->header(asyncsrv::T_INM) == len) { - 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 1024 or maxLen bytes of the remaining content - const int chunkSize = min((size_t)1024, 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, len); - - request->send(response); - }); - - // time curl -N -v -G -d 'd=3000' -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 by 2 seconds - // 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); - }); - - /* - ❯ 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); // overrides previous one - response->addHeader(asyncsrv::T_Content_Type, "foo"); - response->setContentType("application/octet-stream"); // overrides previous one - // ... - request->send(response); - } else { - // ... - } - }); - - // Send a GET request to /get?message= - server.on("/get", HTTP_GET, [](AsyncWebServerRequest *request) { - String message; - if (request->hasParam(PARAM_MESSAGE)) { - message = request->getParam(PARAM_MESSAGE)->value(); - } else { - message = "No message sent"; - } - request->send(200, "text/plain", "Hello, GET: " + message); - }); - - // Send a POST request to /post with a form field message set to - server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request) { - String message; - if (request->hasParam(PARAM_MESSAGE, true)) { - message = request->getParam(PARAM_MESSAGE, true)->value(); - } else { - message = "No message sent"; - } - request->send(200, "text/plain", "Hello, POST: " + message); - }); - -#if __has_include("ArduinoJson.h") - // JSON - - // sends JSON - // curl -v -X GET 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); - }); - - // curl -v -X GET 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 - jsonHandler->setMethod(HTTP_POST | HTTP_PUT); - jsonHandler->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); - }); - - // MessagePack - - // receives MessagePack and sends MessagePack - msgPackHandler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) { - // JsonObject jsonObj = json.as(); - // ... - - AsyncMessagePackResponse *response = new AsyncMessagePackResponse(); - JsonObject root = response->getRoot().to(); - root["hello"] = "world"; - response->setLength(); - request->send(response); - }); - - // sends MessagePack - server.on("/msgpack1", HTTP_GET, [](AsyncWebServerRequest *request) { - AsyncMessagePackResponse *response = new AsyncMessagePackResponse(); - JsonObject root = response->getRoot().to(); - root["hello"] = "world"; - response->setLength(); - request->send(response); - }); -#endif - - events.onConnect([](AsyncEventSourceClient *client) { - if (client->lastId()) { - Serial.printf("SSE Client reconnected! Last message ID that it gat is: %" PRIu32 "\n", client->lastId()); - } - client->send("hello!", NULL, millis(), 1000); - }); - - server.on("/sse", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(200, "text/html", SSE_HTLM); - }); - - ws.onEvent([](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) { - (void)len; - if (type == WS_EVT_CONNECT) { - wsClients++; - ws.textAll("new client connected"); - Serial.println("ws connect"); - client->setCloseClientOnQueueFull(false); - client->ping(); - } else if (type == WS_EVT_DISCONNECT) { - wsClients--; - 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; - 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); - } - } - } - }); - - // SSS endpoints - // sends a message every 10 ms - // - // go to http://192.168.4.1/sse - // > curl -v -N -H "Accept: text/event-stream" http://192.168.4.1/events - // - // some perf tests: - // 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); - - // Run in terminal 1: websocat ws://192.168.4.1/ws => stream data - // Run in terminal 2: websocat ws://192.168.4.1/ws => stream data - // Run in terminal 3: websocat ws://192.168.4.1/ws => should fail: - /* -❯ websocat ws://192.168.4.1/ws -websocat: WebSocketError: WebSocketError: Received unexpected status code (503 Service Unavailable) -websocat: error running - */ - server.addHandler(&ws).addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) { - if (ws.count() > 2) { - // too many clients - answer back immediately and stop processing next middlewares and handler - request->send(503, "text/plain", "Server is busy"); - } else { - // process next middleware and at the end the handler - next(); - } - }); - - // Reset connection on HTTP request: - // for i in {1..20}; do curl -v -X GET https://192.168.4.1:80; done; - // The heap size should not decrease over time. - -#if __has_include("ArduinoJson.h") - server.addHandler(jsonHandler); - server.addHandler(msgPackHandler); -#endif - - // catch any request, and send a 404 Not Found response - // except for /game_log which is handled by onRequestBody - server.onNotFound([](AsyncWebServerRequest *request) { - if (request->url() == "/game_log") { - return; // response object already created by onRequestBody - } - - request->send(404, "text/plain", "Not found"); - }); - - // https://github.com/ESP32Async/ESPAsyncWebServer/issues/6 - // curl -v -X POST http://192.168.4.1/game_log -H "Content-Type: application/json" -d '{"game": "test"}' - // catch any POST request and send a 200 OK response - 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\"}"); - } - }); - - server.begin(); -} - -uint32_t lastSSE = 0; -uint32_t deltaSSE = 10; - -uint32_t lastWS = 0; -uint32_t deltaWS = 100; - -uint32_t lastHeap = 0; - -void loop() { - uint32_t now = millis(); - if (now - lastSSE >= deltaSSE) { - events.send(String("ping-") + now, "heartbeat", now); - lastSSE = millis(); - } - if (now - lastWS >= deltaWS) { - ws.printfAll("kp%.4f", (10.0 / 3.0)); - // for (auto& client : ws.getClients()) { - // client.printf("kp%.4f", (10.0 / 3.0)); - // } - lastWS = millis(); - } -#ifdef ESP32 - if (now - lastHeap >= 2000) { - Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap()); - lastHeap = now; - } -#endif -} diff --git a/examples/SlowChunkResponse/SlowChunkResponse.ino b/examples/SlowChunkResponse/SlowChunkResponse.ino new file mode 100644 index 00000000..0f620ee8 --- /dev/null +++ b/examples/SlowChunkResponse/SlowChunkResponse.ino @@ -0,0 +1,142 @@ +// 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 fiel download from SD Card) +// poll events will be throttled. +// +// You can test by firing a request to http://esp8266.local/ while the slow response is being served. +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) +#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); + }); + + // 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/examples/StaticFile/StaticFile.ino b/examples/StaticFile/StaticFile.ino new file mode 100644 index 00000000..b6314e39 --- /dev/null +++ b/examples/StaticFile/StaticFile.ino @@ -0,0 +1,121 @@ +// 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) +#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(); + } + + // 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"); + + server.begin(); +} + +// not needed +void loop() { +} diff --git a/examples/StreamFiles/StreamConcat.h b/examples/StreamFiles/StreamConcat.h deleted file mode 100644 index 33b4917f..00000000 --- a/examples/StreamFiles/StreamConcat.h +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov - -#pragma once - -#include - -class StreamConcat : public Stream { -public: - StreamConcat(Stream *s1, Stream *s2) : _s1(s1), _s2(s2) {} - - size_t write(__unused const uint8_t *p, __unused size_t n) override { - return 0; - } - size_t write(__unused uint8_t c) override { - return 0; - } - void flush() override {} - - int available() override { - return _s1->available() + _s2->available(); - } - - int read() override { - int c = _s1->read(); - return c != -1 ? c : _s2->read(); - } - -#if defined(TARGET_RP2040) - size_t readBytes(char *buffer, size_t length) { -#else - size_t readBytes(char *buffer, size_t length) override { -#endif - size_t count = _s1->readBytes(buffer, length); - return count > 0 ? count : _s2->readBytes(buffer, length); - } - - int peek() override { - int c = _s1->peek(); - return c != -1 ? c : _s2->peek(); - } - -private: - Stream *_s1; - Stream *_s2; -}; diff --git a/examples/StreamFiles/StreamFiles.ino b/examples/StreamFiles/StreamFiles.ino deleted file mode 100644 index 2a531a34..00000000 --- a/examples/StreamFiles/StreamFiles.ino +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov - -#include -#include -#ifdef ESP32 -#include -#include -#elif defined(ESP8266) -#include -#include -#elif defined(TARGET_RP2040) -#include -#include -#endif - -#include -#include -#include - -#include "StreamConcat.h" - -DNSServer dnsServer; -AsyncWebServer server(80); - -void setup() { - Serial.begin(115200); - - LittleFS.begin(); - -#ifndef CONFIG_IDF_TARGET_ESP32H2 - WiFi.mode(WIFI_AP); - WiFi.softAP("esp-captive"); - - dnsServer.start(53, "*", WiFi.softAPIP()); -#endif - - File file1 = LittleFS.open("/header.html", "w"); - file1.print("ESP Captive Portal"); - file1.close(); - - File file2 = LittleFS.open("/body.html", "w"); - file2.print("

Welcome to ESP Captive Portal

"); - file2.close(); - - File file3 = LittleFS.open("/footer.html", "w"); - file3.print(""); - file3.close(); - - server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { - File header = LittleFS.open("/header.html", "r"); - File body = LittleFS.open("/body.html", "r"); - StreamConcat stream1(&header, &body); - - StreamString content; -#if defined(TARGET_RP2040) - content.printf("FreeHeap: %d", rp2040.getFreeHeap()); -#else - content.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap()); -#endif - StreamConcat stream2 = StreamConcat(&stream1, &content); - - File footer = LittleFS.open("/footer.html", "r"); - StreamConcat stream3 = StreamConcat(&stream2, &footer); - - request->send(stream3, "text/html", stream3.available()); - header.close(); - body.close(); - footer.close(); - }); - - server.onNotFound([](AsyncWebServerRequest *request) { - request->send(404, "text/plain", "Not found"); - }); - - server.begin(); -} - -uint32_t last = 0; - -void loop() { - // dnsServer.processNextRequest(); - - if (millis() - last > 2000) { -#if defined(TARGET_RP2040) - Serial.printf("FreeHeap: %d", rp2040.getFreeHeap()); -#else - Serial.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap()); -#endif - last = millis(); - } -} diff --git a/examples/Templates/Templates.ino b/examples/Templates/Templates.ino new file mode 100644 index 00000000..b74208f9 --- /dev/null +++ b/examples/Templates/Templates.ino @@ -0,0 +1,98 @@ +// 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) +#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() { +} diff --git a/examples/WebSocket/WebSocket.ino b/examples/WebSocket/WebSocket.ino new file mode 100644 index 00000000..43b2bb6c --- /dev/null +++ b/examples/WebSocket/WebSocket.ino @@ -0,0 +1,112 @@ +// 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) +#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; + 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) { + // cleanup disconnected clients or too many clients + ws.cleanupClients(); + +#ifdef ESP32 + Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap()); +#endif + lastHeap = now; + } +} diff --git a/platformio.ini b/platformio.ini index 6f4d9580..f7c6be26 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,12 +1,30 @@ [platformio] default_envs = arduino-2, arduino-3, esp8266, raspberrypi lib_dir = . +; src_dir = examples/Auth ; src_dir = examples/CaptivePortal -src_dir = examples/SimpleServer -; src_dir = examples/StreamFiles +; src_dir = examples/CatchAllHandler +; src_dir = examples/ChunkResponse +; src_dir = examples/CORS +; src_dir = examples/EndBegin ; src_dir = examples/Filters -; src_dir = examples/Issue85 -; src_dir = examples/Issue162 +; src_dir = examples/FlashResponse +; src_dir = examples/HeaderManipulation +; src_dir = examples/Json +; src_dir = examples/Logging +; src_dir = examples/MessagePack +; src_dir = examples/Middleware +; src_dir = examples/Params +src_dir = examples/PerfTests +; src_dir = examples/RateLimit +; src_dir = examples/Redirect +; src_dir = examples/ResumableDownload +; src_dir = examples/Rewrite +; src_dir = examples/ServerSentEvents +; src_dir = examples/SlowChunkResponse +; src_dir = examples/StaticFile +; src_dir = examples/Templates +; src_dir = examples/WebSocket [env] framework = arduino @@ -30,8 +48,6 @@ monitor_filters = esp32_exception_decoder, log2file lib_compat_mode = strict lib_ldf_mode = chain lib_deps = - ; bblanchon/ArduinoJson @ 5.13.4 - ; bblanchon/ArduinoJson @ 6.21.5 bblanchon/ArduinoJson @ 7.3.0 ESP32Async/AsyncTCP @ 3.3.2 board_build.partitions = partitions-4MB.csv @@ -54,15 +70,10 @@ lib_deps = build_flags = ${env.build_flags} -D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=0 -[env:perf-test-AsyncTCP] -build_flags = ${env.build_flags} - -D PERF_TEST=1 - -[env:perf-test-AsyncTCPSock] +[env:AsyncTCPSock] lib_deps = https://github.com/ESP32Async/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip build_flags = ${env.build_flags} - -D PERF_TEST=1 [env:esp8266] platform = espressif8266