diff --git a/.github/scripts/list-examples.sh b/.github/scripts/list-examples.sh new file mode 100755 index 00000000..c7e44730 --- /dev/null +++ b/.github/scripts/list-examples.sh @@ -0,0 +1,14 @@ + +#!/bin/bash +# shellcheck disable=SC2002 + +# fail the script if any command unexpectedly fails +set -e + +examples=$(ls examples) +matrix="[" +for example in $examples; do + matrix="$matrix\"$example\"," +done +matrix="${matrix%,}]" +echo "$matrix" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24f2ddce..f3a64bc1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,12 +15,49 @@ concurrency: cancel-in-progress: true jobs: - arduino: - name: Arduino + arduino-lint: + name: Arduino Lint runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: arduino/arduino-lint-action@v2 + with: + library-manager: update + + list-examples: + name: List examples + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - id: list-examples + run: | + examples=$(./list-examples.sh) + echo "::set-output name=examples::$examples" + + arduino-setup: + name: Arduino Library Setup + runs-on: ubuntu-latest + steps: + - name: Install arduino-cli + run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh + + - name: Update core index + run: arduino-cli core update-index --additional-urls "${{ matrix.index_url }}" + + - name: Install core + run: arduino-cli core install --additional-urls "${{ matrix.index_url }}" ${{ matrix.core }} + + - name: Install ArduinoJson + run: arduino-cli lib install ArduinoJson + + arduino-build: + name: Arduino CLI Build + runs-on: ubuntu-latest + needs: [list-examples, arduino-setup] strategy: fail-fast: false matrix: + example: ${{ fromJson(needs.build.outputs.examples) }} include: - core: esp32:esp32 board: esp32:esp32:esp32 @@ -39,23 +76,6 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Arduino Lint - uses: arduino/arduino-lint-action@v2 - with: - library-manager: update - - - name: Install arduino-cli - run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh - - - name: Update core index - run: arduino-cli core update-index --additional-urls "${{ matrix.index_url }}" - - - name: Install core - run: arduino-cli core install --additional-urls "${{ matrix.index_url }}" ${{ matrix.core }} - - - name: Install ArduinoJson - run: arduino-cli lib install ArduinoJson - - name: Install AsyncTCP (ESP32) if: ${{ matrix.core == 'esp32:esp32' }} run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/ESP32Async/AsyncTCP#v3.3.3 @@ -68,17 +88,8 @@ 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" + - name: Build + run: arduino-cli compile --library . --warnings none -b ${{ matrix.board }} "examples/${{ matrix.example }}/${{ matrix.example }}.ino" platformio: name: "pio:${{ matrix.env }}:${{ matrix.board }}" @@ -147,7 +158,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/SimpleServer/SimpleServer.ino b/examples/All/All.ino similarity index 99% rename from examples/SimpleServer/SimpleServer.ino rename to examples/All/All.ino index 250e4543..45135852 100644 --- a/examples/SimpleServer/SimpleServer.ino +++ b/examples/All/All.ino @@ -1,10 +1,10 @@ // 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 +// +// This is an All in one example ;-) +// Do not run this example: it just holds the code for compilation purposes +// #include #ifdef ESP32 diff --git a/examples/Auth/Auth.ino b/examples/Auth/Auth.ino new file mode 100644 index 00000000..acc842fc --- /dev/null +++ b/examples/Auth/Auth.ino @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Authentication and authorization middlewares +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) +#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..6815c611 --- /dev/null +++ b/examples/CORS/CORS.ino @@ -0,0 +1,58 @@ +// 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..8088c20c --- /dev/null +++ b/examples/CatchAllHandler/CatchAllHandler.ino @@ -0,0 +1,131 @@ +// 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\"}"); + } + // note that there is no else here: the goal is only to prepare a response based on some body content + // onNotFound wil lalways be called after this, and will not override the response object if `/game_log` is requested + }); + + server.begin(); +} + +// not needed +void loop() {} diff --git a/examples/ChunkResponse/ChunkResponse.ino b/examples/ChunkResponse/ChunkResponse.ino new file mode 100644 index 00000000..dd329754 --- /dev/null +++ b/examples/ChunkResponse/ChunkResponse.ino @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Chunk response with caching example +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) +#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() {} diff --git a/examples/SSE_perftest/SSE_perftest.ino b/examples/ChunkRetryResponse/ChunkRetryResponse.ino similarity index 51% rename from examples/SSE_perftest/SSE_perftest.ino rename to examples/ChunkRetryResponse/ChunkRetryResponse.ino index c218187e..028c3c3f 100644 --- a/examples/SSE_perftest/SSE_perftest.ino +++ b/examples/ChunkRetryResponse/ChunkRetryResponse.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 +// +// Shows how to wait in a chunk response for incoming data +// #include #ifdef ESP32 @@ -26,9 +25,7 @@ #include #endif -#include - -const char *htmlContent PROGMEM = R"( +static const char *htmlContent PROGMEM = R"( @@ -88,175 +85,132 @@ const char *htmlContent PROGMEM = R"( )"; -const char *staticContent PROGMEM = R"( - - - - Sample HTML - - -

Hello, %IP%

- - -)"; +static const size_t htmlContentLength = strlen_P(htmlContent); -AsyncWebServer server(80); -AsyncEventSource events("/events"); +static AsyncWebServer server(80); +static AsyncLoggingMiddleware requestLogger; -///////////////////////////////////////////////////////////////////////////////////////////////////// - -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 String triggerUART; +static int key = -1; 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 + // adds some internal request loging for debugging + requestLogger.setEnabled(true); + requestLogger.setOutput(Serial); + + server.addMiddleware(&requestLogger); + +#if __has_include("ArduinoJson.h") + + // + // HOW TO RUN THIS EXAMPLE: + // + // 1. Trigger a request that will be blocked for a long time: + // > time curl -v -X POST http://192.168.4.1/api -H "Content-Type: application/json" -d '{"input": "Please type a key to continue in Serial console..."}' --output - + // + // 2. While waiting, in another terminal, run some concurrent requests: + // > time curl -v http://192.168.4.1/ + // + // 3. Type a key in the Serial console to continue the processing within 30 seconds. + // This should unblock the first request. + // server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(200, "text/html", staticContent); + // 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) { - 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( + "/api", HTTP_POST, + [](AsyncWebServerRequest *request) { + // request parsing has finished - server.on("/sse", HTTP_GET, [](AsyncWebServerRequest *request) { - request->send(200, "text/html", SSE_HTLM); - }); + // no data ? + if (((String *)request->_tempObject)->isEmpty()) { + request->send(400); + return; + } - // go to http://192.168.4.1/sse - server.addHandler(&events); + JsonDocument doc; - server.onNotFound(notFound); + // deserialize and check for errors + if (deserializeJson(doc, *(String *)request->_tempObject)) { + request->send(400); + return; + } - server.begin(); -} + // start UART com: UART will send the data to teh Serial console and wait for the key press + triggerUART = doc["input"].as(); + key = -1; -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}; - -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; - } + AsyncWebServerResponse *response = request->beginChunkedResponse("text/plain", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + // still waiting for UARY ? + if (triggerUART.length() && key == -1) { + return RESPONSE_TRY_AGAIN; + } - return state; -} + // finished ? + if (!triggerUART.length() && key == -1) { + return 0; // 0 means we are done + } -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; + log_d("UART answered!"); + + String answer = "You typed: "; + answer.concat((char)key); + + // note: I did not check for maxLen, but you should (see ChunkResponse.ino) + memcpy(buffer, answer.c_str(), answer.length()); + + // finish! + triggerUART = emptyString; + key = -1; + + return answer.length(); + }); + + request->send(response); + }, + NULL, // upload handler is not used so it should be NULL + [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { + log_d("Body: index: %u, len: %u, total: %u", index, len, total); + + if (!index) { + log_d("Start body parsing"); + request->_tempObject = new String(); + // cast request->_tempObject pointer to String and reserve total size + ((String *)request->_tempObject)->reserve(total); + // set timeout 30s + request->client()->setRxTimeout(30); } + + log_d("Append body data"); + ((String *)request->_tempObject)->concat(data, len); } + ); - lastSSE = millis(); - } +#endif - 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; - } + server.begin(); +} - sse_disc = sse_enq = sse_penq = 0; - sse_second = now; +void loop() { + if (triggerUART.length() && key == -1) { + Serial.println(triggerUART); + log_d("Waiting for UART input..."); + while (!Serial.available()) { + delay(100); + } + key = Serial.read(); + Serial.flush(); + log_d("UART input: %c", key); + triggerUART = emptyString; } } diff --git a/examples/EndBegin/EndBegin.ino b/examples/EndBegin/EndBegin.ino new file mode 100644 index 00000000..3b61366d --- /dev/null +++ b/examples/EndBegin/EndBegin.ino @@ -0,0 +1,47 @@ +// 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..07a103ce 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..93aaf069 --- /dev/null +++ b/examples/FlashResponse/FlashResponse.ino @@ -0,0 +1,105 @@ +// 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..2723efbb --- /dev/null +++ b/examples/HeaderManipulation/HeaderManipulation.ino @@ -0,0 +1,86 @@ +// 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..7ba752e9 --- /dev/null +++ b/examples/Json/Json.ino @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Shows how to send and receive 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..4249adce --- /dev/null +++ b/examples/Logging/Logging.ino @@ -0,0 +1,47 @@ +// 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..a8fda5d5 --- /dev/null +++ b/examples/MessagePack/MessagePack.ino @@ -0,0 +1,86 @@ +// 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..1a54392e --- /dev/null +++ b/examples/Middleware/Middleware.ino @@ -0,0 +1,80 @@ +// 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..938aba0d --- /dev/null +++ b/examples/Params/Params.ino @@ -0,0 +1,64 @@ +// 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/PerfTests/PerfTests.ino b/examples/PerfTests/PerfTests.ino new file mode 100644 index 00000000..ab0ce2f9 --- /dev/null +++ b/examples/PerfTests/PerfTests.ino @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Perf tests +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) +#include +#include +#endif + +#include + +static const char *htmlContent PROGMEM = R"( + + + + Sample HTML + + +

Hello, World!

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+ + +)"; + +static const size_t htmlContentLength = strlen_P(htmlContent); + +static 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 + + // HTTP endpoint + // + // > brew install autocannon + // > autocannon -c 10 -w 10 -d 20 http://192.168.4.1 + // > autocannon -c 16 -w 16 -d 20 http://192.168.4.1 + // + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + // need to cast to uint8_t* + // if you do not, the const char* will be copied in a temporary String buffer + request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength); + }); + + // SSS endpoint + // + // launch 16 concurrent workers for 30 seconds + // > for i in {1..10}; do ( count=$(gtimeout 30 curl -s -N -H "Accept: text/event-stream" http://192.168.4.1/events 2>&1 | grep -c "^data:"); echo "Total: $count events, $(echo "$count / 4" | bc -l) events / second" ) & done; + // > for i in {1..16}; do ( count=$(gtimeout 30 curl -s -N -H "Accept: text/event-stream" http://192.168.4.1/events 2>&1 | grep -c "^data:"); echo "Total: $count events, $(echo "$count / 4" | bc -l) events / second" ) & done; + // + // With AsyncTCP, with 16 workers: a lot of "Event message queue overflow: discard message", no crash + // + // Total: 1711 events, 427.75 events / second + // Total: 1711 events, 427.75 events / second + // Total: 1626 events, 406.50 events / second + // Total: 1562 events, 390.50 events / second + // Total: 1706 events, 426.50 events / second + // Total: 1659 events, 414.75 events / second + // Total: 1624 events, 406.00 events / second + // Total: 1706 events, 426.50 events / second + // Total: 1487 events, 371.75 events / second + // Total: 1573 events, 393.25 events / second + // Total: 1569 events, 392.25 events / second + // Total: 1559 events, 389.75 events / second + // Total: 1560 events, 390.00 events / second + // Total: 1562 events, 390.50 events / second + // Total: 1626 events, 406.50 events / second + // + // With AsyncTCP, with 10 workers: + // + // Total: 2038 events, 509.50 events / second + // Total: 2120 events, 530.00 events / second + // Total: 2119 events, 529.75 events / second + // Total: 2038 events, 509.50 events / second + // Total: 2037 events, 509.25 events / second + // Total: 2119 events, 529.75 events / second + // Total: 2119 events, 529.75 events / second + // Total: 2120 events, 530.00 events / second + // Total: 2038 events, 509.50 events / second + // Total: 2038 events, 509.50 events / second + // + // With AsyncTCPSock, with 16 workers: ESP32 CRASH !!! + // + // With AsyncTCPSock, with 10 workers: + // + // Total: 1242 events, 310.50 events / second + // Total: 1242 events, 310.50 events / second + // Total: 1242 events, 310.50 events / second + // Total: 1242 events, 310.50 events / second + // Total: 1181 events, 295.25 events / second + // Total: 1182 events, 295.50 events / second + // Total: 1240 events, 310.00 events / second + // Total: 1181 events, 295.25 events / second + // Total: 1181 events, 295.25 events / second + // Total: 1183 events, 295.75 events / second + // + server.addHandler(&events); + + server.begin(); +} + +static uint32_t lastSSE = 0; +static uint32_t deltaSSE = 10; + +static uint32_t lastHeap = 0; + +void loop() { + uint32_t now = millis(); + if (now - lastSSE >= deltaSSE) { + events.send(String("ping-") + now, "heartbeat", now); + lastSSE = millis(); + } + +#ifdef ESP32 + if (now - lastHeap >= 2000) { + Serial.printf("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..86c25148 --- /dev/null +++ b/examples/RateLimit/RateLimit.ino @@ -0,0 +1,62 @@ +// 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..b26320ca --- /dev/null +++ b/examples/Redirect/Redirect.ino @@ -0,0 +1,46 @@ +// 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..edde27e8 --- /dev/null +++ b/examples/ResumableDownload/ResumableDownload.ino @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Make sure resumable downloads can be implemented (HEAD request / response and Range header) +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) +#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() {} diff --git a/examples/Rewrite/Rewrite.ino b/examples/Rewrite/Rewrite.ino new file mode 100644 index 00000000..3db89628 --- /dev/null +++ b/examples/Rewrite/Rewrite.ino @@ -0,0 +1,50 @@ +// 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/SlowChunkResponse/SlowChunkResponse.ino b/examples/SlowChunkResponse/SlowChunkResponse.ino new file mode 100644 index 00000000..5007e55d --- /dev/null +++ b/examples/SlowChunkResponse/SlowChunkResponse.ino @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov + +// +// Simulate a slow response in a chunk response (like file download from SD Card) +// poll events will be throttled. +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +static const char *htmlContent PROGMEM = R"( + + + + Sample HTML + + +

Hello, World!

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+

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

+ + +)"; + +static const size_t htmlContentLength = strlen_P(htmlContent); +static constexpr char characters[] = "0123456789ABCDEF"; +static size_t charactersIndex = 0; + +void setup() { + Serial.begin(115200); + +#ifndef CONFIG_IDF_TARGET_ESP32H2 + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // curl -v http://192.168.4.1/ + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + // need to cast to uint8_t* + // if you do not, the const char* will be copied in a temporary String buffer + request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength); + }); + + // IMPORTANT - DO NOT WRITE SUCH CODE IN PRODUCTON ! + // + // This example simulates the slowdown that can happen when: + // - downloading a huge file from sdcard + // - doing some file listing on SDCard because it is horribly slow to get a file listing with file stats on SDCard. + // So in both cases, ESP would deadlock or TWDT would trigger. + // + // This example simulats that by slowing down the chunk callback: + // - d=2000 is the delay in ms in the callback + // - l=10000 is the length of the response + // + // time curl -N -v -G -d 'd=2000' -d 'l=10000' http://192.168.4.1/slow.html --output - + // + server.on("/slow.html", HTTP_GET, [](AsyncWebServerRequest *request) { + uint32_t d = request->getParam("d")->value().toInt(); + uint32_t l = request->getParam("l")->value().toInt(); + Serial.printf("d = %" PRIu32 ", l = %" PRIu32 "\n", d, l); + AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [d, l](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + Serial.printf("%u\n", index); + // finished ? + if (index >= l) { + return 0; + } + + // slow down the task to simulate some heavy processing, like SD card reading + delay(d); + + memset(buffer, characters[charactersIndex], 256); + charactersIndex = (charactersIndex + 1) % sizeof(characters); + return 256; + }); + + request->send(response); + }); + + server.begin(); +} + +static uint32_t lastHeap = 0; + +void loop() { +#ifdef ESP32 + uint32_t now = millis(); + if (now - lastHeap >= 2000) { + Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap()); + lastHeap = now; + } +#endif +} diff --git a/examples/StaticFile/StaticFile.ino b/examples/StaticFile/StaticFile.ino new file mode 100644 index 00000000..7ff2131b --- /dev/null +++ b/examples/StaticFile/StaticFile.ino @@ -0,0 +1,120 @@ +// 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..e0d0173a --- /dev/null +++ b/examples/Templates/Templates.ino @@ -0,0 +1,97 @@ +// 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 0343f3b4..c99fffaf 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,12 +1,32 @@ [platformio] default_envs = arduino-2, arduino-3, esp8266, raspberrypi lib_dir = . +src_dir = examples/All +; 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/ChunkRetryResponse +; 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 +50,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.3 board_build.partitions = partitions-4MB.csv @@ -54,15 +72,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