Update na verzi 3.7.2. POZOR: vyzaduje do platformio.ini doplnit lib_compat_mode = strict

This commit is contained in:
Pavel Brychta 2025-03-06 07:56:54 +01:00
parent d55bb23fef
commit b12c714d5e
92 changed files with 7585 additions and 6187 deletions

View File

@ -6,12 +6,4 @@ set(COMPONENT_ADD_INCLUDEDIRS
"src"
)
set(COMPONENT_REQUIRES
"arduino-esp32"
"AsyncTCP"
)
register_component()
target_compile_definitions(${COMPONENT_TARGET} PUBLIC -DESP32)
target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti)

View File

@ -6,7 +6,7 @@
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
identity and expression, level of experience, education, socioeconomic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

1968
README.md

File diff suppressed because it is too large Load Diff

48
data/README.md Normal file
View File

@ -0,0 +1,48 @@
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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.

157
examples/Auth/Auth.ino Normal file
View File

@ -0,0 +1,157 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Authentication and authorization middlewares
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
// basicAuth
static AsyncAuthenticationMiddleware basicAuth;
static AsyncAuthenticationMiddleware basicAuthHash;
// simple digest authentication
static AsyncAuthenticationMiddleware digestAuth;
static AsyncAuthenticationMiddleware digestAuthHash;
// complex authentication which adds request attributes for the next middlewares and handler
static AsyncMiddlewareFunction complexAuth([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
if (!request->authenticate("user", "password")) {
return request->requestAuthentication();
}
// add attributes to the request for the next middlewares and handler
request->setAttribute("user", "Mathieu");
request->setAttribute("role", "staff");
if (request->hasParam("token")) {
request->setAttribute("token", request->getParam("token")->value().c_str());
}
next();
});
static AsyncAuthorizationMiddleware authz([](AsyncWebServerRequest *request) {
return request->getAttribute("token") == "123";
});
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// basic authentication
basicAuth.setUsername("admin");
basicAuth.setPassword("admin");
basicAuth.setRealm("MyApp");
basicAuth.setAuthFailureMessage("Authentication failed");
basicAuth.setAuthType(AsyncAuthType::AUTH_BASIC);
basicAuth.generateHash(); // precompute hash (optional but recommended)
// basic authentication with hash
basicAuthHash.setUsername("admin");
basicAuthHash.setPasswordHash("YWRtaW46YWRtaW4="); // BASE64(admin:admin)
basicAuthHash.setRealm("MyApp");
basicAuthHash.setAuthFailureMessage("Authentication failed");
basicAuthHash.setAuthType(AsyncAuthType::AUTH_BASIC);
// digest authentication
digestAuth.setUsername("admin");
digestAuth.setPassword("admin");
digestAuth.setRealm("MyApp");
digestAuth.setAuthFailureMessage("Authentication failed");
digestAuth.setAuthType(AsyncAuthType::AUTH_DIGEST);
digestAuth.generateHash(); // precompute hash (optional but recommended)
// digest authentication with hash
digestAuthHash.setUsername("admin");
digestAuthHash.setPasswordHash("f499b71f9a36d838b79268e145e132f7"); // MD5(user:realm:pass)
digestAuthHash.setRealm("MyApp");
digestAuthHash.setAuthFailureMessage("Authentication failed");
digestAuthHash.setAuthType(AsyncAuthType::AUTH_DIGEST);
// basic authentication method
// curl -v -u admin:admin http://192.168.4.1/auth-basic
server
.on(
"/auth-basic", HTTP_GET,
[](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
}
)
.addMiddleware(&basicAuth);
// basic authentication method with hash
// curl -v -u admin:admin http://192.168.4.1/auth-basic-hash
server
.on(
"/auth-basic-hash", HTTP_GET,
[](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
}
)
.addMiddleware(&basicAuthHash);
// digest authentication
// curl -v -u admin:admin --digest http://192.168.4.1/auth-digest
server
.on(
"/auth-digest", HTTP_GET,
[](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
}
)
.addMiddleware(&digestAuth);
// digest authentication with hash
// curl -v -u admin:admin --digest http://192.168.4.1/auth-digest-hash
server
.on(
"/auth-digest-hash", HTTP_GET,
[](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
}
)
.addMiddleware(&digestAuthHash);
// test digest auth custom authorization middleware
// curl -v --digest -u user:password http://192.168.4.1/auth-custom?token=123 => OK
// curl -v --digest -u user:password http://192.168.4.1/auth-custom?token=456 => 403
// curl -v --digest -u user:FAILED http://192.168.4.1/auth-custom?token=456 => 401
server
.on(
"/auth-custom", HTTP_GET,
[](AsyncWebServerRequest *request) {
String buffer = "Hello ";
buffer.concat(request->getAttribute("user"));
buffer.concat(" with role: ");
buffer.concat(request->getAttribute("role"));
request->send(200, "text/plain", buffer);
}
)
.addMiddlewares({&complexAuth, &authz});
server.begin();
}
// not needed
void loop() {
delay(100);
}

60
examples/CORS/CORS.ino Normal file
View File

@ -0,0 +1,60 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// How to use CORS middleware
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static AsyncCorsMiddleware cors;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
cors.setOrigin("http://192.168.4.1");
cors.setMethods("POST, GET, OPTIONS, DELETE");
cors.setHeaders("X-Custom-Header");
cors.setAllowCredentials(false);
cors.setMaxAge(600);
server.addMiddleware(&cors);
// Test CORS preflight request
// curl -v -X OPTIONS -H "origin: http://192.168.4.1" http://192.168.4.1/cors
//
// Test CORS request
// curl -v -H "origin: http://192.168.4.1" http://192.168.4.1/cors
//
// Test non-CORS request
// curl -v http://192.168.4.1/cors
//
server.on("/cors", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include <DNSServer.h>
#ifdef ESP32
#include <AsyncTCP.h>
@ -5,14 +8,14 @@
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040)
#include <WebServer.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include "ESPAsyncWebServer.h"
DNSServer dnsServer;
AsyncWebServer server(80);
static DNSServer dnsServer;
static AsyncWebServer server(80);
class CaptiveRequestHandler : public AsyncWebHandler {
public:
@ -23,7 +26,7 @@ class CaptiveRequestHandler : public AsyncWebHandler {
void handleRequest(AsyncWebServerRequest *request) {
AsyncResponseStream *response = request->beginResponseStream("text/html");
response->print("<!DOCTYPE html><html><head><title>Captive Portal</title></head><body>");
response->print("<p>This is out captive portal front page.</p>");
response->print("<p>This is our captive portal front page.</p>");
response->printf("<p>You were trying to reach: http://%s%s</p>", request->host().c_str(), request->url().c_str());
#ifndef CONFIG_IDF_TARGET_ESP32H2
response->printf("<p>Try opening <a href='http://%s'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str());
@ -41,8 +44,7 @@ void setup() {
#ifndef CONFIG_IDF_TARGET_ESP32H2
if (!WiFi.softAP("esp-captive")) {
Serial.println("Soft AP creation failed.");
while (1)
;
while (1);
}
dnsServer.start(53, "*", WiFi.softAPIP());

View File

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

View File

@ -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 <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// first time: serves the file and cache headers
// curl -N -v http://192.168.4.1/ --output -
//
// secodn time: serves 304
// curl -N -v -H "if-none-match: 4272" http://192.168.4.1/ --output -
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
String etag = String(htmlContentLength);
if (request->header(asyncsrv::T_INM) == etag) {
request->send(304);
return;
}
AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
Serial.printf("%u / %u\n", index, htmlContentLength);
// finished ?
if (htmlContentLength <= index) {
Serial.println("finished");
return 0;
}
// serve a maximum of 256 or maxLen bytes of the remaining content
// this small number is specifically chosen to demonstrate the chunking
// DO NOT USE SUCH SMALL NUMBER IN PRODUCTION
// Reducing the chunk size will increase the response time, thus reducing the server's capacity in processing concurrent requests
const int chunkSize = min((size_t)256, min(maxLen, htmlContentLength - index));
Serial.printf("sending: %u\n", chunkSize);
memcpy(buffer, htmlContent + index, chunkSize);
return chunkSize;
});
response->addHeader(asyncsrv::T_Cache_Control, "public,max-age=60");
response->addHeader(asyncsrv::T_ETag, etag);
request->send(response);
});
server.begin();
}
void loop() {
delay(100);
}

View File

@ -0,0 +1,216 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Shows how to wait in a chunk response for incoming data
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#if __has_include("ArduinoJson.h")
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <AsyncMessagePack.h>
#endif
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
static AsyncWebServer server(80);
static AsyncLoggingMiddleware requestLogger;
static String triggerUART;
static int key = -1;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// adds some internal request logging for debugging
requestLogger.setEnabled(true);
requestLogger.setOutput(Serial);
server.addMiddleware(&requestLogger);
#if __has_include("ArduinoJson.h")
//
// HOW TO RUN THIS EXAMPLE:
//
// 1. Trigger a request that will be blocked for a long time:
// > time curl -v -X POST http://192.168.4.1/api -H "Content-Type: application/json" -d '{"input": "Please type a key to continue in Serial console..."}' --output -
//
// 2. While waiting, in another terminal, run some concurrent requests:
// > time curl -v http://192.168.4.1/
//
// 3. Type a key in the Serial console to continue the processing within 30 seconds.
// This should unblock the first request.
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
// need to cast to uint8_t*
// if you do not, the const char* will be copied in a temporary String buffer
request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
});
server.on(
"/api", HTTP_POST,
[](AsyncWebServerRequest *request) {
// request parsing has finished
// no data ?
if (!((String *)request->_tempObject)->length()) {
request->send(400);
return;
}
JsonDocument doc;
// deserialize and check for errors
if (deserializeJson(doc, *(String *)request->_tempObject)) {
request->send(400);
return;
}
// start UART com: UART will send the data to the Serial console and wait for the key press
triggerUART = doc["input"].as<const char *>();
key = -1;
AsyncWebServerResponse *response = request->beginChunkedResponse("text/plain", [](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
// still waiting for UARY ?
if (triggerUART.length() && key == -1) {
return RESPONSE_TRY_AGAIN;
}
// finished ?
if (!triggerUART.length() && key == -1) {
return 0; // 0 means we are done
}
// log_d("UART answered!");
String answer = "You typed: ";
answer.concat((char)key);
// note: I did not check for maxLen, but you should (see ChunkResponse.ino)
memcpy(buffer, answer.c_str(), answer.length());
// finish!
triggerUART = emptyString;
key = -1;
return answer.length();
});
request->send(response);
},
NULL, // upload handler is not used so it should be NULL
[](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
// log_d("Body: index: %u, len: %u, total: %u", index, len, total);
if (!index) {
// log_d("Start body parsing");
request->_tempObject = new String();
// cast request->_tempObject pointer to String and reserve total size
((String *)request->_tempObject)->reserve(total);
// set timeout 30s
request->client()->setRxTimeout(30);
}
// log_d("Append body data");
((String *)request->_tempObject)->concat((const char *)data, len);
}
);
#endif
server.begin();
}
void loop() {
if (triggerUART.length() && key == -1) {
Serial.println(triggerUART);
// log_d("Waiting for UART input...");
while (!Serial.available()) {
delay(100);
}
key = Serial.read();
Serial.flush();
// log_d("UART input: %c", key);
triggerUART = emptyString;
}
}

View File

@ -0,0 +1,49 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// https://github.com/ESP32Async/ESPAsyncWebServer/discussions/23
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world");
});
server.begin();
Serial.println("begin() - run: curl -v http://192.168.4.1/ => should succeed");
delay(10000);
Serial.println("end()");
server.end();
server.begin();
Serial.println("begin() - run: curl -v http://192.168.4.1/ => should succeed");
}
// not needed
void loop() {
delay(100);
}

View File

@ -1,4 +1,9 @@
// Reproduced issue https://github.com/ESP32Async/ESPAsyncWebServer/issues/26
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Shows how to use setFilter to route requests to different handlers based on WiFi mode
//
#include <DNSServer.h>
#ifdef ESP32
@ -7,14 +12,14 @@
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040)
#include <WebServer.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include "ESPAsyncWebServer.h"
DNSServer dnsServer;
AsyncWebServer server(80);
static DNSServer dnsServer;
static AsyncWebServer server(80);
class CaptiveRequestHandler : public AsyncWebHandler {
public:
@ -42,7 +47,9 @@ void setup() {
Serial.begin(115200);
server
.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
.on(
"/", HTTP_GET,
[](AsyncWebServerRequest *request) {
Serial.println("Captive portal request...");
#ifndef CONFIG_IDF_TARGET_ESP32H2
Serial.println("WiFi.localIP(): " + WiFi.localIP().toString());
@ -61,11 +68,14 @@ void setup() {
#endif
request->send(200, "text/plain", "This is the captive portal");
hit1 = true;
})
}
)
.setFilter(ON_AP_FILTER);
server
.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
.on(
"/", HTTP_GET,
[](AsyncWebServerRequest *request) {
Serial.println("Website request...");
#ifndef CONFIG_IDF_TARGET_ESP32H2
Serial.println("WiFi.localIP(): " + WiFi.localIP().toString());
@ -84,7 +94,8 @@ void setup() {
#endif
request->send(200, "text/plain", "This is the website");
hit2 = true;
})
}
)
.setFilter(ON_STA_FILTER);
// assert(WiFi.softAP("esp-captive-portal"));
@ -121,4 +132,5 @@ void setup() {
}
void loop() {
delay(100);
}

View File

@ -0,0 +1,107 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Shows how to serve a large HTML page from flash memory without copying it to heap in a temporary buffer
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// curl -v http://192.168.4.1/
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
// need to cast to uint8_t*
// if you do not, the const char* will be copied in a temporary String buffer
request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@ -0,0 +1,88 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Show how to manipulate headers in the request / response
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
// request logger
static AsyncLoggingMiddleware requestLogger;
// filter out specific headers from the incoming request
static AsyncHeaderFilterMiddleware headerFilter;
// remove all headers from the incoming request except the ones provided in the constructor
AsyncHeaderFreeMiddleware headerFree;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
requestLogger.setEnabled(true);
requestLogger.setOutput(Serial);
headerFilter.filter("X-Remove-Me");
headerFree.keep("X-Keep-Me");
headerFree.keep("host");
server.addMiddlewares({&requestLogger, &headerFilter});
// x-remove-me header will be removed
//
// curl -v -H "X-Header: Foo" -H "x-remove-me: value" http://192.168.4.1/remove
//
server.on("/remove", HTTP_GET, [](AsyncWebServerRequest *request) {
// print all headers
for (size_t i = 0; i < request->headers(); i++) {
const AsyncWebHeader *h = request->getHeader(i);
Serial.printf("Header[%s]: %s\n", h->name().c_str(), h->value().c_str());
}
request->send(200, "text/plain", "Hello, world!");
});
// Only headers x-keep-me and host will be kept
//
// curl -v -H "x-keep-me: value" -H "x-remove-me: value" http://192.168.4.1/keep
//
server
.on(
"/keep", HTTP_GET,
[](AsyncWebServerRequest *request) {
// print all headers
for (size_t i = 0; i < request->headers(); i++) {
const AsyncWebHeader *h = request->getHeader(i);
Serial.printf("Header[%s]: %s\n", h->name().c_str(), h->value().c_str());
}
request->send(200, "text/plain", "Hello, world!");
}
)
.addMiddleware(&headerFree);
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@ -0,0 +1,69 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Query and send headers
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
//
// curl -v http://192.168.4.1
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
//List all collected headers
int headers = request->headers();
int i;
for (i = 0; i < headers; i++) {
const AsyncWebHeader *h = request->getHeader(i);
Serial.printf("HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str());
}
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Hello World!");
//Add header to the response
response->addHeader("Server", "ESP Async Web Server");
//Add multiple headers with the same name
response->addHeader("Set-Cookie", "sessionId=38afes7a8", false);
response->addHeader("Set-Cookie", "id=a3fWa; Max-Age=2592000", false);
response->addHeader("Set-Cookie", "qwerty=219ffwef9w0f; Domain=example.com", false);
//Remove specific header
response->removeHeader("Set-Cookie", "sessionId=38afes7a8");
//Remove all headers with the same name
response->removeHeader("Set-Cookie");
request->send(response);
});
server.begin();
}
void loop() {
//Sleep in the loop task to not keep the CPU busy
delay(1000);
}

View File

@ -1,99 +0,0 @@
/**
*
* Connect to AP and run in bash:
*
* > while true; do echo "PING"; sleep 0.1; done | websocat ws://192.168.4.1/ws
*
*/
#include <Arduino.h>
#ifdef ESP8266
#include <ESP8266WiFi.h>
#endif
#ifdef ESP32
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <list>
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(
<!DOCTYPE html>
<html>
<head></head>
<script>
let ws = new WebSocket("ws://" + window.location.host + "/ws");
ws.addEventListener("open", (e) => {
console.log("WebSocket connected", e);
});
ws.addEventListener("error", (e) => {
console.log("WebSocket error", e);
});
ws.addEventListener("close", (e) => {
console.log("WebSocket close", e);
});
ws.addEventListener("message", (e) => {
console.log("WebSocket message", e);
});
function closeAllWsClients() {
fetch("/close_all_ws_clients", {
method : "POST",
});
};
</script>
<body>
<button onclick = "closeAllWsClients()" style = "width: 200px"> ws close all</button><p></p>
</body>
</html>
)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();
}

View File

@ -1,127 +0,0 @@
/**
*
* Connect to AP and run in bash:
*
* > while true; do echo "PING"; sleep 0.1; done | websocat ws://192.168.4.1/ws
*
*/
#include <Arduino.h>
#ifdef ESP8266
#include <ESP8266WiFi.h>
#endif
#ifdef ESP32
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <list>
size_t msgCount = 0;
size_t window = 100;
std::list<uint32_t> 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';
// }
// }
}

90
examples/Json/Json.ino Normal file
View File

@ -0,0 +1,90 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Shows how to send and receive Json data
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#if __has_include("ArduinoJson.h")
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <AsyncMessagePack.h>
#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<JsonObject>();
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<JsonObject>();
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<JsonObject>();
root["hello"] = json.as<JsonObject>()["name"];
response->setLength();
request->send(response);
});
server.addHandler(handler);
#endif
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@ -0,0 +1,49 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Show how to log the incoming request and response as a curl-like syntax
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static AsyncLoggingMiddleware requestLogger;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
requestLogger.setEnabled(true);
requestLogger.setOutput(Serial);
server.addMiddleware(&requestLogger);
// curl -v -H "X-Header:Foo" http://192.168.4.1/
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@ -0,0 +1,88 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Shows how to send and receive Message Pack data
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#if __has_include("ArduinoJson.h")
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <AsyncMessagePack.h>
#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<JsonObject>();
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<JsonObject>();
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<JsonObject>();
root["hello"] = json.as<JsonObject>()["name"];
response->setLength();
request->send(response);
});
server.addHandler(handler);
#endif
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@ -0,0 +1,82 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Show how to sue Middleware
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
// New middleware classes can be created!
class MyMiddleware : public AsyncMiddleware {
public:
void run(AsyncWebServerRequest *request, ArMiddlewareNext next) override {
Serial.printf("Before handler: %s %s\n", request->methodToString(), request->url().c_str());
next(); // continue middleware chain
Serial.printf("After handler: response code=%d\n", request->getResponse()->code());
}
};
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// add a global middleware to the server
server.addMiddleware(new MyMiddleware());
// Test with:
//
// - curl -v http://192.168.4.1/ => 200 OK
// - curl -v http://192.168.4.1/?user=anon => 403 Forbidden
// - curl -v http://192.168.4.1/?user=foo => 200 OK
// - curl -v http://192.168.4.1/?user=error => 400 ERROR
//
AsyncCallbackWebHandler &handler = server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.printf("In Handler: %s %s\n", request->methodToString(), request->url().c_str());
request->send(200, "text/plain", "Hello, world!");
});
// add a middleware to this handler only to send 403 if the user is anon
handler.addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
Serial.println("Checking user=anon");
if (request->hasParam("user") && request->getParam("user")->value() == "anon") {
request->send(403, "text/plain", "Forbidden");
} else {
next();
}
});
// add a middleware to this handler that will replace the previously created response by another one
handler.addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
next();
Serial.println("Checking user=error");
if (request->hasParam("user") && request->getParam("user")->value() == "error") {
request->send(400, "text/plain", "ERROR");
}
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

122
examples/Params/Params.ino Normal file
View File

@ -0,0 +1,122 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Query parameters and body parameters
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>POST Request with Multiple Parameters</title>
</head>
<body>
<form action="http://192.168.4.1" method="POST">
<label for="who">Who?</label>
<input type="text" id="who" name="who" value="Carl"><br>
<label for="g0">g0:</label>
<input type="text" id="g0" name="g0" value="1"><br>
<label for="a0">a0:</label>
<input type="text" id="a0" name="a0" value="2"><br>
<label for="n0">n0:</label>
<input type="text" id="n0" name="n0" value="3"><br>
<label for="t10">t10:</label>
<input type="text" id="t10" name="t10" value="3"><br>
<label for="t20">t20:</label>
<input type="text" id="t20" name="t20" value="4"><br>
<label for="t30">t30:</label>
<input type="text" id="t30" name="t30" value="5"><br>
<label for="t40">t40:</label>
<input type="text" id="t40" name="t40" value="6"><br>
<label for="t50">t50:</label>
<input type="text" id="t50" name="t50" value="7"><br>
<label for="g1">g1:</label>
<input type="text" id="g1" name="g1" value="2"><br>
<label for="a1">a1:</label>
<input type="text" id="a1" name="a1" value="2"><br>
<label for="n1">n1:</label>
<input type="text" id="n1" name="n1" value="3"><br>
<label for="t11">t11:</label>
<input type="text" id="t11" name="t11" value="13"><br>
<label for="t21">t21:</label>
<input type="text" id="t21" name="t21" value="14"><br>
<label for="t31">t31:</label>
<input type="text" id="t31" name="t31" value="15"><br>
<label for="t41">t41:</label>
<input type="text" id="t41" name="t41" value="16"><br>
<label for="t51">t51:</label>
<input type="text" id="t51" name="t51" value="17"><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// Get query parameters
//
// curl -v http://192.168.4.1/?who=Bob
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
if (request->hasParam("who")) {
Serial.printf("Who? %s\n", request->getParam("who")->value().c_str());
}
request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
});
// Get form body parameters
//
// curl -v -H "Content-Type: application/x-www-form-urlencoded" -d "who=Carl" -d "param=value" http://192.168.4.1/
//
server.on("/", HTTP_POST, [](AsyncWebServerRequest *request) {
// display params
size_t count = request->params();
for (size_t i = 0; i < count; i++) {
const AsyncWebParameter *p = request->getParam(i);
Serial.printf("PARAM[%u]: %s = %s\n", i, p->name().c_str(), p->value().c_str());
}
// get who param
String who;
if (request->hasParam("who", true)) {
who = request->getParam("who", true)->value();
} else {
who = "No message sent";
}
request->send(200, "text/plain", "Hello " + who + "!");
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@ -0,0 +1,130 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// - Download ESP32 partition by name and/or type and/or subtype
// - Support encrypted and non-encrypted partitions
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#ifndef ESP32
// this example is only for the ESP32
void setup() {}
void loop() {}
#else
#include <esp_partition.h>
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
LittleFS.begin(true);
// To upload the FS partition, run:
// > pio run -e arduino-3 -t buildfs
// > pio run -e arduino-3 -t uploadfs
//
// Examples:
//
// - Download the partition named "spiffs": http://192.168.4.1/partition?label=spiffs
// - Download the partition named "spiffs" with type "data": http://192.168.4.1/partition?label=spiffs&type=1
// - Download the partition named "spiffs" with type "data" and subtype "spiffs": http://192.168.4.1/partition?label=spiffs&type=1&subtype=130
// - Download the partition with subtype "nvs": http://192.168.4.1/partition?type=1&subtype=2
//
// "type" and "subtype" IDs can be found in esp_partition.h header file.
//
// Add "&raw=false" parameter to download the partition unencrypted (for encrypted partitions).
// By default, the raw partition is downloaded, so if a partition is encrypted, the encrypted data will be downloaded.
//
// To browse a downloaded LittleFS partition, you can use https://tniessen.github.io/littlefs-disk-img-viewer/ (block size is 4096)
//
server.on("/partition", HTTP_GET, [](AsyncWebServerRequest *request) {
const AsyncWebParameter *pLabel = request->getParam("label");
const AsyncWebParameter *pType = request->getParam("type");
const AsyncWebParameter *pSubtype = request->getParam("subtype");
const AsyncWebParameter *pRaw = request->getParam("raw");
if (!pLabel && !pType && !pSubtype) {
request->send(400, "text/plain", "Bad request: missing parameter");
return;
}
esp_partition_type_t type = ESP_PARTITION_TYPE_ANY;
esp_partition_subtype_t subtype = ESP_PARTITION_SUBTYPE_ANY;
const char *label = nullptr;
bool raw = true;
if (pLabel) {
label = pLabel->value().c_str();
}
if (pType) {
type = (esp_partition_type_t)pType->value().toInt();
}
if (pSubtype) {
subtype = (esp_partition_subtype_t)pSubtype->value().toInt();
}
if (pRaw && pRaw->value() == "false") {
raw = false;
}
const esp_partition_t *partition = esp_partition_find_first(type, subtype, label);
if (!partition) {
request->send(404, "text/plain", "Partition not found");
return;
}
AsyncWebServerResponse *response =
request->beginChunkedResponse("application/octet-stream", [partition, raw](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
const size_t remaining = partition->size - index;
if (!remaining) {
return 0;
}
const size_t len = std::min(maxLen, remaining);
if (raw && esp_partition_read_raw(partition, index, buffer, len) == ESP_OK) {
return len;
}
if (!raw && esp_partition_read(partition, index, buffer, len) == ESP_OK) {
return len;
}
return 0;
});
response->addHeader("Content-Disposition", "attachment; filename=" + String(partition->label) + ".bin");
response->setContentLength(partition->size);
request->send(response);
});
server.begin();
}
void loop() {
delay(100);
}
#endif

View File

@ -0,0 +1,243 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Perf tests
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
static constexpr char characters[] = "0123456789ABCDEF";
static size_t charactersIndex = 0;
static AsyncWebServer server(80);
static AsyncEventSource events("/events");
static volatile size_t requests = 0;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// Pauses in the request parsing phase
//
// autocannon -c 32 -w 32 -a 96 -t 30 --renderStatusCodes -m POST -H "Content-Type: application/json" -b '{"foo": "bar"}' http://192.168.4.1/delay
//
// curl -v -X POST -H "Content-Type: application/json" -d '{"game": "test"}' http://192.168.4.1/delay
//
server.onNotFound([](AsyncWebServerRequest *request) {
requests++;
if (request->url() == "/delay") {
request->send(200, "application/json", "{\"status\":\"OK\"}");
} else {
request->send(404, "text/plain", "Not found");
}
});
server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
if (request->url() == "/delay") {
delay(3000);
}
});
// HTTP endpoint
//
// > brew install autocannon
// > autocannon -c 10 -w 10 -d 20 http://192.168.4.1
// > autocannon -c 16 -w 16 -d 20 http://192.168.4.1
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
// need to cast to uint8_t*
// if you do not, the const char* will be copied in a temporary String buffer
requests++;
request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
});
// IMPORTANT - DO NOT WRITE SUCH CODE IN PRODUCTON !
//
// This example simulates the slowdown that can happen when:
// - downloading a huge file from sdcard
// - doing some file listing on SDCard because it is horribly slow to get a file listing with file stats on SDCard.
// So in both cases, ESP would deadlock or TWDT would trigger.
//
// This example simulats that by slowing down the chunk callback:
// - d=2000 is the delay in ms in the callback
// - l=10000 is the length of the response
//
// time curl -N -v -G -d 'd=2000' -d 'l=10000' http://192.168.4.1/slow.html --output -
//
server.on("/slow.html", HTTP_GET, [](AsyncWebServerRequest *request) {
requests++;
uint32_t d = request->getParam("d")->value().toInt();
uint32_t l = request->getParam("l")->value().toInt();
Serial.printf("d = %" PRIu32 ", l = %" PRIu32 "\n", d, l);
AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [d, l](uint8_t *buffer, size_t maxLen, size_t index) -> size_t {
Serial.printf("%u\n", index);
// finished ?
if (index >= l) {
return 0;
}
// slow down the task to simulate some heavy processing, like SD card reading
delay(d);
memset(buffer, characters[charactersIndex], 256);
charactersIndex = (charactersIndex + 1) % sizeof(characters);
return 256;
});
request->send(response);
});
// SSS endpoint
//
// launch 16 concurrent workers for 30 seconds
// > for i in {1..10}; do ( count=$(gtimeout 30 curl -s -N -H "Accept: text/event-stream" http://192.168.4.1/events 2>&1 | grep -c "^data:"); echo "Total: $count events, $(echo "$count / 4" | bc -l) events / second" ) & done;
// > for i in {1..16}; do ( count=$(gtimeout 30 curl -s -N -H "Accept: text/event-stream" http://192.168.4.1/events 2>&1 | grep -c "^data:"); echo "Total: $count events, $(echo "$count / 4" | bc -l) events / second" ) & done;
//
// With AsyncTCP, with 16 workers: a lot of "Event message queue overflow: discard message", no crash
//
// Total: 1711 events, 427.75 events / second
// Total: 1711 events, 427.75 events / second
// Total: 1626 events, 406.50 events / second
// Total: 1562 events, 390.50 events / second
// Total: 1706 events, 426.50 events / second
// Total: 1659 events, 414.75 events / second
// Total: 1624 events, 406.00 events / second
// Total: 1706 events, 426.50 events / second
// Total: 1487 events, 371.75 events / second
// Total: 1573 events, 393.25 events / second
// Total: 1569 events, 392.25 events / second
// Total: 1559 events, 389.75 events / second
// Total: 1560 events, 390.00 events / second
// Total: 1562 events, 390.50 events / second
// Total: 1626 events, 406.50 events / second
//
// With AsyncTCP, with 10 workers:
//
// Total: 2038 events, 509.50 events / second
// Total: 2120 events, 530.00 events / second
// Total: 2119 events, 529.75 events / second
// Total: 2038 events, 509.50 events / second
// Total: 2037 events, 509.25 events / second
// Total: 2119 events, 529.75 events / second
// Total: 2119 events, 529.75 events / second
// Total: 2120 events, 530.00 events / second
// Total: 2038 events, 509.50 events / second
// Total: 2038 events, 509.50 events / second
//
// With AsyncTCPSock, with 16 workers: ESP32 CRASH !!!
//
// With AsyncTCPSock, with 10 workers:
//
// Total: 1242 events, 310.50 events / second
// Total: 1242 events, 310.50 events / second
// Total: 1242 events, 310.50 events / second
// Total: 1242 events, 310.50 events / second
// Total: 1181 events, 295.25 events / second
// Total: 1182 events, 295.50 events / second
// Total: 1240 events, 310.00 events / second
// Total: 1181 events, 295.25 events / second
// Total: 1181 events, 295.25 events / second
// Total: 1183 events, 295.75 events / second
//
server.addHandler(&events);
server.begin();
}
static uint32_t lastSSE = 0;
static uint32_t deltaSSE = 10;
static uint32_t lastHeap = 0;
void loop() {
uint32_t now = millis();
if (now - lastSSE >= deltaSSE) {
events.send(String("ping-") + now, "heartbeat", now);
lastSSE = millis();
}
#ifdef ESP32
if (now - lastHeap >= 2000) {
Serial.printf("Uptime: %3lu s, requests: %3u, Free heap: %" PRIu32 "\n", millis() / 1000, requests, ESP.getFreeHeap());
lastHeap = now;
}
#endif
}

View File

@ -0,0 +1,64 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Show how to rate limit the server or some endpoints
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static AsyncRateLimitMiddleware rateLimit;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// maximum 5 requests per 10 seconds
rateLimit.setMaxRequests(5);
rateLimit.setWindowSize(10);
// run quickly several times:
//
// curl -v http://192.168.4.1/
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
});
// run quickly several times:
//
// curl -v http://192.168.4.1/rate-limited
//
server
.on(
"/rate-limited", HTTP_GET,
[](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
}
)
.addMiddleware(&rateLimit); // only rate limit this endpoint, but could be applied globally to the server
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@ -0,0 +1,48 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Shows how to redirect
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// curl -v http://192.168.4.1/
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->redirect("/index.txt");
});
// curl -v http://192.168.4.1/index.txt
server.on("/index.txt", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@ -0,0 +1,91 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Shows how to use request continuation to pause a request for a long processing task, and be able to resume it later.
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <list>
#include <mutex>
static AsyncWebServer server(80);
// request handler that is saved from the paused request to communicate with Serial
static String message;
static AsyncWebServerRequestPtr serialRequest;
// request handler that is saved from the paused request to communicate with GPIO
static uint8_t pin = 35;
static AsyncWebServerRequestPtr gpioRequest;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// Post a message that will be sent to the Serial console, and pause the request until the user types a key
//
// curl -v -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "question=Name%3F%20" http://192.168.4.1/serial
//
// curl output should show "Answer: [y/n]" as the response
server.on("/serial", HTTP_POST, [](AsyncWebServerRequest *request) {
message = request->getParam("question", true)->value();
serialRequest = request->pause();
});
// Wait for a GPIO to be high
//
// curl -v http://192.168.4.1/gpio
//
// curl output should show "GPIO is high!" as the response
server.on("/gpio", HTTP_GET, [](AsyncWebServerRequest *request) {
gpioRequest = request->pause();
});
pinMode(pin, INPUT);
server.begin();
}
void loop() {
delay(500);
// Check for a high voltage on the RX1 pin
if (digitalRead(pin) == HIGH) {
if (auto request = gpioRequest.lock()) {
request->send(200, "text/plain", "GPIO is high!");
}
}
// check for an incoming message from the Serial console
if (message.length()) {
Serial.printf("%s", message.c_str());
// drops buffer
while (Serial.available()) {
Serial.read();
}
Serial.setTimeout(10000);
String response = Serial.readStringUntil('\n'); // waits for a key to be pressed
Serial.println();
message = emptyString;
if (auto request = serialRequest.lock()) {
request->send(200, "text/plain", "Answer: " + response);
}
}
}

View File

@ -0,0 +1,165 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Shows how to use request continuation to pause a request for a long processing task, and be able to resume it later.
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <list>
#include <memory>
#include <mutex>
static AsyncWebServer server(80);
// ===============================================================
// The code below is used to simulate some long running operations
// ===============================================================
typedef struct {
size_t id;
AsyncWebServerRequestPtr requestPtr;
uint8_t data;
} LongRunningOperation;
static std::list<std::unique_ptr<LongRunningOperation>> longRunningOperations;
static size_t longRunningOperationsCount = 0;
#ifdef ESP32
static std::mutex longRunningOperationsMutex;
#endif
static void startLongRunningOperation(AsyncWebServerRequestPtr &&requestPtr) {
#ifdef ESP32
std::lock_guard<std::mutex> lock(longRunningOperationsMutex);
#endif
// LongRunningOperation *op = new LongRunningOperation();
std::unique_ptr<LongRunningOperation> op(new LongRunningOperation());
op->id = ++longRunningOperationsCount;
op->data = 10;
// you need to hold the AsyncWebServerRequestPtr returned by pause();
// This object is authorized to leave the scope of the request handler.
op->requestPtr = std::move(requestPtr);
Serial.printf("[%u] Start long running operation for %" PRIu8 " seconds...\n", op->id, op->data);
longRunningOperations.push_back(std::move(op));
}
static bool processLongRunningOperation(LongRunningOperation *op) {
// request was deleted ?
if (op->requestPtr.expired()) {
Serial.printf("[%u] Request was deleted - stopping long running operation\n", op->id);
return true; // operation finished
}
// processing the operation
Serial.printf("[%u] Long running operation processing... %" PRIu8 " seconds left\n", op->id, op->data);
// check if we have finished ?
op->data--;
if (op->data) {
// not finished yet
return false;
}
// Try to get access to the request pointer if it is still exist.
// If there has been a disconnection during that time, the pointer won't be valid anymore
if (auto request = op->requestPtr.lock()) {
Serial.printf("[%u] Long running operation finished! Sending back response...\n", op->id);
request->send(200, "text/plain", String(op->id) + " ");
} else {
Serial.printf("[%u] Long running operation finished, but request was deleted!\n", op->id);
}
return true; // operation finished
}
/// ==========================================================
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// Add a middleware to see how pausing a request affects the middleware chain
server.addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
Serial.printf("Middleware chain start\n");
// continue to the next middleware, and at the end the request handler
next();
// we can check the request pause state after the handler was executed
if (request->isPaused()) {
Serial.printf("Request was paused!\n");
}
Serial.printf("Middleware chain ends\n");
});
// HOW TO RUN THIS EXAMPLE:
//
// 1. Open several terminals to trigger some requests concurrently that will be paused with:
// > time curl -v http://192.168.4.1/
//
// 2. Look at the output of the Serial console to see how the middleware chain is executed
// and to see the long running operations being processed and resume the requests.
//
// 3. You can try close your curl command to cancel the request and check that the request is deleted.
// Note: in case the network is disconnected, the request will be deleted.
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
// Print a message in case the request is disconnected (network disconnection, client close, etc.)
request->onDisconnect([]() {
Serial.printf("Request was disconnected!\n");
});
// Instruct ESPAsyncWebServer to pause the request and get a AsyncWebServerRequestPtr to be able to access the request later.
// The AsyncWebServerRequestPtr is the ONLY object authorized to leave the scope of the request handler.
// The Middleware chain will continue to run until the end after this handler exit, but the request will be paused and will not
// be sent to the client until send() is called later.
Serial.printf("Pausing request...\n");
AsyncWebServerRequestPtr requestPtr = request->pause();
// start our long operation...
startLongRunningOperation(std::move(requestPtr));
});
server.begin();
}
static uint32_t lastTime = 0;
void loop() {
if (millis() - lastTime >= 1000) {
#ifdef ESP32
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
std::lock_guard<std::mutex> lock(longRunningOperationsMutex);
#endif
// process all long running operations
longRunningOperations.remove_if([](const std::unique_ptr<LongRunningOperation> &op) {
return processLongRunningOperation(op.get());
});
lastTime = millis();
}
}

View File

@ -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 <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
/*
curl -I -X HEAD http://192.168.4.1/download
HTTP/1.1 200 OK
Content-Length: 1024
Content-Type: application/octet-stream
Connection: close
Accept-Ranges: bytes
*/
// Ref: https://github.com/mathieucarbou/ESPAsyncWebServer/pull/80
server.on("/download", HTTP_HEAD | HTTP_GET, [](AsyncWebServerRequest *request) {
if (request->method() == HTTP_HEAD) {
AsyncWebServerResponse *response = request->beginResponse(200, "application/octet-stream");
response->addHeader(asyncsrv::T_Accept_Ranges, "bytes");
response->addHeader(asyncsrv::T_Content_Length, 10);
response->setContentLength(1024); // make sure we can overrides previously set content length
response->addHeader(asyncsrv::T_Content_Type, "foo");
response->setContentType("application/octet-stream"); // make sure we can overrides previously set content type
// ...
request->send(response);
} else {
// ...
}
});
server.begin();
}
void loop() {
delay(100);
}

View File

@ -0,0 +1,52 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Shows how to rewrite URLs
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
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", "<h1>Hello, world!</h1>");
});
// curl -v http://192.168.4.1/
server.rewrite("/", "/index.html");
server.rewrite("/index.txt", "/index.html"); // will hide the .txt file
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@ -1,257 +0,0 @@
//
// 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
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040)
#include <WebServer.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#if __has_include("ArduinoJson.h")
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <AsyncMessagePack.h>
#endif
#include <LittleFS.h>
const char* htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</body>
</html>
)";
const char* staticContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, %IP%</h1>
</body>
</html>
)";
AsyncWebServer server(80);
AsyncEventSource events("/events");
/////////////////////////////////////////////////////////////////////////////////////////////////////
const char* PARAM_MESSAGE PROGMEM = "message";
const char* SSE_HTLM PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Server-Sent Events</title>
<script>
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e);
}, false);
source.addEventListener('heartbeat', function(e) {
console.log("heartbeat", e.data);
}, false);
}
</script>
</head>
<body>
<h1>Open your browser console!</h1>
</body>
</html>
)";
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;
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
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/html", staticContent);
});
events.onConnect([](AsyncEventSourceClient* client) {
if (client->lastId()) {
Serial.printf("SSE Client reconnected! Last message ID that it gat is: %" PRIu32 "\n", client->lastId());
}
client->send("hello!", NULL, millis(), 1000);
});
server.on("/sse", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/html", SSE_HTLM);
});
// go to http://192.168.4.1/sse
server.addHandler(&events);
server.onNotFound(notFound);
server.begin();
}
uint32_t lastSSE = 0;
uint32_t deltaSSE = 25;
uint32_t messagesSSE = 4; // how many messages to q each time
uint32_t sse_disc{0}, sse_enq{0}, sse_penq{0}, sse_second{0};
AsyncEventSource::SendStatus enqueue() {
AsyncEventSource::SendStatus state = events.send(SSE_MSG, "message");
if (state == AsyncEventSource::SendStatus::DISCARDED)
++sse_disc;
else if (state == AsyncEventSource::SendStatus::ENQUEUED) {
++sse_enq;
} else
++sse_penq;
return state;
}
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;
}
}
lastSSE = millis();
}
if (now - sse_second > 1000) {
String s;
s.reserve(100);
s = "Ping:";
s += now / 1000;
s += " clients:";
s += events.count();
s += " disc:";
s += sse_disc;
s += " enq:";
s += sse_enq;
s += " partial:";
s += sse_penq;
s += " avg wait:";
s += events.avgPacketsWaiting();
s += " heap:";
s += ESP.getFreeHeap() / 1024;
events.send(s, "heartbeat", now);
Serial.println();
Serial.println(s);
// if we see discards or partial enqueues, let's decrease message rate, else - increase. So that we can come to a max sustained message rate
if (sse_disc || sse_penq)
++deltaSSE;
else if (deltaSSE > 5)
--deltaSSE;
sse_disc = sse_enq = sse_penq = 0;
sse_second = now;
}
}

View File

@ -0,0 +1,105 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// SSE example
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Server-Sent Events</title>
<script>
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e.data);
}, false);
source.addEventListener('heartbeat', function(e) {
console.log("heartbeat", e.data);
}, false);
}
</script>
</head>
<body>
<h1>Open your browser console!</h1>
</body>
</html>
)";
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
}

View File

@ -1,794 +0,0 @@
//
// A simple server implementation showing how to:
// * serve static messages
// * read GET and POST parameters
// * handle missing pages / 404s
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040)
#include <WebServer.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#if __has_include("ArduinoJson.h")
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <AsyncMessagePack.h>
#endif
#include <LittleFS.h>
const char* htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</body>
</html>
)";
const size_t htmlContentLength = strlen_P(htmlContent);
const char* staticContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, %IP%</h1>
</body>
</html>
)";
AsyncWebServer server(80);
AsyncEventSource events("/events");
AsyncWebSocket ws("/ws");
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Middlewares
/////////////////////////////////////////////////////////////////////////////////////////////////////
// log incoming requests
AsyncLoggingMiddleware requestLogger;
// CORS
AsyncCorsMiddleware cors;
// maximum 5 requests per 10 seconds
AsyncRateLimitMiddleware rateLimit;
// filter out specific headers from the incoming request
AsyncHeaderFilterMiddleware headerFilter;
// remove all headers from the incoming request except the ones provided in the constructor
AsyncHeaderFreeMiddleware headerFree;
// basicAuth
AsyncAuthenticationMiddleware basicAuth;
AsyncAuthenticationMiddleware basicAuthHash;
// simple digest authentication
AsyncAuthenticationMiddleware digestAuth;
AsyncAuthenticationMiddleware digestAuthHash;
// complex authentication which adds request attributes for the next middlewares and handler
AsyncMiddlewareFunction complexAuth([](AsyncWebServerRequest* request, ArMiddlewareNext next) {
if (!request->authenticate("user", "password")) {
return request->requestAuthentication();
}
request->setAttribute("user", "Mathieu");
request->setAttribute("role", "staff");
next();
request->getResponse()->addHeader("X-Rate-Limit", "200");
});
AsyncAuthorizationMiddleware authz([](AsyncWebServerRequest* request) { return request->getAttribute("role") == "staff"; });
int wsClients = 0;
/////////////////////////////////////////////////////////////////////////////////////////////////////
const char* PARAM_MESSAGE PROGMEM = "message";
const char* SSE_HTLM PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Server-Sent Events</title>
<script>
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e.data);
}, false);
source.addEventListener('heartbeat', function(e) {
console.log("heartbeat", e.data);
}, false);
}
</script>
</head>
<body>
<h1>Open your browser console!</h1>
</body>
</html>
)";
void notFound(AsyncWebServerRequest* request) {
request->send(404, "text/plain", "Not found");
}
#if __has_include("ArduinoJson.h")
AsyncCallbackJsonWebHandler* jsonHandler = new AsyncCallbackJsonWebHandler("/json2");
AsyncCallbackMessagePackWebHandler* msgPackHandler = new AsyncCallbackMessagePackWebHandler("/msgpack2");
#endif
static const char characters[] = "0123456789ABCDEF";
static size_t charactersIndex = 0;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
// WiFi.mode(WIFI_STA);
// WiFi.begin("YOUR_SSID", "YOUR_PASSWORD");
// if (WiFi.waitForConnectResult() != WL_CONNECTED) {
// Serial.printf("WiFi Failed!\n");
// return;
// }
// Serial.print("IP Address: ");
// Serial.println(WiFi.localIP());
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
#ifdef ESP32
LittleFS.begin(true);
#else
LittleFS.begin();
#endif
{
File f = LittleFS.open("/index.txt", "w");
if (f) {
for (size_t c = 0; c < sizeof(characters); c++) {
for (size_t i = 0; i < 1024; i++) {
f.print(characters[c]);
}
}
f.close();
}
}
{
File f = LittleFS.open("/index.html", "w");
if (f) {
f.print(staticContent);
f.close();
}
}
// curl -v -X GET http://192.168.4.1/handler-not-sending-response
server.on("/handler-not-sending-response", HTTP_GET, [](AsyncWebServerRequest* request) {
// handler forgot to send a response to the client => 501 Not Implemented
});
// This is possible to replace a response.
// the previous one will be deleted.
// response sending happens when the handler returns.
// curl -v -X GET http://192.168.4.1/replace
server.on("/replace", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/plain", "Hello, world");
// oups! finally we want to send a different response
request->send(400, "text/plain", "validation error");
#ifndef TARGET_RP2040
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
#endif
});
///////////////////////////////////////////////////////////////////////
// Request header manipulations
///////////////////////////////////////////////////////////////////////
// curl -v -X GET -H "x-remove-me: value" http://192.168.4.1/headers
server.on("/headers", HTTP_GET, [](AsyncWebServerRequest* request) {
Serial.printf("Request Headers:\n");
for (auto& h : request->getHeaders())
Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str());
// remove x-remove-me header
request->removeHeader("x-remove-me");
Serial.printf("Request Headers:\n");
for (auto& h : request->getHeaders())
Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str());
std::vector<const char*> headers;
request->getHeaderNames(headers);
for (auto& h : headers)
Serial.printf("Request Header Name: %s\n", h);
request->send(200);
});
///////////////////////////////////////////////////////////////////////
// Middlewares at server level (will apply to all requests)
///////////////////////////////////////////////////////////////////////
requestLogger.setOutput(Serial);
basicAuth.setUsername("admin");
basicAuth.setPassword("admin");
basicAuth.setRealm("MyApp");
basicAuth.setAuthFailureMessage("Authentication failed");
basicAuth.setAuthType(AsyncAuthType::AUTH_BASIC);
basicAuth.generateHash();
basicAuthHash.setUsername("admin");
basicAuthHash.setPasswordHash("YWRtaW46YWRtaW4="); // BASE64(admin:admin)
basicAuthHash.setRealm("MyApp");
basicAuthHash.setAuthFailureMessage("Authentication failed");
basicAuthHash.setAuthType(AsyncAuthType::AUTH_BASIC);
digestAuth.setUsername("admin");
digestAuth.setPassword("admin");
digestAuth.setRealm("MyApp");
digestAuth.setAuthFailureMessage("Authentication failed");
digestAuth.setAuthType(AsyncAuthType::AUTH_DIGEST);
digestAuth.generateHash();
digestAuthHash.setUsername("admin");
digestAuthHash.setPasswordHash("f499b71f9a36d838b79268e145e132f7"); // MD5(user:realm:pass)
digestAuthHash.setRealm("MyApp");
digestAuthHash.setAuthFailureMessage("Authentication failed");
digestAuthHash.setAuthType(AsyncAuthType::AUTH_DIGEST);
rateLimit.setMaxRequests(5);
rateLimit.setWindowSize(10);
headerFilter.filter("X-Remove-Me");
headerFree.keep("X-Keep-Me");
headerFree.keep("host");
cors.setOrigin("http://192.168.4.1");
cors.setMethods("POST, GET, OPTIONS, DELETE");
cors.setHeaders("X-Custom-Header");
cors.setAllowCredentials(false);
cors.setMaxAge(600);
#ifndef PERF_TEST
// global middleware
server.addMiddleware(&requestLogger);
server.addMiddlewares({&rateLimit, &cors, &headerFilter});
#endif
// Test CORS preflight request
// curl -v -X OPTIONS -H "origin: http://192.168.4.1" http://192.168.4.1/middleware/cors
server.on("/middleware/cors", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/plain", "Hello, world!");
});
// curl -v -X GET -H "x-remove-me: value" http://192.168.4.1/middleware/test-header-filter
// - requestLogger will log the incoming headers (including x-remove-me)
// - headerFilter will remove x-remove-me header
// - handler will log the remaining headers
server.on("/middleware/test-header-filter", HTTP_GET, [](AsyncWebServerRequest* request) {
for (auto& h : request->getHeaders())
Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str());
request->send(200);
});
// curl -v -X GET -H "x-keep-me: value" http://192.168.4.1/middleware/test-header-free
// - requestLogger will log the incoming headers (including x-keep-me)
// - headerFree will remove all headers except x-keep-me and host
// - handler will log the remaining headers (x-keep-me and host)
server.on("/middleware/test-header-free", HTTP_GET, [](AsyncWebServerRequest* request) {
for (auto& h : request->getHeaders())
Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str());
request->send(200);
})
.addMiddleware(&headerFree);
// basic authentication method
// curl -v -X GET -H "origin: http://192.168.4.1" -u admin:admin http://192.168.4.1/middleware/auth-basic
server.on("/middleware/auth-basic", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/plain", "Hello, world!");
})
.addMiddleware(&basicAuth);
// basic authentication method with hash
// curl -v -X GET -H "origin: http://192.168.4.1" -u admin:admin http://192.168.4.1/middleware/auth-basic-hash
server.on("/middleware/auth-basic-hash", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/plain", "Hello, world!");
})
.addMiddleware(&basicAuthHash);
// digest authentication
// curl -v -X GET -H "origin: http://192.168.4.1" -u admin:admin --digest http://192.168.4.1/middleware/auth-digest
server.on("/middleware/auth-digest", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/plain", "Hello, world!");
})
.addMiddleware(&digestAuth);
// digest authentication with hash
// curl -v -X GET -H "origin: http://192.168.4.1" -u admin:admin --digest http://192.168.4.1/middleware/auth-digest-hash
server.on("/middleware/auth-digest-hash", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/plain", "Hello, world!");
})
.addMiddleware(&digestAuthHash);
// test digest auth with cors
// curl -v -X GET -H "origin: http://192.168.4.1" --digest -u user:password http://192.168.4.1/middleware/auth-custom
server.on("/middleware/auth-custom", HTTP_GET, [](AsyncWebServerRequest* request) {
String buffer = "Hello ";
buffer.concat(request->getAttribute("user"));
buffer.concat(" with role: ");
buffer.concat(request->getAttribute("role"));
request->send(200, "text/plain", buffer);
})
.addMiddlewares({&complexAuth, &authz});
///////////////////////////////////////////////////////////////////////
// curl -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/redirect
// curl -v -X POST -H "origin: http://192.168.4.1" http://192.168.4.1/redirect
server.on("/redirect", HTTP_GET | HTTP_POST, [](AsyncWebServerRequest* request) {
request->redirect("/");
});
// PERF TEST:
// > brew install autocannon
// > autocannon -c 10 -w 10 -d 20 http://192.168.4.1
// > autocannon -c 16 -w 16 -d 20 http://192.168.4.1
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/html", htmlContent);
});
// curl -v -X GET http://192.168.4.1/index.txt
server.serveStatic("/index.txt", LittleFS, "/index.txt");
// curl -v -X GET http://192.168.4.1/index-private.txt
server.serveStatic("/index-private.txt", LittleFS, "/index.txt").setAuthentication("admin", "admin");
// ServeStatic static is used to serve static output which never changes over time.
// This special endpoints automatyically adds caching headers.
// If a template processor is used, it must enure that the outputed content will always be the ame over time and never changes.
// Otherwise, do not use serveStatic.
// Example below: IP never changes.
// curl -v -X GET http://192.168.4.1/index-static.html
server.serveStatic("/index-static.html", LittleFS, "/index.html").setTemplateProcessor([](const String& var) -> String {
if (var == "IP") {
// for CI, commented out since H2 board doesn ot support WiFi
// return WiFi.localIP().toString();
// return WiFi.softAPIP().toString();
return "127.0.0..1";
}
return emptyString;
});
// to serve a template with dynamic content (output changes over time), use normal
// Example below: content changes over tinme do not use serveStatic.
// curl -v -X GET http://192.168.4.1/index-dynamic.html
server.on("/index-dynamic.html", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(LittleFS, "/index.html", "text/html", false, [](const String& var) -> String {
if (var == "IP")
return String(random(0, 1000));
return emptyString;
});
});
// Issue #14: assert failed: tcp_update_rcv_ann_wnd (needs help to test fix)
// > curl -v http://192.168.4.1/issue-14
pinMode(4, OUTPUT);
server.on("/issue-14", HTTP_GET, [](AsyncWebServerRequest* request) {
digitalWrite(4, HIGH);
request->send(LittleFS, "/index.txt", "text/pain");
delay(500);
digitalWrite(4, LOW);
});
/*
Chunked encoding test: sends 16k of characters.
curl -N -v -X GET -H "origin: http://192.168.4.1" http://192.168.4.1/chunk
*/
server.on("/chunk", HTTP_HEAD | HTTP_GET, [](AsyncWebServerRequest* request) {
AsyncWebServerResponse* response = request->beginChunkedResponse("text/html", [](uint8_t* buffer, size_t maxLen, size_t index) -> size_t {
if (index >= 16384)
return 0;
memset(buffer, characters[charactersIndex], maxLen);
charactersIndex = (charactersIndex + 1) % sizeof(characters);
return maxLen;
});
request->send(response);
});
// curl -N -v -X GET http://192.168.4.1/chunked.html --output -
// curl -N -v -X GET -H "if-none-match: 4272" http://192.168.4.1/chunked.html --output -
server.on("/chunked.html", HTTP_GET, [](AsyncWebServerRequest* request) {
String len = String(htmlContentLength);
if (request->header(asyncsrv::T_INM) == len) {
request->send(304);
return;
}
AsyncWebServerResponse* response = request->beginChunkedResponse("text/html", [](uint8_t* buffer, size_t maxLen, size_t index) -> size_t {
Serial.printf("%u / %u\n", index, htmlContentLength);
// finished ?
if (htmlContentLength <= index) {
Serial.println("finished");
return 0;
}
// serve a maximum of 1024 or maxLen bytes of the remaining content
const int chunkSize = min((size_t)1024, min(maxLen, htmlContentLength - index));
Serial.printf("sending: %u\n", chunkSize);
memcpy(buffer, htmlContent + index, chunkSize);
return chunkSize;
});
response->addHeader(asyncsrv::T_Cache_Control, "public,max-age=60");
response->addHeader(asyncsrv::T_ETag, len);
request->send(response);
});
// time curl -N -v -G -d 'd=3000' -d 'l=10000' http://192.168.4.1/slow.html --output -
server.on("/slow.html", HTTP_GET, [](AsyncWebServerRequest* request) {
uint32_t d = request->getParam("d")->value().toInt();
uint32_t l = request->getParam("l")->value().toInt();
Serial.printf("d = %" PRIu32 ", l = %" PRIu32 "\n", d, l);
AsyncWebServerResponse* response = request->beginChunkedResponse("text/html", [d, l](uint8_t* buffer, size_t maxLen, size_t index) -> size_t {
Serial.printf("%u\n", index);
// finished ?
if (index >= l)
return 0;
// slow down the task by 2 seconds
// to simulate some heavy processing, like SD card reading
delay(d);
memset(buffer, characters[charactersIndex], 256);
charactersIndex = (charactersIndex + 1) % sizeof(characters);
return 256;
});
request->send(response);
});
/*
curl -I -X HEAD http://192.168.4.1/download
HTTP/1.1 200 OK
Content-Length: 1024
Content-Type: application/octet-stream
Connection: close
Accept-Ranges: bytes
*/
// Ref: https://github.com/mathieucarbou/ESPAsyncWebServer/pull/80
server.on("/download", HTTP_HEAD | HTTP_GET, [](AsyncWebServerRequest* request) {
if (request->method() == HTTP_HEAD) {
AsyncWebServerResponse* response = request->beginResponse(200, "application/octet-stream");
response->addHeader(asyncsrv::T_Accept_Ranges, "bytes");
response->addHeader(asyncsrv::T_Content_Length, 10);
response->setContentLength(1024); // overrides previous one
response->addHeader(asyncsrv::T_Content_Type, "foo");
response->setContentType("application/octet-stream"); // overrides previous one
// ...
request->send(response);
} else {
// ...
}
});
// Send a GET request to <IP>/get?message=<message>
server.on("/get", HTTP_GET, [](AsyncWebServerRequest* request) {
String message;
if (request->hasParam(PARAM_MESSAGE)) {
message = request->getParam(PARAM_MESSAGE)->value();
} else {
message = "No message sent";
}
request->send(200, "text/plain", "Hello, GET: " + message);
});
// Send a POST request to <IP>/post with a form field message set to <message>
server.on("/post", HTTP_POST, [](AsyncWebServerRequest* request) {
String message;
if (request->hasParam(PARAM_MESSAGE, true)) {
message = request->getParam(PARAM_MESSAGE, true)->value();
} else {
message = "No message sent";
}
request->send(200, "text/plain", "Hello, POST: " + message);
});
#if __has_include("ArduinoJson.h")
// JSON
// sends JSON
// curl -v -X GET http://192.168.4.1/json1
server.on("/json1", HTTP_GET, [](AsyncWebServerRequest* request) {
AsyncJsonResponse* response = new AsyncJsonResponse();
JsonObject root = response->getRoot().to<JsonObject>();
root["hello"] = "world";
response->setLength();
request->send(response);
});
// curl -v -X GET http://192.168.4.1/json2
server.on("/json2", HTTP_GET, [](AsyncWebServerRequest* request) {
AsyncResponseStream* response = request->beginResponseStream("application/json");
JsonDocument doc;
JsonObject root = doc.to<JsonObject>();
root["foo"] = "bar";
serializeJson(root, *response);
request->send(response);
});
// curl -v -X POST -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/json2
// curl -v -X PUT -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/json2
jsonHandler->setMethod(HTTP_POST | HTTP_PUT);
jsonHandler->onRequest([](AsyncWebServerRequest* request, JsonVariant& json) {
serializeJson(json, Serial);
AsyncJsonResponse* response = new AsyncJsonResponse();
JsonObject root = response->getRoot().to<JsonObject>();
root["hello"] = json.as<JsonObject>()["name"];
response->setLength();
request->send(response);
});
// MessagePack
// receives MessagePack and sends MessagePack
msgPackHandler->onRequest([](AsyncWebServerRequest* request, JsonVariant& json) {
// JsonObject jsonObj = json.as<JsonObject>();
// ...
AsyncMessagePackResponse* response = new AsyncMessagePackResponse();
JsonObject root = response->getRoot().to<JsonObject>();
root["hello"] = "world";
response->setLength();
request->send(response);
});
// sends MessagePack
server.on("/msgpack1", HTTP_GET, [](AsyncWebServerRequest* request) {
AsyncMessagePackResponse* response = new AsyncMessagePackResponse();
JsonObject root = response->getRoot().to<JsonObject>();
root["hello"] = "world";
response->setLength();
request->send(response);
});
#endif
events.onConnect([](AsyncEventSourceClient* client) {
if (client->lastId()) {
Serial.printf("SSE Client reconnected! Last message ID that it gat is: %" PRIu32 "\n", client->lastId());
}
client->send("hello!", NULL, millis(), 1000);
});
server.on("/sse", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/html", SSE_HTLM);
});
ws.onEvent([](AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) {
(void)len;
if (type == WS_EVT_CONNECT) {
wsClients++;
ws.textAll("new client connected");
Serial.println("ws connect");
client->setCloseClientOnQueueFull(false);
client->ping();
} else if (type == WS_EVT_DISCONNECT) {
wsClients--;
ws.textAll("client disconnected");
Serial.println("ws disconnect");
} else if (type == WS_EVT_ERROR) {
Serial.println("ws error");
} else if (type == WS_EVT_PONG) {
Serial.println("ws pong");
} else if (type == WS_EVT_DATA) {
AwsFrameInfo* info = (AwsFrameInfo*)arg;
String msg = "";
if (info->final && info->index == 0 && info->len == len) {
if (info->opcode == WS_TEXT) {
data[len] = 0;
Serial.printf("ws text: %s\n", (char*)data);
}
}
}
});
// SSS endpoints
// sends a message every 10 ms
//
// go to http://192.168.4.1/sse
// > curl -v -N -H "Accept: text/event-stream" http://192.168.4.1/events
//
// some perf tests:
// launch 16 concurrent workers for 30 seconds
// > for i in {1..10}; do ( count=$(gtimeout 30 curl -s -N -H "Accept: text/event-stream" http://192.168.4.1/events 2>&1 | grep -c "^data:"); echo "Total: $count events, $(echo "$count / 4" | bc -l) events / second" ) & done;
// > for i in {1..16}; do ( count=$(gtimeout 30 curl -s -N -H "Accept: text/event-stream" http://192.168.4.1/events 2>&1 | grep -c "^data:"); echo "Total: $count events, $(echo "$count / 4" | bc -l) events / second" ) & done;
//
// With AsyncTCP, with 16 workers: a lot of "Event message queue overflow: discard message", no crash
//
// Total: 1711 events, 427.75 events / second
// Total: 1711 events, 427.75 events / second
// Total: 1626 events, 406.50 events / second
// Total: 1562 events, 390.50 events / second
// Total: 1706 events, 426.50 events / second
// Total: 1659 events, 414.75 events / second
// Total: 1624 events, 406.00 events / second
// Total: 1706 events, 426.50 events / second
// Total: 1487 events, 371.75 events / second
// Total: 1573 events, 393.25 events / second
// Total: 1569 events, 392.25 events / second
// Total: 1559 events, 389.75 events / second
// Total: 1560 events, 390.00 events / second
// Total: 1562 events, 390.50 events / second
// Total: 1626 events, 406.50 events / second
//
// With AsyncTCP, with 10 workers:
//
// Total: 2038 events, 509.50 events / second
// Total: 2120 events, 530.00 events / second
// Total: 2119 events, 529.75 events / second
// Total: 2038 events, 509.50 events / second
// Total: 2037 events, 509.25 events / second
// Total: 2119 events, 529.75 events / second
// Total: 2119 events, 529.75 events / second
// Total: 2120 events, 530.00 events / second
// Total: 2038 events, 509.50 events / second
// Total: 2038 events, 509.50 events / second
//
// With AsyncTCPSock, with 16 workers: ESP32 CRASH !!!
//
// With AsyncTCPSock, with 10 workers:
//
// Total: 1242 events, 310.50 events / second
// Total: 1242 events, 310.50 events / second
// Total: 1242 events, 310.50 events / second
// Total: 1242 events, 310.50 events / second
// Total: 1181 events, 295.25 events / second
// Total: 1182 events, 295.50 events / second
// Total: 1240 events, 310.00 events / second
// Total: 1181 events, 295.25 events / second
// Total: 1181 events, 295.25 events / second
// Total: 1183 events, 295.75 events / second
//
server.addHandler(&events);
// Run in terminal 1: websocat ws://192.168.4.1/ws => stream data
// Run in terminal 2: websocat ws://192.168.4.1/ws => stream data
// Run in terminal 3: websocat ws://192.168.4.1/ws => should fail:
/*
websocat ws://192.168.4.1/ws
websocat: WebSocketError: WebSocketError: Received unexpected status code (503 Service Unavailable)
websocat: error running
*/
server.addHandler(&ws).addMiddleware([](AsyncWebServerRequest* request, ArMiddlewareNext next) {
if (ws.count() > 2) {
// too many clients - answer back immediately and stop processing next middlewares and handler
request->send(503, "text/plain", "Server is busy");
} else {
// process next middleware and at the end the handler
next();
}
});
// Reset connection on HTTP request:
// for i in {1..20}; do curl -v -X GET https://192.168.4.1:80; done;
// The heap size should not decrease over time.
#if __has_include("ArduinoJson.h")
server.addHandler(jsonHandler);
server.addHandler(msgPackHandler);
#endif
server.onNotFound(notFound);
server.begin();
}
uint32_t lastSSE = 0;
uint32_t deltaSSE = 10;
uint32_t lastWS = 0;
uint32_t deltaWS = 100;
uint32_t lastHeap = 0;
void loop() {
uint32_t now = millis();
if (now - lastSSE >= deltaSSE) {
events.send(String("ping-") + now, "heartbeat", now);
lastSSE = millis();
}
if (now - lastWS >= deltaWS) {
ws.printfAll("kp%.4f", (10.0 / 3.0));
// for (auto& client : ws.getClients()) {
// client.printf("kp%.4f", (10.0 / 3.0));
// }
lastWS = millis();
}
#ifdef ESP32
if (now - lastHeap >= 2000) {
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
lastHeap = now;
}
#endif
}

View File

@ -0,0 +1,73 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Authentication and authorization middlewares
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static AsyncAuthenticationMiddleware basicAuth;
static AsyncLoggingMiddleware logging;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// basic authentication
basicAuth.setUsername("admin");
basicAuth.setPassword("admin");
basicAuth.setRealm("MyApp");
basicAuth.setAuthFailureMessage("Authentication failed");
basicAuth.setAuthType(AsyncAuthType::AUTH_BASIC);
basicAuth.generateHash(); // precompute hash (optional but recommended)
// logging middleware
logging.setEnabled(true);
logging.setOutput(Serial);
// we apply auth middleware to the server globally
server.addMiddleware(&basicAuth);
// protected endpoint: requires basic authentication
// curl -v -u admin:admin http://192.168.4.1/
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/plain", "Hello, world!");
});
// we skip all global middleware from the catchall handler
server.catchAllHandler().skipServerMiddlewares();
// we apply a specific middleware to the catchall handler only to log requests without a handler defined
server.catchAllHandler().addMiddleware(&logging);
// standard 404 handler: will display the request in the console i na curl-like style
// curl -v -H "Foo: Bar" http://192.168.4.1/foo
server.onNotFound([](AsyncWebServerRequest *request) {
request->send(404, "text/plain", "Not found");
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@ -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 <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</body>
</html>
)";
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
}

View File

@ -0,0 +1,122 @@
// 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 <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
static AsyncWebServer server(80);
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</body>
</html>
)";
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() {
delay(100);
}

View File

@ -1,37 +0,0 @@
#pragma once
#include <Stream.h>
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;
};

View File

@ -1,89 +0,0 @@
#include <Arduino.h>
#include <DNSServer.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040)
#include <WebServer.h>
#include <WiFi.h>
#endif
#include <StreamString.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#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("<html><head><title>ESP Captive Portal</title><meta http-equiv=\"refresh\" content=\"1\"></head><body>");
file1.close();
File file2 = LittleFS.open("/body.html", "w");
file2.print("<h1>Welcome to ESP Captive Portal</h1>");
file2.close();
File file3 = LittleFS.open("/footer.html", "w");
file3.print("</body></html>");
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();
}
}

View File

@ -0,0 +1,99 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Shows how to serve a static and dynamic template
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
static AsyncWebServer server(80);
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<body>
<h1>Hello, %USER%</h1>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
#ifdef ESP32
LittleFS.begin(true);
#else
LittleFS.begin();
#endif
{
File f = LittleFS.open("/template.html", "w");
assert(f);
f.print(htmlContent);
f.close();
}
// Serve the static template file
//
// curl -v http://192.168.4.1/template.html
server.serveStatic("/template.html", LittleFS, "/template.html");
// Serve the static template with a template processor
//
// ServeStatic static is used to serve static output which never changes over time.
// This special endpoints automatically adds caching headers.
// If a template processor is used, it must ensure that the outputted content will always be the same over time and never changes.
// Otherwise, do not use serveStatic.
// Example below: IP never changes.
//
// curl -v http://192.168.4.1/index.html
server.serveStatic("/index.html", LittleFS, "/template.html").setTemplateProcessor([](const String &var) -> String {
if (var == "USER") {
return "Bob";
}
return emptyString;
});
// Serve a template with dynamic content
//
// to serve a template with dynamic content (output changes over time), use normal
// Example below: content changes over tinme do not use serveStatic.
//
// curl -v http://192.168.4.1/dynamic.html
server.on("/dynamic.html", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(LittleFS, "/template.html", "text/html", false, [](const String &var) -> String {
if (var == "USER") {
return String("Bob ") + millis();
}
return emptyString;
});
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

171
examples/Upload/Upload.ino Normal file
View File

@ -0,0 +1,171 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// Demo text, binary and file upload
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#include <StreamString.h>
#include <LittleFS.h>
static AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
if (!LittleFS.begin()) {
LittleFS.format();
LittleFS.begin();
}
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// 1. Generate a Lorem_ipsum.txt file of about 20KB of text
//
// 3. Run: curl -v -F "data=@Lorem_ipsum.txt" http://192.168.4.1/upload/text
//
server.on(
"/upload/text", HTTP_POST,
[](AsyncWebServerRequest *request) {
if (!request->_tempObject) {
return request->send(400, "text/plain", "Nothing uploaded");
}
StreamString *buffer = reinterpret_cast<StreamString *>(request->_tempObject);
Serial.printf("Text uploaded:\n%s\n", buffer->c_str());
delete buffer;
request->_tempObject = nullptr;
request->send(200, "text/plain", "OK");
},
[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
Serial.printf("Upload[%s]: start=%u, len=%u, final=%d\n", filename.c_str(), index, len, final);
if (!index) {
// first pass
StreamString *buffer = new StreamString();
size_t size = std::max(4094l, request->header("Content-Length").toInt());
Serial.printf("Allocating string buffer of %u bytes\n", size);
if (!buffer->reserve(size)) {
delete buffer;
request->abort();
}
request->_tempObject = buffer;
}
if (len) {
reinterpret_cast<StreamString *>(request->_tempObject)->write(data, len);
}
}
);
// 1. Generate a Lorem_ipsum.txt file of about 20KB of text
//
// 3. Run: curl -v -F "data=@Lorem_ipsum.txt" http://192.168.4.1/upload/file
//
server.on(
"/upload/file", HTTP_POST,
[](AsyncWebServerRequest *request) {
if (request->getResponse()) {
// 400 File not available for writing
return;
}
if (!LittleFS.exists("/my_file.txt")) {
return request->send(400, "text/plain", "Nothing uploaded");
}
// sends back the uploaded file
request->send(LittleFS, "/my_file.txt", "text/plain");
},
[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
Serial.printf("Upload[%s]: start=%u, len=%u, final=%d\n", filename.c_str(), index, len, final);
if (!index) {
request->_tempFile = LittleFS.open("/my_file.txt", "w");
if (!request->_tempFile) {
request->send(400, "text/plain", "File not available for writing");
}
}
if (len) {
request->_tempFile.write(data, len);
}
if (final) {
request->_tempFile.close();
}
}
);
//
// Upload a binary file: curl -v -F "data=@file.mp3" http://192.168.4.1/upload/binary
//
server.on(
"/upload/binary", HTTP_POST,
[](AsyncWebServerRequest *request) {
// response already set ?
if (request->getResponse()) {
// 400 No Content-Length
return;
}
// nothing uploaded ?
if (!request->_tempObject) {
return request->send(400, "text/plain", "Nothing uploaded");
}
uint8_t *buffer = reinterpret_cast<uint8_t *>(request->_tempObject);
// process the buffer
delete buffer;
request->_tempObject = nullptr;
request->send(200, "text/plain", "OK");
},
[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
Serial.printf("Upload[%s]: start=%u, len=%u, final=%d\n", filename.c_str(), index, len, final);
// first pass ?
if (!index) {
size_t size = request->header("Content-Length").toInt();
if (!size) {
request->send(400, "text/plain", "No Content-Length");
} else {
Serial.printf("Allocating buffer of %u bytes\n", size);
uint8_t *buffer = new (std::nothrow) uint8_t[size];
if (!buffer) {
// not enough memory
request->abort();
} else {
request->_tempObject = buffer;
}
}
}
if (len) {
memcpy(reinterpret_cast<uint8_t *>(request->_tempObject) + index, data, len);
}
}
);
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@ -0,0 +1,113 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// WebSocket example
//
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static AsyncWebSocket ws("/ws");
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
//
// Run in terminal 1: websocat ws://192.168.4.1/ws => should stream data
// Run in terminal 2: websocat ws://192.168.4.1/ws => should stream data
// Run in terminal 3: websocat ws://192.168.4.1/ws => should fail:
//
// To send a message to the WebSocket server:
//
// echo "Hello!" | websocat ws://192.168.4.1/ws
//
ws.onEvent([](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
(void)len;
if (type == WS_EVT_CONNECT) {
ws.textAll("new client connected");
Serial.println("ws connect");
client->setCloseClientOnQueueFull(false);
client->ping();
} else if (type == WS_EVT_DISCONNECT) {
ws.textAll("client disconnected");
Serial.println("ws disconnect");
} else if (type == WS_EVT_ERROR) {
Serial.println("ws error");
} else if (type == WS_EVT_PONG) {
Serial.println("ws pong");
} else if (type == WS_EVT_DATA) {
AwsFrameInfo *info = (AwsFrameInfo *)arg;
Serial.printf("index: %" PRIu64 ", len: %" PRIu64 ", final: %" PRIu8 ", opcode: %" PRIu8 "\n", info->index, info->len, info->final, info->opcode);
String msg = "";
if (info->final && info->index == 0 && info->len == len) {
if (info->opcode == WS_TEXT) {
data[len] = 0;
Serial.printf("ws text: %s\n", (char *)data);
}
}
}
});
// shows how to prevent a third WS client to connect
server.addHandler(&ws).addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
// ws.count() is the current count of WS clients: this one is trying to upgrade its HTTP connection
if (ws.count() > 1) {
// if we have 2 clients or more, prevent the next one to connect
request->send(503, "text/plain", "Server is busy");
} else {
// process next middleware and at the end the handler
next();
}
});
server.addHandler(&ws);
server.begin();
}
static uint32_t lastWS = 0;
static uint32_t deltaWS = 100;
static uint32_t lastHeap = 0;
void loop() {
uint32_t now = millis();
if (now - lastWS >= deltaWS) {
ws.printfAll("kp%.4f", (10.0 / 3.0));
lastWS = millis();
}
if (now - lastHeap >= 2000) {
// cleanup disconnected clients or too many clients
ws.cleanupClients();
#ifdef ESP32
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
#endif
lastHeap = now;
}
}

37
idf_component.yml Normal file
View File

@ -0,0 +1,37 @@
description: "Async Web Server for ESP32 Arduino"
url: "https://github.com/ESP32Async/ESPAsyncWebServer"
license: "LGPL-3.0-or-later"
tags:
- arduino
files:
exclude:
- "idf_component_examples/"
- "idf_component_examples/**/*"
- "docs/"
- "docs/*"
- "examples/"
- "examples/**/*"
- ".gitignore"
- ".clang-format"
- ".gitpod.Dockerfile"
- ".gitpod.yml"
- ".codespellrc"
- ".editorconfig"
- ".pre-commit-config.yaml"
- "CODE_OF_CONDUCT.md"
- "library.json"
- "library.properties"
- "partitions-4MB.csv"
- "platformio.ini"
- "pre-commit.requirements.txt"
dependencies:
esp32async/asynctcp:
version: "^3.3.6"
require: public
bblanchon/arduinojson:
version: "^7.3.1"
require: public
examples:
- path: ./idf_component_examples/catchall
- path: ./idf_component_examples/serversentevents
- path: ./idf_component_examples/websocket

View File

@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(main)

View File

@ -0,0 +1 @@
### Basic example to show how to catch all requests and send a 404 Not Found response

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "main.cpp"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,6 @@
## IDF Component Manager Manifest File
dependencies:
esp32async/espasyncwebserver:
version: "*"
override_path: "../../../"
pre_release: true

View File

@ -0,0 +1,125 @@
// 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 <Arduino.h>
#include <AsyncTCP.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
static AsyncWebServer server(80);
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
// curl -v http://192.168.4.1/
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
// need to cast to uint8_t*
// if you do not, the const char* will be copied in a temporary String buffer
request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
});
// catch any request, and send a 404 Not Found response
// except for /game_log which is handled by onRequestBody
//
// curl -v http://192.168.4.1/foo
//
server.onNotFound([](AsyncWebServerRequest *request) {
if (request->url() == "/game_log") {
return; // response object already created by onRequestBody
}
request->send(404, "text/plain", "Not found");
});
// See: https://github.com/ESP32Async/ESPAsyncWebServer/issues/6
// catch any POST request and send a 200 OK response
//
// curl -v -X POST http://192.168.4.1/game_log -H "Content-Type: application/json" -d '{"game": "test"}'
//
server.onRequestBody([](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
if (request->url() == "/game_log") {
request->send(200, "application/json", "{\"status\":\"OK\"}");
}
// note that there is no else here: the goal is only to prepare a response based on some body content
// onNotFound will always be called after this, and will not override the response object if `/game_log` is requested
});
server.begin();
}
// not needed
void loop() {
delay(100);
}

View File

@ -0,0 +1,12 @@
#
# Arduino ESP32
#
CONFIG_AUTOSTART_ARDUINO=y
# end of Arduino ESP32
#
# FREERTOS
#
CONFIG_FREERTOS_HZ=1000
# end of FREERTOS
# end of Component config

View File

@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(main)

View File

@ -0,0 +1 @@
### Basic example to show how to use ServerSentEvents

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "main.cpp"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,6 @@
## IDF Component Manager Manifest File
dependencies:
esp32async/espasyncwebserver:
version: "*"
override_path: "../../../"
pre_release: true

View File

@ -0,0 +1,95 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// SSE example
//
#include <Arduino.h>
#include <AsyncTCP.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Server-Sent Events</title>
<script>
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e.data);
}, false);
source.addEventListener('heartbeat', function(e) {
console.log("heartbeat", e.data);
}, false);
}
</script>
</head>
<body>
<h1>Open your browser console!</h1>
</body>
</html>
)";
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();
}
if (now - lastHeap >= 2000) {
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
lastHeap = now;
}
}

View File

@ -0,0 +1,12 @@
#
# Arduino ESP32
#
CONFIG_AUTOSTART_ARDUINO=y
# end of Arduino ESP32
#
# FREERTOS
#
CONFIG_FREERTOS_HZ=1000
# end of FREERTOS
# end of Component config

View File

@ -0,0 +1,8 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(main)

View File

@ -0,0 +1 @@
### Basic example to show how to use WebSockets

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "main.cpp"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,6 @@
## IDF Component Manager Manifest File
dependencies:
esp32async/espasyncwebserver:
version: "*"
override_path: "../../../"
pre_release: true

View File

@ -0,0 +1,102 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// WebSocket example
//
#include <Arduino.h>
#include <AsyncTCP.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
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();
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
lastHeap = now;
}
}

View File

@ -0,0 +1,12 @@
#
# Arduino ESP32
#
CONFIG_AUTOSTART_ARDUINO=y
# end of Arduino ESP32
#
# FREERTOS
#
CONFIG_FREERTOS_HZ=1000
# end of FREERTOS
# end of Component config

View File

@ -1,6 +1,6 @@
{
"name": "ESPAsyncWebServer",
"version": "3.6.0",
"version": "3.7.2",
"description": "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.",
"keywords": "http,async,websocket,webserver",
"homepage": "https://github.com/ESP32Async/ESPAsyncWebServer",
@ -24,7 +24,7 @@
{
"owner": "ESP32Async",
"name": "AsyncTCP",
"version": "^3.3.2",
"version": "^3.3.6",
"platforms": "espressif32"
},
{
@ -38,9 +38,9 @@
"platforms": "espressif8266"
},
{
"owner": "khoih-prog",
"name": "AsyncTCP_RP2040W",
"version": "^1.2.0",
"owner": "ayushsharma82",
"name": "RPAsyncTCP",
"version": "^1.3.1",
"platforms": "raspberrypi"
}
],

View File

@ -1,6 +1,6 @@
name=ESP Async WebServer
includes=ESPAsyncWebServer.h
version=3.6.0
version=3.7.2
author=ESP32Async
maintainer=ESP32Async
sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040

View File

@ -0,0 +1,11 @@
.DS_Store
.lh
/.pio
/.vscode
/logs
/sdkconfig.*
/CMakeLists.txt
/dependencies.lock
/.dummy
/managed_components

View File

@ -0,0 +1,26 @@
[env]
framework = arduino
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.13/platform-espressif32.zip
build_flags =
-Og
-Wall -Wextra
-Wno-unused-parameter
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
; -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000
; -D CONFIG_ASYNC_TCP_PRIORITY=10
-D CONFIG_ASYNC_TCP_QUEUE_SIZE=128
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1
-D CONFIG_ASYNC_TCP_STACK_SIZE=4096
upload_protocol = esptool
monitor_speed = 115200
monitor_filters = esp32_exception_decoder, log2file
lib_compat_mode = strict
lib_ldf_mode = chain
lib_deps =
ESP32Async/AsyncTCP @ 3.3.6
ESP32Async/ESpAsyncWebServer @ 3.7.0
custom_sdkconfig = CONFIG_LWIP_MAX_ACTIVE_TCP=32
[env:esp32dev]
board = esp32dev

View File

@ -0,0 +1,142 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
//
// This example demonstrates how to increase the maximum number of active TCP connections
//
// in platformo.ini:
//
// Use hybrid compilation to set the maximum number of active TCP connections
//
// custom_sdkconfig = CONFIG_LWIP_MAX_ACTIVE_TCP=32
//
// and increase the queue stack size
//
// -D CONFIG_ASYNC_TCP_QUEUE_SIZE=128
//
#include <Arduino.h>
#include <AsyncTCP.h>
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
static const char *htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</body>
</html>
)";
static const size_t htmlContentLength = strlen_P(htmlContent);
static AsyncWebServer server(80);
static AsyncEventSource events("/events");
static volatile size_t requests = 0;
void setup() {
Serial.begin(115200);
Serial.println("============================");
Serial.printf("CONFIG_LWIP_MAX_ACTIVE_TCP %d\n", CONFIG_LWIP_MAX_ACTIVE_TCP);
Serial.println("============================");
WiFi.mode(WIFI_STA);
WiFi.begin("IoT", "");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.println("Connecting to WiFi...");
}
// HTTP endpoint
//
// > autocannon -c 32 -d 20 -t 30 --renderStatusCodes http://192.168.125.146/
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
requests++;
request->send(200, "text/html", (uint8_t *)htmlContent, htmlContentLength);
});
// SSS endpoint
//
// launch 32 concurrent workers for 30 seconds
// > for i in {1..32}; do ( count=$(gtimeout 30 curl -s -N -H "Accept: text/event-stream" http://192.168.125.146/events 2>&1 | grep -c "^data:"); echo "Total: $count events, $(echo "$count / 4" | bc -l) events / second" ) & done;
//
server.addHandler(&events);
server.begin();
}
static uint32_t lastSSE = 0;
static uint32_t deltaSSE = 10;
static uint32_t lastHeap = 0;
void loop() {
uint32_t now = millis();
if (now - lastSSE >= deltaSSE) {
events.send(String("ping-") + now, "heartbeat", now);
lastSSE = millis();
}
#ifdef ESP32
if (now - lastHeap >= 2000) {
Serial.printf("Uptime: %3lu s, requests: %3u, Free heap: %" PRIu32 "\n", millis() / 1000, requests, ESP.getFreeHeap());
lastHeap = now;
}
#endif
}

View File

@ -1,15 +1,41 @@
[platformio]
default_envs = arduino-2, arduino-3, arduino-311, esp8266, raspberrypi
default_envs = arduino-2, arduino-3, esp8266, raspberrypi
lib_dir = .
; src_dir = examples/Auth
; src_dir = examples/CaptivePortal
src_dir = examples/SimpleServer
; src_dir = examples/StreamFiles
; src_dir = examples/CatchAllHandler
; src_dir = examples/ChunkResponse
; src_dir = examples/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/PartitionDownloader
src_dir = examples/PerfTests
; src_dir = examples/RateLimit
; src_dir = examples/Redirect
; src_dir = examples/RequestContinuation
; src_dir = examples/RequestContinuationComplete
; src_dir = examples/ResumableDownload
; src_dir = examples/Rewrite
; src_dir = examples/ServerSentEvents
; src_dir = examples/SkipServerMiddleware
; src_dir = examples/SlowChunkResponse
; src_dir = examples/StaticFile
; src_dir = examples/Templates
; src_dir = examples/Upload
; src_dir = examples/WebSocket
[env]
framework = arduino
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.13/platform-espressif32.zip
board = esp32dev
build_flags =
-Og
-Wall -Wextra
@ -28,62 +54,51 @@ monitor_filters = esp32_exception_decoder, log2file
lib_compat_mode = strict
lib_ldf_mode = chain
lib_deps =
; bblanchon/ArduinoJson @ 5.13.4
; bblanchon/ArduinoJson @ 6.21.5
bblanchon/ArduinoJson @ 7.3.0
ESP32Async/AsyncTCP @ 3.3.2
board = esp32dev
bblanchon/ArduinoJson @ 7.3.1
ESP32Async/AsyncTCP @ 3.3.6
board_build.partitions = partitions-4MB.csv
board_build.filesystem = littlefs
[env:arduino-2]
platform = espressif32@6.9.0
platform = espressif32@6.10.0
[env:arduino-3]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
; board = esp32-s3-devkitc-1
; board = esp32-c6-devkitc-1
[env:arduino-3-latest]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20-rc1/platform-espressif32.zip
[env:arduino-3-no-json]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
; board = esp32-s3-devkitc-1
; board = esp32-c6-devkitc-1
lib_deps =
ESP32Async/AsyncTCP @ 3.3.2
ESP32Async/AsyncTCP @ 3.3.6
[env:arduino-311]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip
; board = esp32-s3-devkitc-1
; board = esp32-c6-devkitc-1
; board = esp32-h2-devkitm-1
[env:arduino-3-latest-asynctcp]
lib_deps =
https://github.com/ESP32Async/AsyncTCP
[env:perf-test-AsyncTCP]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip
[env:arduino-3-no-chunk-inflight]
build_flags = ${env.build_flags}
-D PERF_TEST=1
-D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=0
[env:perf-test-AsyncTCPSock]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip
[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
; board = huzzah
board = d1_mini
lib_deps =
bblanchon/ArduinoJson @ 7.3.0
bblanchon/ArduinoJson @ 7.3.1
ESP32Async/ESPAsyncTCP @ 2.0.0
[env:raspberrypi]
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
platform = https://github.com/maxgerhardt/platform-raspberrypi.git#c7502925e3b08af70e9f924d54ab9d00a7e64781
board = rpipicow
board_build.core = earlephilhower
lib_deps =
bblanchon/ArduinoJson @ 7.3.0
khoih-prog/AsyncTCP_RP2040W @ 1.2.0
ayushsharma82/RPAsyncTCP@^1.3.1
lib_ignore =
lwIP_ESPHost
build_flags = ${env.build_flags}
@ -92,37 +107,44 @@ build_flags = ${env.build_flags}
; CI
[env:ci-arduino-2]
platform = espressif32@6.9.0
platform = espressif32@6.10.0
board = ${sysenv.PIO_BOARD}
[env:ci-arduino-3]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
board = ${sysenv.PIO_BOARD}
[env:ci-arduino-3-latest]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20-rc1/platform-espressif32.zip
board = ${sysenv.PIO_BOARD}
[env:ci-arduino-3-no-json]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
board = ${sysenv.PIO_BOARD}
lib_deps =
ESP32Async/AsyncTCP @ 3.3.2
ESP32Async/AsyncTCP @ 3.3.6
[env:ci-arduino-311]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.11/platform-espressif32.zip
[env:ci-arduino-3-latest-asynctcp]
lib_deps =
https://github.com/ESP32Async/AsyncTCP
[env:ci-arduino-3-no-chunk-inflight]
board = ${sysenv.PIO_BOARD}
build_flags = ${env.build_flags}
-D ASYNCWEBSERVER_USE_CHUNK_INFLIGHT=1
[env:ci-esp8266]
platform = espressif8266
board = ${sysenv.PIO_BOARD}
lib_deps =
bblanchon/ArduinoJson @ 7.3.0
bblanchon/ArduinoJson @ 7.3.1
ESP32Async/ESPAsyncTCP @ 2.0.0
[env:ci-raspberrypi]
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
platform = https://github.com/maxgerhardt/platform-raspberrypi.git#c7502925e3b08af70e9f924d54ab9d00a7e64781
board = ${sysenv.PIO_BOARD}
board_build.core = earlephilhower
lib_deps =
bblanchon/ArduinoJson @ 7.3.0
khoih-prog/AsyncTCP_RP2040W @ 1.2.0
ayushsharma82/RPAsyncTCP@^1.3.1
lib_ignore =
lwIP_ESPHost
build_flags = ${env.build_flags}

View File

@ -0,0 +1 @@
pre-commit==4.1.0

View File

@ -1,22 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "Arduino.h"
#if defined(ESP32)
#include <rom/ets_sys.h>
@ -30,15 +14,22 @@ using namespace asyncsrv;
static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
String str;
size_t len{0};
if (message)
if (message) {
len += strlen(message);
}
if (event)
if (event) {
len += strlen(event);
}
len += 42; // give it some overhead
str.reserve(len);
if (!str.reserve(len)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
return emptyString;
}
if (reconnect) {
str += T_retry_;
@ -58,8 +49,9 @@ static String generateEventMessage(const char* message, const char* event, uint3
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
}
if (!message)
if (!message) {
return str;
}
size_t messageLen = strlen(message);
char *lineStart = (char *)message;
@ -79,7 +71,7 @@ static String generateEventMessage(const char* message, const char* event, uint3
char *nextLine = NULL;
if (nextN != NULL && nextR != NULL) { // windows line-ending \r\n
if (nextR + 1 == nextN) {
// normal \r\n sequense
// normal \r\n sequence
lineEnd = nextR;
nextLine = nextN + 1;
} else {
@ -124,8 +116,9 @@ size_t AsyncEventSourceMessage::ack(size_t len, __attribute__((unused)) uint32_t
}
size_t AsyncEventSourceMessage::write(AsyncClient *client) {
if (!client)
if (!client) {
return 0;
}
if (_sent >= _data->length() || !client->canSend()) {
return 0;
@ -155,19 +148,42 @@ size_t AsyncEventSourceMessage::send(AsyncClient* client) {
// Client
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server)
: _client(request->client()), _server(server) {
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) : _client(request->client()), _server(server) {
if (request->hasHeader(T_Last_Event_ID))
if (request->hasHeader(T_Last_Event_ID)) {
_lastId = atoi(request->getHeader(T_Last_Event_ID)->value().c_str());
}
_client->setRxTimeout(0);
_client->onError(NULL, NULL);
_client->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; static_cast<AsyncEventSourceClient*>(r)->_onAck(len, time); }, this);
_client->onPoll([](void* r, AsyncClient* c) { (void)c; static_cast<AsyncEventSourceClient*>(r)->_onPoll(); }, this);
_client->onAck(
[](void *r, AsyncClient *c, size_t len, uint32_t time) {
(void)c;
static_cast<AsyncEventSourceClient *>(r)->_onAck(len, time);
},
this
);
_client->onPoll(
[](void *r, AsyncClient *c) {
(void)c;
static_cast<AsyncEventSourceClient *>(r)->_onPoll();
},
this
);
_client->onData(NULL, NULL);
_client->onTimeout([this](void* r, AsyncClient* c __attribute__((unused)), uint32_t time) { static_cast<AsyncEventSourceClient*>(r)->_onTimeout(time); }, this);
_client->onDisconnect([this](void* r, AsyncClient* c) { static_cast<AsyncEventSourceClient*>(r)->_onDisconnect(); delete c; }, this);
_client->onTimeout(
[this](void *r, AsyncClient *c __attribute__((unused)), uint32_t time) {
static_cast<AsyncEventSourceClient *>(r)->_onTimeout(time);
},
this
);
_client->onDisconnect(
[this](void *r, AsyncClient *c) {
static_cast<AsyncEventSourceClient *>(r)->_onDisconnect();
delete c;
},
this
);
_server->_addClient(this);
delete request;
@ -249,10 +265,11 @@ void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t
#endif
// adjust in-flight len
if (len < _inflight)
if (len < _inflight) {
_inflight -= len;
else
} else {
_inflight = 0;
}
// acknowledge as much messages's data as we got confirmed len from a AsyncTCP
while (len && _messageQueue.size()) {
@ -264,9 +281,10 @@ void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t
}
// try to send another batch of data
if (_messageQueue.size())
if (_messageQueue.size()) {
_runQueue();
}
}
void AsyncEventSourceClient::_onPoll() {
if (_messageQueue.size()) {
@ -279,31 +297,36 @@ void AsyncEventSourceClient::_onPoll() {
}
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))) {
if (_client)
if (_client) {
_client->close(true);
}
}
void AsyncEventSourceClient::_onDisconnect() {
if (!_client)
if (!_client) {
return;
}
_client = nullptr;
_server->_handleDisconnect(this);
}
void AsyncEventSourceClient::close() {
if (_client)
if (_client) {
_client->close();
}
}
bool AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
if (!connected())
if (!connected()) {
return false;
}
return _queueMessage(std::make_shared<String>(generateEventMessage(message, event, id, reconnect)));
}
void AsyncEventSourceClient::_runQueue() {
if (!_client)
if (!_client) {
return;
}
// there is no need to lock the mutex here, 'cause all the calls to this method must be already lock'ed
size_t total_bytes_written = 0;
@ -320,14 +343,16 @@ void AsyncEventSourceClient::_runQueue() {
}
// flush socket
if (total_bytes_written)
if (total_bytes_written) {
_client->send();
}
}
void AsyncEventSourceClient::set_max_inflight_bytes(size_t value) {
if (value >= SSE_MIN_INFLIGH && value <= SSE_MAX_INFLIGH)
if (value >= SSE_MIN_INFLIGH && value <= SSE_MAX_INFLIGH) {
_max_inflight = value;
}
}
/* AsyncEventSource */
@ -338,27 +363,32 @@ void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) {
}
void AsyncEventSource::_addClient(AsyncEventSourceClient *client) {
if (!client)
if (!client) {
return;
}
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
_clients.emplace_back(client);
if (_connectcb)
if (_connectcb) {
_connectcb(client);
}
_adjust_inflight_window();
}
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient *client) {
if (_disconnectcb)
if (_disconnectcb) {
_disconnectcb(client);
}
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
for (auto i = _clients.begin(); i != _clients.end(); ++i) {
if (i->get() == client)
if (i->get() == client) {
_clients.erase(i);
break;
}
}
_adjust_inflight_window();
}
@ -371,10 +401,11 @@ void AsyncEventSource::close() {
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
for (const auto &c : _clients) {
if (c->connected())
if (c->connected()) {
c->close();
}
}
}
// pmb fix
size_t AsyncEventSource::avgPacketsWaiting() const {
@ -383,8 +414,9 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
if (!_clients.size())
if (!_clients.size()) {
return 0;
}
for (const auto &c : _clients) {
if (c->connected()) {
@ -395,8 +427,7 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up
}
AsyncEventSource::SendStatus AsyncEventSource::send(
const char* message, const char* event, uint32_t id, uint32_t reconnect) {
AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
AsyncEvent_SharedData_t shared_msg = std::make_shared<String>(generateEventMessage(message, event, id, reconnect));
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
@ -404,11 +435,12 @@ AsyncEventSource::SendStatus AsyncEventSource::send(
size_t hits = 0;
size_t miss = 0;
for (const auto &c : _clients) {
if (c->write(shared_msg))
if (c->write(shared_msg)) {
++hits;
else
} else {
++miss;
}
}
return hits == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
}
@ -417,9 +449,11 @@ size_t AsyncEventSource::count() const {
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
size_t n_clients{0};
for (const auto& i : _clients)
if (i->connected())
for (const auto &i : _clients) {
if (i->connected()) {
++n_clients;
}
}
return n_clients;
}
@ -435,8 +469,9 @@ void AsyncEventSource::handleRequest(AsyncWebServerRequest* request) {
void AsyncEventSource::_adjust_inflight_window() {
if (_clients.size()) {
size_t inflight = SSE_MAX_INFLIGH / _clients.size();
for (const auto& c : _clients)
for (const auto &c : _clients) {
c->set_max_inflight_bytes(inflight);
}
// Serial.printf("adjusted inflight to: %u\n", inflight);
}
}

View File

@ -1,22 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCEVENTSOURCE_H_
#define ASYNCEVENTSOURCE_H_
@ -37,8 +21,8 @@
#endif
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
#define SSE_MAX_INFLIGH 8 * 1024 // but no more than 8k, no need to blow it, since same data is kept in local Q
#elif defined(TARGET_RP2040)
#include <AsyncTCP_RP2040W.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 32
#endif
@ -76,18 +60,26 @@ class AsyncEventSourceMessage {
public:
AsyncEventSourceMessage(AsyncEvent_SharedData_t data) : _data(data){};
#ifdef ESP32
#if defined(ESP32)
AsyncEventSourceMessage(const char *data, size_t len) : _data(std::make_shared<String>(data, len)){};
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
AsyncEventSourceMessage(const char *data, size_t len) : _data(std::make_shared<String>()) {
if (data && len > 0) {
_data->concat(data, len);
}
};
#else
// esp8266's String does not have constructor with data/length arguments. Use a concat method here
AsyncEventSourceMessage(const char* data, size_t len) { _data->concat(data, len); };
AsyncEventSourceMessage(const char *data, size_t len) {
_data->concat(data, len);
};
#endif
/**
* @brief acknowledge sending len bytes of data
* @note if num of bytes to ack is larger then the unacknowledged message length the number of carried over bytes are returned
*
* @param len bytes to acknowlegde
* @param len bytes to acknowledge
* @param time
* @return size_t number of extra bytes carried over
*/
@ -111,13 +103,17 @@ class AsyncEventSourceMessage {
size_t send(AsyncClient *client);
// returns true if full message's length were acked
bool finished() { return _acked == _data->length(); }
bool finished() {
return _acked == _data->length();
}
/**
* @brief returns true if all data has been sent already
*
*/
bool sent() { return _sent == _data->length(); }
bool sent() {
return _sent == _data->length();
}
};
/**
@ -155,8 +151,12 @@ class AsyncEventSourceClient {
* @return false if queue is full
*/
bool send(const char *message, const char *event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
bool send(const String& message, const String& event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event.c_str(), id, reconnect); }
bool send(const String& message, const char* event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event, id, reconnect); }
bool send(const String &message, const String &event, uint32_t id = 0, uint32_t reconnect = 0) {
return send(message.c_str(), event.c_str(), id, reconnect);
}
bool send(const String &message, const char *event, uint32_t id = 0, uint32_t reconnect = 0) {
return send(message.c_str(), event, id, reconnect);
}
/**
* @brief place supplied preformatted SSE message to the message queue
@ -166,20 +166,32 @@ class AsyncEventSourceClient {
* @return true on success
* @return false on queue overflow or no client connected
*/
bool write(AsyncEvent_SharedData_t message) { return connected() && _queueMessage(std::move(message)); };
bool write(AsyncEvent_SharedData_t message) {
return connected() && _queueMessage(std::move(message));
};
[[deprecated("Use _write(AsyncEvent_SharedData_t message) instead to share same data with multiple SSE clients")]]
bool write(const char* message, size_t len) { return connected() && _queueMessage(message, len); };
bool write(const char *message, size_t len) {
return connected() && _queueMessage(message, len);
};
// close client's connection
void close();
// getters
AsyncClient* client() { return _client; }
bool connected() const { return _client && _client->connected(); }
uint32_t lastId() const { return _lastId; }
size_t packetsWaiting() const { return _messageQueue.size(); };
AsyncClient *client() {
return _client;
}
bool connected() const {
return _client && _client->connected();
}
uint32_t lastId() const {
return _lastId;
}
size_t packetsWaiting() const {
return _messageQueue.size();
};
/**
* @brief Sets max amount of bytes that could be written to client's socket while awaiting delivery acknowledge
@ -195,7 +207,9 @@ class AsyncEventSourceClient {
*
* @return size_t
*/
size_t get_max_inflight_bytes() const { return _max_inflight; }
size_t get_max_inflight_bytes() const {
return _max_inflight;
}
// system callbacks (do not call if from user code!)
void _onAck(size_t len, uint32_t time);
@ -233,9 +247,13 @@ class AsyncEventSource : public AsyncWebHandler {
AsyncEventSource(const char *url) : _url(url){};
AsyncEventSource(const String &url) : _url(url){};
~AsyncEventSource() { close(); };
~AsyncEventSource() {
close();
};
const char* url() const { return _url.c_str(); }
const char *url() const {
return _url.c_str();
}
// close all connected clients
void close();
@ -245,7 +263,9 @@ class AsyncEventSource : public AsyncWebHandler {
*
* @param cb
*/
void onConnect(ArEventHandlerFunction cb) { _connectcb = cb; }
void onConnect(ArEventHandlerFunction cb) {
_connectcb = cb;
}
/**
* @brief Send an SSE message to client
@ -258,11 +278,17 @@ class AsyncEventSource : public AsyncWebHandler {
* @return SendStatus if message was placed in any/all/part of the client's queues
*/
SendStatus send(const char *message, const char *event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
SendStatus send(const String& message, const String& event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event.c_str(), id, reconnect); }
SendStatus send(const String& message, const char* event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event, id, reconnect); }
SendStatus send(const String &message, const String &event, uint32_t id = 0, uint32_t reconnect = 0) {
return send(message.c_str(), event.c_str(), id, reconnect);
}
SendStatus send(const String &message, const char *event, uint32_t id = 0, uint32_t reconnect = 0) {
return send(message.c_str(), event, id, reconnect);
}
// The client pointer sent to the callback is only for reference purposes. DO NOT CALL ANY METHOD ON IT !
void onDisconnect(ArEventHandlerFunction cb) { _disconnectcb = cb; }
void onDisconnect(ArEventHandlerFunction cb) {
_disconnectcb = cb;
}
void authorizeConnect(ArAuthorizeConnectHandler cb);
// returns number of connected clients
@ -286,7 +312,9 @@ class AsyncEventSourceResponse : public AsyncWebServerResponse {
AsyncEventSourceResponse(AsyncEventSource *server);
void _respond(AsyncWebServerRequest *request);
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
bool _sourceValid() const { return true; }
bool _sourceValid() const {
return true;
}
};
#endif /* ASYNCEVENTSOURCE_H_ */

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include "AsyncJson.h"
#if ASYNC_JSON_SUPPORT == 1
@ -6,29 +9,32 @@
AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_json;
if (isArray)
if (isArray) {
_root = _jsonBuffer.createArray();
else
} else {
_root = _jsonBuffer.createObject();
}
}
#elif ARDUINOJSON_VERSION_MAJOR == 6
AsyncJsonResponse::AsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_json;
if (isArray)
if (isArray) {
_root = _jsonBuffer.createNestedArray();
else
} else {
_root = _jsonBuffer.createNestedObject();
}
}
#else
AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_json;
if (isArray)
if (isArray) {
_root = _jsonBuffer.add<JsonArray>();
else
} else {
_root = _jsonBuffer.add<JsonObject>();
}
}
#endif
size_t AsyncJsonResponse::setLength() {
@ -90,14 +96,17 @@ AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String& uri, ArJs
#endif
bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method()))
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
return false;
}
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
return false;
}
if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_json))
if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_json)) {
return false;
}
return true;
}
@ -141,6 +150,13 @@ void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest* request, uin
_contentLength = total;
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
request->_tempObject = malloc(total);
if (request->_tempObject == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
}
if (request->_tempObject != NULL) {
memcpy((uint8_t *)(request->_tempObject) + index, data, len);

View File

@ -1,37 +1,6 @@
// AsyncJson.h
/*
Async Response to use with ArduinoJson and AsyncWebServer
Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon.
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Example of callback in use
server.on("/json", HTTP_ANY, [](AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse();
JsonObject& root = response->getRoot();
root["key1"] = "key number one";
JsonObject& nested = root.createNestedObject("nested");
nested["key1"] = "key number one";
response->setLength();
request->send(response);
});
--------------------
Async Request to use with ArduinoJson and AsyncWebServer
Written by Arsène von Wyss (avonwyss)
Example
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint");
handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
JsonObject jsonObj = json.as<JsonObject>();
// ...
});
server.addHandler(handler);
*/
#ifndef ASYNC_JSON_H_
#define ASYNC_JSON_H_
@ -74,13 +43,21 @@ class AsyncJsonResponse : public AsyncAbstractResponse {
#else
AsyncJsonResponse(bool isArray = false);
#endif
JsonVariant& getRoot() { return _root; }
bool _sourceValid() const { return _isValid; }
JsonVariant &getRoot() {
return _root;
}
bool _sourceValid() const {
return _isValid;
}
size_t setLength();
size_t getSize() const { return _jsonBuffer.size(); }
size_t getSize() const {
return _jsonBuffer.size();
}
size_t _fillBuffer(uint8_t *data, size_t len);
#if ARDUINOJSON_VERSION_MAJOR >= 6
bool overflowed() const { return _jsonBuffer.overflowed(); }
bool overflowed() const {
return _jsonBuffer.overflowed();
}
#endif
};
@ -115,15 +92,26 @@ class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest = nullptr);
#endif
void setMethod(WebRequestMethodComposite method) { _method = method; }
void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; }
void onRequest(ArJsonRequestHandlerFunction fn) { _onRequest = fn; }
void setMethod(WebRequestMethodComposite method) {
_method = method;
}
void setMaxContentLength(int maxContentLength) {
_maxContentLength = maxContentLength;
}
void onRequest(ArJsonRequestHandlerFunction fn) {
_onRequest = fn;
}
bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest *request) override final;
void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) override final {}
void handleUpload(
__unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
__unused bool final
) override final {}
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final;
bool isRequestHandlerTrivial() const override final { return !_onRequest; }
bool isRequestHandlerTrivial() const override final {
return !_onRequest;
}
};
#endif // ASYNC_JSON_SUPPORT == 1

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include "AsyncMessagePack.h"
#if ASYNC_MSG_PACK_SUPPORT == 1
@ -6,20 +9,22 @@
AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_msgpack;
if (isArray)
if (isArray) {
_root = _jsonBuffer.createNestedArray();
else
} else {
_root = _jsonBuffer.createNestedObject();
}
}
#else
AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray) : _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_msgpack;
if (isArray)
if (isArray) {
_root = _jsonBuffer.add<JsonArray>();
else
} else {
_root = _jsonBuffer.add<JsonObject>();
}
}
#endif
size_t AsyncMessagePackResponse::setLength() {
@ -37,7 +42,9 @@ size_t AsyncMessagePackResponse::_fillBuffer(uint8_t* data, size_t len) {
}
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest, size_t maxJsonBufferSize)
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(
const String &uri, ArMessagePackRequestHandlerFunction onRequest, size_t maxJsonBufferSize
)
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
#else
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest)
@ -45,14 +52,17 @@ AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const Str
#endif
bool AsyncCallbackMessagePackWebHandler::canHandle(AsyncWebServerRequest *request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method()))
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
return false;
}
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
return false;
}
if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack))
if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack)) {
return false;
}
return true;
}
@ -92,6 +102,13 @@ void AsyncCallbackMessagePackWebHandler::handleBody(AsyncWebServerRequest* reque
_contentLength = total;
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
request->_tempObject = malloc(total);
if (request->_tempObject == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
}
if (request->_tempObject != NULL) {
memcpy((uint8_t *)(request->_tempObject) + index, data, len);

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#pragma once
/*
@ -58,13 +61,21 @@ class AsyncMessagePackResponse : public AsyncAbstractResponse {
#else
AsyncMessagePackResponse(bool isArray = false);
#endif
JsonVariant& getRoot() { return _root; }
bool _sourceValid() const { return _isValid; }
JsonVariant &getRoot() {
return _root;
}
bool _sourceValid() const {
return _isValid;
}
size_t setLength();
size_t getSize() const { return _jsonBuffer.size(); }
size_t getSize() const {
return _jsonBuffer.size();
}
size_t _fillBuffer(uint8_t *data, size_t len);
#if ARDUINOJSON_VERSION_MAJOR >= 6
bool overflowed() const { return _jsonBuffer.overflowed(); }
bool overflowed() const {
return _jsonBuffer.overflowed();
}
#endif
};
@ -83,20 +94,33 @@ class AsyncCallbackMessagePackWebHandler : public AsyncWebHandler {
public:
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
AsyncCallbackMessagePackWebHandler(
const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE
);
#else
AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr);
#endif
void setMethod(WebRequestMethodComposite method) { _method = method; }
void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; }
void onRequest(ArMessagePackRequestHandlerFunction fn) { _onRequest = fn; }
void setMethod(WebRequestMethodComposite method) {
_method = method;
}
void setMaxContentLength(int maxContentLength) {
_maxContentLength = maxContentLength;
}
void onRequest(ArMessagePackRequestHandlerFunction fn) {
_onRequest = fn;
}
bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest *request) override final;
void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) override final {}
void handleUpload(
__unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
__unused bool final
) override final {}
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final;
bool isRequestHandlerTrivial() const override final { return !_onRequest; }
bool isRequestHandlerTrivial() const override final {
return !_onRequest;
}
};
#endif // ASYNC_MSG_PACK_SUPPORT == 1

View File

@ -1,22 +1,32 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include <ESPAsyncWebServer.h>
AsyncWebHeader::AsyncWebHeader(const String &data) {
if (!data)
if (!data) {
return;
}
int index = data.indexOf(':');
if (index < 0)
if (index < 0) {
return;
}
_name = data.substring(0, index);
_value = data.substring(index + 2);
}
String AsyncWebHeader::toString() const {
String str;
str.reserve(_name.length() + _value.length() + 2);
if (str.reserve(_name.length() + _value.length() + 2)) {
str.concat(_name);
str.concat((char)0x3a);
str.concat((char)0x20);
str.concat(_value);
str.concat(asyncsrv::T_rn);
} else {
#ifdef ESP32
log_e("Failed to allocate");
#endif
}
return str;
}

View File

@ -0,0 +1,40 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/** Major version number (X.x.x) */
#define ASYNCWEBSERVER_VERSION_MAJOR 3
/** Minor version number (x.X.x) */
#define ASYNCWEBSERVER_VERSION_MINOR 7
/** Patch version number (x.x.X) */
#define ASYNCWEBSERVER_VERSION_PATCH 2
/**
* Macro to convert version number into an integer
*
* To be used in comparisons, such as ASYNCWEBSERVER_VERSION >= ASYNCWEBSERVER_VERSION_VAL(2, 0, 0)
*/
#define ASYNCWEBSERVER_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
/**
* Current version, as an integer
*
* To be used in comparisons, such as ASYNCWEBSERVER_VERSION_NUM >= ASYNCWEBSERVER_VERSION_VAL(2, 0, 0)
*/
#define ASYNCWEBSERVER_VERSION_NUM ASYNCWEBSERVER_VERSION_VAL(ASYNCWEBSERVER_VERSION_MAJOR, ASYNCWEBSERVER_VERSION_MINOR, ASYNCWEBSERVER_VERSION_PATCH)
/**
* Current version, as string
*/
#define df2xstr(s) #s
#define df2str(s) df2xstr(s)
#define ASYNCWEBSERVER_VERSION df2str(ASYNCWEBSERVER_VERSION_MAJOR) "." df2str(ASYNCWEBSERVER_VERSION_MINOR) "." df2str(ASYNCWEBSERVER_VERSION_PATCH)
#ifdef __cplusplus
}
#endif

View File

@ -1,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "AsyncWebSocket.h"
#include "Arduino.h"
@ -32,18 +15,20 @@
#include <SHA1Builder.h>
#endif
#include <rom/ets_sys.h>
#elif defined(TARGET_RP2040) || defined(ESP8266)
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(ESP8266)
#include <Hash.h>
#endif
using namespace asyncsrv;
size_t webSocketSendFrameWindow(AsyncClient *client) {
if (!client || !client->canSend())
if (!client || !client->canSend()) {
return 0;
}
size_t space = client->space();
if (space < 9)
if (space < 9) {
return 0;
}
return space - 8;
}
@ -66,30 +51,35 @@ size_t webSocketSendFrame(AsyncClient* client, bool final, uint8_t opcode, bool
mbuf[2] = rand() % 0xFF;
mbuf[3] = rand() % 0xFF;
}
if (len > 125)
if (len > 125) {
headLen += 2;
}
if (space < headLen) {
// Serial.println("SF 2");
return 0;
}
space -= headLen;
if (len > space)
if (len > space) {
len = space;
}
uint8_t *buf = (uint8_t *)malloc(headLen);
if (buf == NULL) {
// os_printf("could not malloc %u bytes for frame header\n", headLen);
// Serial.println("SF 3");
#ifdef ESP32
log_e("Failed to allocate");
#endif
client->abort();
return 0;
}
buf[0] = opcode & 0x0F;
if (final)
if (final) {
buf[0] |= 0x80;
if (len < 126)
}
if (len < 126) {
buf[1] = len & 0x7F;
else {
} else {
buf[1] = 126;
buf[2] = (uint8_t)((len >> 8) & 0xFF);
buf[3] = (uint8_t)(len & 0xFF);
@ -109,9 +99,10 @@ size_t webSocketSendFrame(AsyncClient* client, bool final, uint8_t opcode, bool
if (len) {
if (len && mask) {
size_t i;
for (i = 0; i < len; i++)
for (i = 0; i < len; i++) {
data[i] = data[i] ^ mbuf[i % 4];
}
}
if (client->add((const char *)data, len) != len) {
// os_printf("error adding %lu data bytes\n", len);
// Serial.println("SF 5");
@ -131,8 +122,7 @@ size_t webSocketSendFrame(AsyncClient* client, bool final, uint8_t opcode, bool
* AsyncWebSocketMessageBuffer
*/
AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size)
: _buffer(std::make_shared<std::vector<uint8_t>>(size)) {
AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const uint8_t *data, size_t size) : _buffer(std::make_shared<std::vector<uint8_t>>(size)) {
if (_buffer->capacity() < size) {
_buffer->reserve(size);
} else {
@ -140,16 +130,16 @@ AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const uint8_t* data, si
}
}
AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size)
: _buffer(std::make_shared<std::vector<uint8_t>>(size)) {
AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size) : _buffer(std::make_shared<std::vector<uint8_t>>(size)) {
if (_buffer->capacity() < size) {
_buffer->reserve(size);
}
}
bool AsyncWebSocketMessageBuffer::reserve(size_t size) {
if (_buffer->capacity() >= size)
if (_buffer->capacity() >= size) {
return true;
}
_buffer->reserve(size);
return _buffer->capacity() >= size;
}
@ -169,30 +159,44 @@ class AsyncWebSocketControl {
public:
AsyncWebSocketControl(uint8_t opcode, const uint8_t *data = NULL, size_t len = 0, bool mask = false)
: _opcode(opcode), _len(len), _mask(len && mask), _finished(false) {
if (data == NULL)
if (data == NULL) {
_len = 0;
}
if (_len) {
if (_len > 125)
if (_len > 125) {
_len = 125;
}
_data = (uint8_t *)malloc(_len);
if (_data == NULL)
if (_data == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
_len = 0;
else
} else {
memcpy(_data, data, len);
} else
}
} else {
_data = NULL;
}
}
~AsyncWebSocketControl() {
if (_data != NULL)
if (_data != NULL) {
free(_data);
}
}
bool finished() const { return _finished; }
uint8_t opcode() { return _opcode; }
uint8_t len() { return _len + 2; }
bool finished() const {
return _finished;
}
uint8_t opcode() {
return _opcode;
}
uint8_t len() {
return _len + 2;
}
size_t send(AsyncClient *client) {
_finished = true;
return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len);
@ -203,11 +207,8 @@ class AsyncWebSocketControl {
* AsyncWebSocketMessage Message
*/
AsyncWebSocketMessage::AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask) : _WSbuffer{buffer},
_opcode(opcode & 0x07),
_mask{mask},
_status{_WSbuffer ? WS_MSG_SENDING : WS_MSG_ERROR} {
}
AsyncWebSocketMessage::AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask)
: _WSbuffer{buffer}, _opcode(opcode & 0x07), _mask{mask}, _status{_WSbuffer ? WS_MSG_SENDING : WS_MSG_ERROR} {}
void AsyncWebSocketMessage::ack(size_t len, uint32_t time) {
(void)time;
@ -219,17 +220,20 @@ void AsyncWebSocketMessage::ack(size_t len, uint32_t time) {
}
size_t AsyncWebSocketMessage::send(AsyncClient *client) {
if (!client)
if (!client) {
return 0;
}
if (_status != WS_MSG_SENDING)
if (_status != WS_MSG_SENDING) {
return 0;
}
if (_acked < _ack) {
return 0;
}
if (_sent == _WSbuffer->size()) {
if (_acked == _ack)
if (_acked == _ack) {
_status = WS_MSG_SENT;
}
return 0;
}
if (_sent > _WSbuffer->size()) {
@ -271,8 +275,7 @@ size_t AsyncWebSocketMessage::send(AsyncClient* client) {
const char *AWSC_PING_PAYLOAD = "ESPAsyncWebServer-PING";
const size_t AWSC_PING_PAYLOAD_LEN = 22;
AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest* request, AsyncWebSocket* server)
: _tempObject(NULL) {
AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server) : _tempObject(NULL) {
_client = request->client();
_server = server;
_clientId = _server->_getNextId();
@ -281,12 +284,48 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest* request, Async
_lastMessageTime = millis();
_keepAlivePeriod = 0;
_client->setRxTimeout(0);
_client->onError([](void* r, AsyncClient* c, int8_t error) { (void)c; ((AsyncWebSocketClient*)(r))->_onError(error); }, this);
_client->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; ((AsyncWebSocketClient*)(r))->_onAck(len, time); }, this);
_client->onDisconnect([](void* r, AsyncClient* c) { ((AsyncWebSocketClient*)(r))->_onDisconnect(); delete c; }, this);
_client->onTimeout([](void* r, AsyncClient* c, uint32_t time) { (void)c; ((AsyncWebSocketClient*)(r))->_onTimeout(time); }, this);
_client->onData([](void* r, AsyncClient* c, void* buf, size_t len) { (void)c; ((AsyncWebSocketClient*)(r))->_onData(buf, len); }, this);
_client->onPoll([](void* r, AsyncClient* c) { (void)c; ((AsyncWebSocketClient*)(r))->_onPoll(); }, this);
_client->onError(
[](void *r, AsyncClient *c, int8_t error) {
(void)c;
((AsyncWebSocketClient *)(r))->_onError(error);
},
this
);
_client->onAck(
[](void *r, AsyncClient *c, size_t len, uint32_t time) {
(void)c;
((AsyncWebSocketClient *)(r))->_onAck(len, time);
},
this
);
_client->onDisconnect(
[](void *r, AsyncClient *c) {
((AsyncWebSocketClient *)(r))->_onDisconnect();
delete c;
},
this
);
_client->onTimeout(
[](void *r, AsyncClient *c, uint32_t time) {
(void)c;
((AsyncWebSocketClient *)(r))->_onTimeout(time);
},
this
);
_client->onData(
[](void *r, AsyncClient *c, void *buf, size_t len) {
(void)c;
((AsyncWebSocketClient *)(r))->_onData(buf, len);
},
this
);
_client->onPoll(
[](void *r, AsyncClient *c) {
(void)c;
((AsyncWebSocketClient *)(r))->_onPoll();
},
this
);
delete request;
memset(&_pinfo, 0, sizeof(_pinfo));
}
@ -303,9 +342,10 @@ AsyncWebSocketClient::~AsyncWebSocketClient() {
}
void AsyncWebSocketClient::_clearQueue() {
while (!_messageQueue.empty() && _messageQueue.front().finished())
while (!_messageQueue.empty() && _messageQueue.front().finished()) {
_messageQueue.pop_front();
}
}
void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
_lastMessageTime = millis();
@ -321,8 +361,9 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
if (_status == WS_DISCONNECTING && head.opcode() == WS_DISCONNECT) {
_controlQueue.pop_front();
_status = WS_DISCONNECTED;
if (_client)
if (_client) {
_client->close(true);
}
return;
}
_controlQueue.pop_front();
@ -339,8 +380,9 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
}
void AsyncWebSocketClient::_onPoll() {
if (!_client)
if (!_client) {
return;
}
#ifdef ESP32
std::unique_lock<std::mutex> lock(_lock);
@ -357,12 +399,14 @@ void AsyncWebSocketClient::_onPoll() {
void AsyncWebSocketClient::_runQueue() {
// all calls to this method MUST be protected by a mutex lock!
if (!_client)
if (!_client) {
return;
}
_clearQueue();
if (!_controlQueue.empty() && (_messageQueue.empty() || _messageQueue.front().betweenFrames()) && webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front().len() - 1)) {
if (!_controlQueue.empty() && (_messageQueue.empty() || _messageQueue.front().betweenFrames())
&& webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front().len() - 1)) {
_controlQueue.front().send(_client);
} else if (!_messageQueue.empty() && _messageQueue.front().betweenFrames() && webSocketSendFrameWindow(_client)) {
_messageQueue.front().send(_client);
@ -391,8 +435,9 @@ bool AsyncWebSocketClient::canSend() const {
}
bool AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t *data, size_t len, bool mask) {
if (!_client)
if (!_client) {
return false;
}
#ifdef ESP32
std::lock_guard<std::mutex> lock(_lock);
@ -400,15 +445,17 @@ bool AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t* data, si
_controlQueue.emplace_back(opcode, data, len, mask);
if (_client && _client->canSend())
if (_client && _client->canSend()) {
_runQueue();
}
return true;
}
bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask) {
if (!_client || buffer->size() == 0 || _status != WS_CONNECTED)
if (!_client || buffer->size() == 0 || _status != WS_CONNECTED) {
return false;
}
#ifdef ESP32
std::lock_guard<std::mutex> lock(_lock);
@ -418,8 +465,9 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint
if (closeWhenFull) {
_status = WS_DISCONNECTED;
if (_client)
if (_client) {
_client->close(true);
}
#ifdef ESP8266
ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: closing connection\n");
@ -440,15 +488,17 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint
_messageQueue.emplace_back(buffer, opcode, mask);
if (_client && _client->canSend())
if (_client && _client->canSend()) {
_runQueue();
}
return true;
}
void AsyncWebSocketClient::close(uint16_t code, const char *message) {
if (_status != WS_CONNECTED)
if (_status != WS_CONNECTED) {
return;
}
_status = WS_DISCONNECTING;
@ -456,8 +506,9 @@ void AsyncWebSocketClient::close(uint16_t code, const char* message) {
uint8_t packetLen = 2;
if (message != NULL) {
size_t mlen = strlen(message);
if (mlen > 123)
if (mlen > 123) {
mlen = 123;
}
packetLen += mlen;
}
char *buf = (char *)malloc(packetLen);
@ -470,6 +521,11 @@ void AsyncWebSocketClient::close(uint16_t code, const char* message) {
_queueControl(WS_DISCONNECT, (uint8_t *)buf, packetLen);
free(buf);
return;
} else {
#ifdef ESP32
log_e("Failed to allocate");
_client->abort();
#endif
}
}
_queueControl(WS_DISCONNECT);
@ -484,8 +540,9 @@ void AsyncWebSocketClient::_onError(int8_t) {
}
void AsyncWebSocketClient::_onTimeout(uint32_t time) {
if (!_client)
if (!_client) {
return;
}
// Serial.println("onTime");
(void)time;
_client->close(true);
@ -522,12 +579,14 @@ void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
plen -= 2;
} else if (_pinfo.len == 127 && plen >= 8) {
_pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56;
_pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32
| (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56;
data += 8;
plen -= 8;
}
if (_pinfo.masked && plen >= 4) { // if ws.close() is called, Safari sends a close frame with plen 2 and masked bit set. We must not decrement plen which is already 0.
if (_pinfo.masked
&& plen >= 4) { // if ws.close() is called, Safari sends a close frame with plen 2 and masked bit set. We must not decrement plen which is already 0.
memcpy(_pinfo.mask, data, 4);
data += 4;
plen -= 4;
@ -538,9 +597,10 @@ void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
const auto datalast = data[datalen];
if (_pinfo.masked) {
for (size_t i = 0; i < datalen; i++)
for (size_t i = 0; i < datalen; i++) {
data[i] ^= _pinfo.mask[(_pinfo.index + i) % 4];
}
}
if ((datalen + _pinfo.index) < _pinfo.len) {
_pstate = 1;
@ -551,8 +611,9 @@ void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
_pinfo.num = 0;
}
}
if (datalen > 0)
if (datalen > 0) {
_server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen);
}
_pinfo.index += datalen;
} else if ((datalen + _pinfo.index) == _pinfo.len) {
@ -567,27 +628,31 @@ void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
}
if (_status == WS_DISCONNECTING) {
_status = WS_DISCONNECTED;
if (_client)
if (_client) {
_client->close(true);
}
} else {
_status = WS_DISCONNECTING;
if (_client)
if (_client) {
_client->ackLater();
}
_queueControl(WS_DISCONNECT, data, datalen);
}
} else if (_pinfo.opcode == WS_PING) {
_server->_handleEvent(this, WS_EVT_PING, NULL, NULL, 0);
_queueControl(WS_PONG, data, datalen);
} else if (_pinfo.opcode == WS_PONG) {
if (datalen != AWSC_PING_PAYLOAD_LEN || memcmp(AWSC_PING_PAYLOAD, data, AWSC_PING_PAYLOAD_LEN) != 0)
if (datalen != AWSC_PING_PAYLOAD_LEN || memcmp(AWSC_PING_PAYLOAD, data, AWSC_PING_PAYLOAD_LEN) != 0) {
_server->_handleEvent(this, WS_EVT_PONG, NULL, NULL, 0);
}
} else if (_pinfo.opcode < WS_DISCONNECT) { // continuation or text/binary frame
_server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen);
if (_pinfo.final)
if (_pinfo.final) {
_pinfo.num = 0;
else
} else {
_pinfo.num += 1;
}
}
} else {
// os_printf("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len);
// what should we do?
@ -595,8 +660,9 @@ void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
}
// restore byte as _handleEvent may have added a null terminator i.e., data[len] = 0;
if (datalen)
if (datalen) {
data[datalen] = datalast;
}
data += datalen;
plen -= datalen;
@ -609,13 +675,15 @@ size_t AsyncWebSocketClient::printf(const char* format, ...) {
size_t len = vsnprintf(nullptr, 0, format, arg);
va_end(arg);
if (len == 0)
if (len == 0) {
return 0;
}
char *buffer = new char[len + 1];
if (!buffer)
if (!buffer) {
return 0;
}
va_start(arg, format);
len = vsnprintf(buffer, len + 1, format, arg);
@ -633,13 +701,15 @@ size_t AsyncWebSocketClient::printf_P(PGM_P formatP, ...) {
size_t len = vsnprintf_P(nullptr, 0, formatP, arg);
va_end(arg);
if (len == 0)
if (len == 0) {
return 0;
}
char *buffer = new char[len + 1];
if (!buffer)
if (!buffer) {
return 0;
}
va_start(arg, formatP);
len = vsnprintf_P(buffer, len + 1, formatP, arg);
@ -657,7 +727,7 @@ namespace {
std::memcpy(buffer->data(), message, len);
return buffer;
}
}
} // namespace
bool AsyncWebSocketClient::text(AsyncWebSocketMessageBuffer *buffer) {
bool enqueued = false;
@ -694,8 +764,9 @@ bool AsyncWebSocketClient::text(const __FlashStringHelper* data) {
size_t n = 0;
while (1) {
if (pgm_read_byte(p + n) == 0)
if (pgm_read_byte(p + n) == 0) {
break;
}
n += 1;
}
@ -755,15 +826,17 @@ bool AsyncWebSocketClient::binary(const __FlashStringHelper* data, size_t len) {
#endif
IPAddress AsyncWebSocketClient::remoteIP() const {
if (!_client)
if (!_client) {
return IPAddress((uint32_t)0U);
}
return _client->remoteIP();
}
uint16_t AsyncWebSocketClient::remotePort() const {
if (!_client)
if (!_client) {
return 0;
}
return _client->remotePort();
}
@ -785,48 +858,62 @@ AsyncWebSocketClient* AsyncWebSocket::_newClient(AsyncWebServerRequest* request)
}
bool AsyncWebSocket::availableForWriteAll() {
return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient& c) { return c.queueIsFull(); });
return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) {
return c.queueIsFull();
});
}
bool AsyncWebSocket::availableForWrite(uint32_t id) {
const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [id](const AsyncWebSocketClient& c) { return c.id() == id; });
if (iter == std::end(_clients))
const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [id](const AsyncWebSocketClient &c) {
return c.id() == id;
});
if (iter == std::end(_clients)) {
return true;
}
return !iter->queueIsFull();
}
size_t AsyncWebSocket::count() const {
return std::count_if(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient& c) { return c.status() == WS_CONNECTED; });
return std::count_if(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) {
return c.status() == WS_CONNECTED;
});
}
AsyncWebSocketClient *AsyncWebSocket::client(uint32_t id) {
const auto iter = std::find_if(_clients.begin(), _clients.end(), [id](const AsyncWebSocketClient& c) { return c.id() == id && c.status() == WS_CONNECTED; });
if (iter == std::end(_clients))
const auto iter = std::find_if(_clients.begin(), _clients.end(), [id](const AsyncWebSocketClient &c) {
return c.id() == id && c.status() == WS_CONNECTED;
});
if (iter == std::end(_clients)) {
return nullptr;
}
return &(*iter);
}
void AsyncWebSocket::close(uint32_t id, uint16_t code, const char *message) {
if (AsyncWebSocketClient* c = client(id))
if (AsyncWebSocketClient *c = client(id)) {
c->close(code, message);
}
}
void AsyncWebSocket::closeAll(uint16_t code, const char *message) {
for (auto& c : _clients)
if (c.status() == WS_CONNECTED)
for (auto &c : _clients) {
if (c.status() == WS_CONNECTED) {
c.close(code, message);
}
}
}
void AsyncWebSocket::cleanupClients(uint16_t maxClients) {
if (count() > maxClients)
if (count() > maxClients) {
_clients.front().close();
}
for (auto iter = std::begin(_clients); iter != std::end(_clients);) {
if (iter->shouldBeDeleted())
iter = _clients.erase(iter);
else
iter++;
for (auto i = _clients.begin(); i != _clients.end(); ++i) {
if (i->shouldBeDeleted()) {
_clients.erase(i);
break;
}
}
}
@ -838,11 +925,13 @@ bool AsyncWebSocket::ping(uint32_t id, const uint8_t* data, size_t len) {
AsyncWebSocket::SendStatus AsyncWebSocket::pingAll(const uint8_t *data, size_t len) {
size_t hit = 0;
size_t miss = 0;
for (auto& c : _clients)
if (c.status() == WS_CONNECTED && c.ping(data, len))
for (auto &c : _clients) {
if (c.status() == WS_CONNECTED && c.ping(data, len)) {
hit++;
else
} else {
miss++;
}
}
return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
}
@ -866,8 +955,9 @@ bool AsyncWebSocket::text(uint32_t id, const __FlashStringHelper* data) {
size_t n = 0;
while (true) {
if (pgm_read_byte(p + n) == 0)
if (pgm_read_byte(p + n) == 0) {
break;
}
n += 1;
}
@ -914,8 +1004,9 @@ AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const __FlashStringHelper* da
size_t n = 0;
while (1) {
if (pgm_read_byte(p + n) == 0)
if (pgm_read_byte(p + n) == 0) {
break;
}
n += 1;
}
@ -942,11 +1033,13 @@ AsyncWebSocket::SendStatus AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer*
AsyncWebSocket::SendStatus AsyncWebSocket::textAll(AsyncWebSocketSharedBuffer buffer) {
size_t hit = 0;
size_t miss = 0;
for (auto& c : _clients)
if (c.status() == WS_CONNECTED && c.text(buffer))
for (auto &c : _clients) {
if (c.status() == WS_CONNECTED && c.text(buffer)) {
hit++;
else
} else {
miss++;
}
}
return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
}
@ -1029,11 +1122,13 @@ AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer
AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(AsyncWebSocketSharedBuffer buffer) {
size_t hit = 0;
size_t miss = 0;
for (auto& c : _clients)
if (c.status() == WS_CONNECTED && c.binary(buffer))
for (auto &c : _clients) {
if (c.status() == WS_CONNECTED && c.binary(buffer)) {
hit++;
else
} else {
miss++;
}
}
return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
}
@ -1055,13 +1150,15 @@ size_t AsyncWebSocket::printfAll(const char* format, ...) {
size_t len = vsnprintf(nullptr, 0, format, arg);
va_end(arg);
if (len == 0)
if (len == 0) {
return 0;
}
char *buffer = new char[len + 1];
if (!buffer)
if (!buffer) {
return 0;
}
va_start(arg, format);
len = vsnprintf(buffer, len + 1, format, arg);
@ -1091,13 +1188,15 @@ size_t AsyncWebSocket::printfAll_P(PGM_P formatP, ...) {
size_t len = vsnprintf_P(nullptr, 0, formatP, arg);
va_end(arg);
if (len == 0)
if (len == 0) {
return 0;
}
char *buffer = new char[len + 1];
if (!buffer)
if (!buffer) {
return 0;
}
va_start(arg, formatP);
len = vsnprintf_P(buffer, len + 1, formatP, arg);
@ -1155,6 +1254,13 @@ void AsyncWebSocket::handleRequest(AsyncWebServerRequest* request) {
}
const AsyncWebHeader *key = request->getHeader(WS_STR_KEY);
AsyncWebServerResponse *response = new AsyncWebSocketResponse(key->value(), this);
if (response == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
if (request->hasHeader(WS_STR_PROTOCOL)) {
const AsyncWebHeader *protocol = request->getHeader(WS_STR_PROTOCOL);
// ToDo: check protocol
@ -1164,23 +1270,11 @@ void AsyncWebSocket::handleRequest(AsyncWebServerRequest* request) {
}
AsyncWebSocketMessageBuffer *AsyncWebSocket::makeBuffer(size_t size) {
AsyncWebSocketMessageBuffer* buffer = new AsyncWebSocketMessageBuffer(size);
if (buffer->length() != size) {
delete buffer;
return nullptr;
} else {
return buffer;
}
return new AsyncWebSocketMessageBuffer(size);
}
AsyncWebSocketMessageBuffer *AsyncWebSocket::makeBuffer(const uint8_t *data, size_t size) {
AsyncWebSocketMessageBuffer* buffer = new AsyncWebSocketMessageBuffer(data, size);
if (buffer->length() != size) {
delete buffer;
return nullptr;
} else {
return buffer;
}
return new AsyncWebSocketMessageBuffer(data, size);
}
/*
@ -1196,11 +1290,14 @@ AsyncWebSocketResponse::AsyncWebSocketResponse(const String& key, AsyncWebSocket
uint8_t hash[20];
char buffer[33];
#if defined(ESP8266) || defined(TARGET_RP2040)
#if defined(ESP8266) || defined(TARGET_RP2040) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(TARGET_RP2350)
sha1(key + WS_STR_UUID, hash);
#else
String k;
k.reserve(key.length() + WS_STR_UUID_LEN);
if (!k.reserve(key.length() + WS_STR_UUID_LEN)) {
log_e("Failed to allocate");
return;
}
k.concat(key);
k.concat(WS_STR_UUID);
SHA1Builder sha1;
@ -1232,8 +1329,9 @@ void AsyncWebSocketResponse::_respond(AsyncWebServerRequest* request) {
size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
(void)time;
if (len)
if (len) {
_server->_newClient(request);
}
return 0;
}

View File

@ -1,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCWEBSOCKET_H_
#define ASYNCWEBSOCKET_H_
@ -33,8 +16,8 @@
#ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 8
#endif
#elif defined(TARGET_RP2040)
#include <AsyncTCP_RP2040W.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 32
#endif
@ -90,24 +73,32 @@ typedef struct {
uint64_t index;
} AwsFrameInfo;
typedef enum { WS_DISCONNECTED,
typedef enum {
WS_DISCONNECTED,
WS_CONNECTED,
WS_DISCONNECTING } AwsClientStatus;
typedef enum { WS_CONTINUATION,
WS_DISCONNECTING
} AwsClientStatus;
typedef enum {
WS_CONTINUATION,
WS_TEXT,
WS_BINARY,
WS_DISCONNECT = 0x08,
WS_PING,
WS_PONG } AwsFrameType;
typedef enum { WS_MSG_SENDING,
WS_PONG
} AwsFrameType;
typedef enum {
WS_MSG_SENDING,
WS_MSG_SENT,
WS_MSG_ERROR } AwsMessageStatus;
typedef enum { WS_EVT_CONNECT,
WS_MSG_ERROR
} AwsMessageStatus;
typedef enum {
WS_EVT_CONNECT,
WS_EVT_DISCONNECT,
WS_EVT_PING,
WS_EVT_PONG,
WS_EVT_ERROR,
WS_EVT_DATA } AwsEventType;
WS_EVT_DATA
} AwsEventType;
class AsyncWebSocketMessageBuffer {
friend AsyncWebSocket;
@ -122,8 +113,12 @@ class AsyncWebSocketMessageBuffer {
AsyncWebSocketMessageBuffer(const uint8_t *data, size_t size);
//~AsyncWebSocketMessageBuffer();
bool reserve(size_t size);
uint8_t* get() { return _buffer->data(); }
size_t length() const { return _buffer->size(); }
uint8_t *get() {
return _buffer->data();
}
size_t length() const {
return _buffer->size();
}
};
class AsyncWebSocketMessage {
@ -139,8 +134,12 @@ class AsyncWebSocketMessage {
public:
AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
bool finished() const { return _status != WS_MSG_SENDING; }
bool betweenFrames() const { return _acked == _ack; }
bool finished() const {
return _status != WS_MSG_SENDING;
}
bool betweenFrames() const {
return _acked == _ack;
}
void ack(size_t len, uint32_t time);
size_t send(AsyncClient *client);
@ -177,13 +176,27 @@ class AsyncWebSocketClient {
~AsyncWebSocketClient();
// client id increments for the given server
uint32_t id() const { return _clientId; }
AwsClientStatus status() const { return _status; }
AsyncClient* client() { return _client; }
const AsyncClient* client() const { return _client; }
AsyncWebSocket* server() { return _server; }
const AsyncWebSocket* server() const { return _server; }
AwsFrameInfo const& pinfo() const { return _pinfo; }
uint32_t id() const {
return _clientId;
}
AwsClientStatus status() const {
return _status;
}
AsyncClient *client() {
return _client;
}
const AsyncClient *client() const {
return _client;
}
AsyncWebSocket *server() {
return _server;
}
const AsyncWebSocket *server() const {
return _server;
}
AwsFrameInfo const &pinfo() const {
return _pinfo;
}
// - If "true" (default), the connection will be closed if the message queue is full.
// This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection.
@ -203,13 +216,19 @@ class AsyncWebSocketClient {
// Use cases:,
// - if using websocket to send logging messages, maybe some loss is acceptable.
// - But if using websocket to send UI update messages, maybe the connection should be closed and the UI redrawn.
void setCloseClientOnQueueFull(bool close) { closeWhenFull = close; }
bool willCloseClientOnQueueFull() const { return closeWhenFull; }
void setCloseClientOnQueueFull(bool close) {
closeWhenFull = close;
}
bool willCloseClientOnQueueFull() const {
return closeWhenFull;
}
IPAddress remoteIP() const;
uint16_t remotePort() const;
bool shouldBeDeleted() const { return !_client; }
bool shouldBeDeleted() const {
return !_client;
}
// control frames
void close(uint16_t code = 0, const char *message = NULL);
@ -224,7 +243,9 @@ class AsyncWebSocketClient {
}
// data packets
void message(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false) { _queueMessage(buffer, opcode, mask); }
void message(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false) {
_queueMessage(buffer, opcode, mask);
}
bool queueIsFull() const;
size_t queueLen() const;
@ -287,15 +308,23 @@ class AsyncWebSocket : public AsyncWebHandler {
explicit AsyncWebSocket(const char *url) : _url(url), _cNextId(1), _enabled(true) {}
AsyncWebSocket(const String &url) : _url(url), _cNextId(1), _enabled(true) {}
~AsyncWebSocket(){};
const char* url() const { return _url.c_str(); }
void enable(bool e) { _enabled = e; }
bool enabled() const { return _enabled; }
const char *url() const {
return _url.c_str();
}
void enable(bool e) {
_enabled = e;
}
bool enabled() const {
return _enabled;
}
bool availableForWriteAll();
bool availableForWrite(uint32_t id);
size_t count() const;
AsyncWebSocketClient *client(uint32_t id);
bool hasClient(uint32_t id) { return client(id) != nullptr; }
bool hasClient(uint32_t id) {
return client(id) != nullptr;
}
void close(uint32_t id, uint16_t code = 0, const char *message = NULL);
void closeAll(uint16_t code = 0, const char *message = NULL);
@ -344,11 +373,17 @@ class AsyncWebSocket : public AsyncWebHandler {
size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
#endif
void onEvent(AwsEventHandler handler) { _eventHandler = handler; }
void handleHandshake(AwsHandshakeHandler handler) { _handshakeHandler = handler; }
void onEvent(AwsEventHandler handler) {
_eventHandler = handler;
}
void handleHandshake(AwsHandshakeHandler handler) {
_handshakeHandler = handler;
}
// system callbacks (do not call)
uint32_t _getNextId() { return _cNextId++; }
uint32_t _getNextId() {
return _cNextId++;
}
AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request);
void _handleEvent(AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);
bool canHandle(AsyncWebServerRequest *request) const override final;
@ -358,7 +393,9 @@ class AsyncWebSocket : public AsyncWebHandler {
AsyncWebSocketMessageBuffer *makeBuffer(size_t size = 0);
AsyncWebSocketMessageBuffer *makeBuffer(const uint8_t *data, size_t size);
std::list<AsyncWebSocketClient>& getClients() { return _clients; }
std::list<AsyncWebSocketClient> &getClients() {
return _clients;
}
};
// WebServer response to authenticate the socket and detach the tcp client from the web server request
@ -371,7 +408,9 @@ class AsyncWebSocketResponse : public AsyncWebServerResponse {
AsyncWebSocketResponse(const String &key, AsyncWebSocket *server);
void _respond(AsyncWebServerRequest *request);
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
bool _sourceValid() const { return true; }
bool _sourceValid() const {
return true;
}
};
#endif /* ASYNCWEBSOCKET_H_ */

View File

@ -1,7 +1,9 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include <ChunkPrint.h>
ChunkPrint::ChunkPrint(uint8_t* destination, size_t from, size_t len)
: _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
ChunkPrint::ChunkPrint(uint8_t *destination, size_t from, size_t len) : _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
size_t ChunkPrint::write(uint8_t c) {
if (_to_skip > 0) {

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#ifndef CHUNKPRINT_H
#define CHUNKPRINT_H
@ -13,6 +16,8 @@ class ChunkPrint : public Print {
public:
ChunkPrint(uint8_t *destination, size_t from, size_t len);
size_t write(uint8_t c);
size_t write(const uint8_t* buffer, size_t size) { return this->Print::write(buffer, size); }
size_t write(const uint8_t *buffer, size_t size) {
return this->Print::write(buffer, size);
}
};
#endif

View File

@ -1,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _ESPAsyncWebServer_H_
#define _ESPAsyncWebServer_H_
@ -37,8 +20,8 @@
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040)
#include <AsyncTCP_RP2040W.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <HTTP_Method.h>
#include <WiFi.h>
#include <http_parser.h>
@ -48,10 +31,7 @@
#include "literals.h"
#define ASYNCWEBSERVER_VERSION "3.6.0"
#define ASYNCWEBSERVER_VERSION_MAJOR 3
#define ASYNCWEBSERVER_VERSION_MINOR 6
#define ASYNCWEBSERVER_VERSION_REVISION 0
#include "AsyncWebServerVersion.h"
#define ASYNCWEBSERVER_FORK_ESP32Async
#ifdef ASYNCWEBSERVER_REGEX
@ -60,6 +40,12 @@
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined")))
#endif
// See https://github.com/ESP32Async/ESPAsyncWebServer/commit/3d3456e9e81502a477f6498c44d0691499dda8f9#diff-646b25b11691c11dce25529e3abce843f0ba4bd07ab75ec9eee7e72b06dbf13fR388-R392
// This setting slowdown chunk serving but avoids crashing or deadlocks in the case where slow chunk responses are created, like file serving form SD Card
#ifndef ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
#define ASYNCWEBSERVER_USE_CHUNK_INFLIGHT 1
#endif
class AsyncWebServer;
class AsyncWebServerRequest;
class AsyncWebServerResponse;
@ -72,7 +58,7 @@ class AsyncCallbackWebHandler;
class AsyncResponseStream;
class AsyncMiddlewareChain;
#if defined(TARGET_RP2040)
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
typedef enum http_method WebRequestMethod;
#else
#ifndef WEBSERVER_H
@ -97,7 +83,7 @@ namespace fs {
static const char *write;
static const char *append;
};
};
}; // namespace fs
#else
#include "FileOpenMode.h"
#endif
@ -122,12 +108,23 @@ class AsyncWebParameter {
bool _isFile;
public:
AsyncWebParameter(const String& name, const String& value, bool form = false, bool file = false, size_t size = 0) : _name(name), _value(value), _size(size), _isForm(form), _isFile(file) {}
const String& name() const { return _name; }
const String& value() const { return _value; }
size_t size() const { return _size; }
bool isPost() const { return _isForm; }
bool isFile() const { return _isFile; }
AsyncWebParameter(const String &name, const String &value, bool form = false, bool file = false, size_t size = 0)
: _name(name), _value(value), _size(size), _isForm(form), _isFile(file) {}
const String &name() const {
return _name;
}
const String &value() const {
return _value;
}
size_t size() const {
return _size;
}
bool isPost() const {
return _isForm;
}
bool isFile() const {
return _isFile;
}
};
/*
@ -147,8 +144,12 @@ class AsyncWebHeader {
AsyncWebHeader &operator=(const AsyncWebHeader &) = default;
const String& name() const { return _name; }
const String& value() const { return _value; }
const String &name() const {
return _name;
}
const String &value() const {
return _value;
}
String toString() const;
};
@ -156,12 +157,14 @@ class AsyncWebHeader {
* REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect
* */
typedef enum { RCT_NOT_USED = -1,
typedef enum {
RCT_NOT_USED = -1,
RCT_DEFAULT = 0,
RCT_HTTP,
RCT_WS,
RCT_EVENT,
RCT_MAX } RequestedConnectionType;
RCT_MAX
} RequestedConnectionType;
// this enum is similar to Arduino WebServer's AsyncAuthType and PsychicHttp
typedef enum {
@ -176,6 +179,8 @@ typedef enum {
typedef std::function<size_t(uint8_t *, size_t, size_t)> AwsResponseFiller;
typedef std::function<String(const String &)> AwsTemplateProcessor;
using AsyncWebServerRequestPtr = std::weak_ptr<AsyncWebServerRequest>;
class AsyncWebServerRequest {
using File = fs::File;
using FS = fs::FS;
@ -189,8 +194,9 @@ class AsyncWebServerRequest {
AsyncWebServerResponse *_response;
ArDisconnectHandler _onDisconnectfn;
// response is sent
bool _sent = false;
bool _sent = false; // response is sent
bool _paused = false; // request is paused (request continuation)
std::shared_ptr<AsyncWebServerRequest> _this; // shared pointer to this request
String _temp;
uint8_t _parseState;
@ -212,7 +218,7 @@ class AsyncWebServerRequest {
std::list<AsyncWebHeader> _headers;
std::list<AsyncWebParameter> _params;
std::vector<String> _pathParams;
std::list<String> _pathParams;
std::unordered_map<const char *, String, std::hash<const char *>, std::equal_to<const char *>> _attributes;
@ -248,6 +254,9 @@ class AsyncWebServerRequest {
void _handleUploadByte(uint8_t data, bool last);
void _handleUploadEnd();
void _send();
void _runMiddlewareChain();
public:
File _tempFile;
void *_tempObject;
@ -255,23 +264,48 @@ class AsyncWebServerRequest {
AsyncWebServerRequest(AsyncWebServer *, AsyncClient *);
~AsyncWebServerRequest();
AsyncClient* client() { return _client; }
uint8_t version() const { return _version; }
WebRequestMethodComposite method() const { return _method; }
const String& url() const { return _url; }
const String& host() const { return _host; }
const String& contentType() const { return _contentType; }
size_t contentLength() const { return _contentLength; }
bool multipart() const { return _isMultipart; }
AsyncClient *client() {
return _client;
}
uint8_t version() const {
return _version;
}
WebRequestMethodComposite method() const {
return _method;
}
const String &url() const {
return _url;
}
const String &host() const {
return _host;
}
const String &contentType() const {
return _contentType;
}
size_t contentLength() const {
return _contentLength;
}
bool multipart() const {
return _isMultipart;
}
const char *methodToString() const;
const char *requestedConnTypeToString() const;
RequestedConnectionType requestedConnType() const { return _reqconntype; }
bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED) const;
bool isWebSocketUpgrade() const { return _method == HTTP_GET && isExpectedRequestedConnType(RCT_WS); }
bool isSSE() const { return _method == HTTP_GET && isExpectedRequestedConnType(RCT_EVENT); }
bool isHTTP() const { return isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP); }
RequestedConnectionType requestedConnType() const {
return _reqconntype;
}
bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED)
const;
bool isWebSocketUpgrade() const {
return _method == HTTP_GET && isExpectedRequestedConnType(RCT_WS);
}
bool isSSE() const {
return _method == HTTP_GET && isExpectedRequestedConnType(RCT_EVENT);
}
bool isHTTP() const {
return isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP);
}
void onDisconnect(ArDisconnectHandler fn);
// hash is the string representation of:
@ -279,10 +313,17 @@ class AsyncWebServerRequest {
// user:realm:md5(user:realm:pass) for digest
bool authenticate(const char *hash) const;
bool authenticate(const char *username, const char *credentials, const char *realm = NULL, bool isHash = false) const;
void requestAuthentication(const char* realm = nullptr, bool isDigest = true) { requestAuthentication(isDigest ? AsyncAuthType::AUTH_DIGEST : AsyncAuthType::AUTH_BASIC, realm); }
void requestAuthentication(const char *realm = nullptr, bool isDigest = true) {
requestAuthentication(isDigest ? AsyncAuthType::AUTH_DIGEST : AsyncAuthType::AUTH_BASIC, realm);
}
void requestAuthentication(AsyncAuthType method, const char *realm = nullptr, const char *_authFailMsg = nullptr);
void setHandler(AsyncWebHandler* handler) { _handler = handler; }
// IMPORTANT: this method is for internal use ONLY
// Please do not use it!
// It can be removed or modified at any time without notice
void setHandler(AsyncWebHandler *handler) {
_handler = handler;
}
#ifndef ESP8266
[[deprecated("All headers are now collected. Use removeHeader(name) or AsyncHeaderFreeMiddleware if you really need to free some headers.")]]
@ -296,48 +337,80 @@ class AsyncWebServerRequest {
}
/**
* @brief issue HTTP redirect responce with Location header
* @brief issue HTTP redirect response with Location header
*
* @param url - url to redirect to
* @param code - responce code, default is 302 : temporary redirect
* @param code - response code, default is 302 : temporary redirect
*/
void redirect(const char *url, int code = 302);
void redirect(const String& url, int code = 302) { return redirect(url.c_str(), code); };
void redirect(const String &url, int code = 302) {
return redirect(url.c_str(), code);
};
void send(AsyncWebServerResponse *response);
AsyncWebServerResponse* getResponse() const { return _response; }
AsyncWebServerResponse *getResponse() const {
return _response;
}
void send(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, callback)); }
void send(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType.c_str(), content, callback)); }
void send(int code, const String& contentType, const String& content, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType.c_str(), content.c_str(), callback)); }
void send(int code, const char *contentType = asyncsrv::empty, const char *content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(code, contentType, content, callback));
}
void send(int code, const String &contentType, const char *content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(code, contentType.c_str(), content, callback));
}
void send(int code, const String &contentType, const String &content, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(code, contentType.c_str(), content.c_str(), callback));
}
void send(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, len, callback)); }
void send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, len, callback)); }
void send(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(code, contentType, content, len, callback));
}
void send(int code, const String &contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(code, contentType, content, len, callback));
}
void send(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) {
if (fs.exists(path) || (!download && fs.exists(path + asyncsrv::T__gz))) {
send(beginResponse(fs, path, contentType, download, callback));
} else
} else {
send(404);
}
void send(FS& fs, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callback = nullptr) { send(fs, path, contentType.c_str(), download, callback); }
}
void send(FS &fs, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr) {
send(fs, path, contentType.c_str(), download, callback);
}
void send(File content, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) {
if (content) {
send(beginResponse(content, path, contentType, download, callback));
} else
} else {
send(404);
}
void send(File content, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callback = nullptr) { send(content, path, contentType.c_str(), download, callback); }
}
void send(File content, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr) {
send(content, path, contentType.c_str(), download, callback);
}
void send(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(stream, contentType, len, callback)); }
void send(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(stream, contentType, len, callback)); }
void send(Stream &stream, const char *contentType, size_t len, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(stream, contentType, len, callback));
}
void send(Stream &stream, const String &contentType, size_t len, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(stream, contentType, len, callback));
}
void send(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginResponse(contentType, len, callback, templateCallback)); }
void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginResponse(contentType, len, callback, templateCallback)); }
void send(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
send(beginResponse(contentType, len, callback, templateCallback));
}
void send(const String &contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
send(beginResponse(contentType, len, callback, templateCallback));
}
void sendChunked(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginChunkedResponse(contentType, callback, templateCallback)); }
void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginChunkedResponse(contentType, callback, templateCallback)); }
void sendChunked(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
send(beginChunkedResponse(contentType, callback, templateCallback));
}
void sendChunked(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
send(beginChunkedResponse(contentType, callback, templateCallback));
}
#ifndef ESP8266
[[deprecated("Replaced by send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr)")]]
@ -356,30 +429,51 @@ class AsyncWebServerRequest {
}
#endif
AsyncWebServerResponse* beginResponse(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse* beginResponse(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) { return beginResponse(code, contentType.c_str(), content, callback); }
AsyncWebServerResponse* beginResponse(int code, const String& contentType, const String& content, AwsTemplateProcessor callback = nullptr) { return beginResponse(code, contentType.c_str(), content.c_str(), callback); }
AsyncWebServerResponse *
beginResponse(int code, const char *contentType = asyncsrv::empty, const char *content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse *beginResponse(int code, const String &contentType, const char *content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) {
return beginResponse(code, contentType.c_str(), content, callback);
}
AsyncWebServerResponse *beginResponse(int code, const String &contentType, const String &content, AwsTemplateProcessor callback = nullptr) {
return beginResponse(code, contentType.c_str(), content.c_str(), callback);
}
AsyncWebServerResponse *beginResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse* beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { return beginResponse(code, contentType.c_str(), content, len, callback); }
AsyncWebServerResponse *beginResponse(int code, const String &contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr) {
return beginResponse(code, contentType.c_str(), content, len, callback);
}
AsyncWebServerResponse* beginResponse(FS& fs, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse* beginResponse(FS& fs, const String& path, const String& contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { return beginResponse(fs, path, contentType.c_str(), download, callback); }
AsyncWebServerResponse *
beginResponse(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse *
beginResponse(FS &fs, const String &path, const String &contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) {
return beginResponse(fs, path, contentType.c_str(), download, callback);
}
AsyncWebServerResponse* beginResponse(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse* beginResponse(File content, const String& path, const String& contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { return beginResponse(content, path, contentType.c_str(), download, callback); }
AsyncWebServerResponse *
beginResponse(File content, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse *
beginResponse(File content, const String &path, const String &contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) {
return beginResponse(content, path, contentType.c_str(), download, callback);
}
AsyncWebServerResponse *beginResponse(Stream &stream, const char *contentType, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse* beginResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) { return beginResponse(stream, contentType.c_str(), len, callback); }
AsyncWebServerResponse *beginResponse(Stream &stream, const String &contentType, size_t len, AwsTemplateProcessor callback = nullptr) {
return beginResponse(stream, contentType.c_str(), len, callback);
}
AsyncWebServerResponse *beginResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncWebServerResponse* beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { return beginResponse(contentType.c_str(), len, callback, templateCallback); }
AsyncWebServerResponse *beginResponse(const String &contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
return beginResponse(contentType.c_str(), len, callback, templateCallback);
}
AsyncWebServerResponse *beginChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncWebServerResponse *beginChunkedResponse(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncResponseStream *beginResponseStream(const char *contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE);
AsyncResponseStream* beginResponseStream(const String& contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE) { return beginResponseStream(contentType.c_str(), bufferSize); }
AsyncResponseStream *beginResponseStream(const String &contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE) {
return beginResponseStream(contentType.c_str(), bufferSize);
}
#ifndef ESP8266
[[deprecated("Replaced by beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr)")]]
@ -388,10 +482,37 @@ class AsyncWebServerRequest {
return beginResponse(code, contentType.c_str(), content, len, callback);
}
#ifndef ESP8266
[[deprecated("Replaced by beginResponse(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr)")]]
[[deprecated("Replaced by beginResponse(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr)"
)]]
#endif
AsyncWebServerResponse *beginResponse_P(int code, const String &contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);
/**
* @brief Request Continuation: this function pauses the current request and returns a weak pointer (AsyncWebServerRequestPtr is a std::weak_ptr) to the request in order to reuse it later on.
* The middelware chain will continue to be processed until the end, but no response will be sent.
* To resume operations (send the request), the request must be retrieved from the weak pointer and a send() function must be called.
* AsyncWebServerRequestPtr is the only object allowed to exist the scope of the request handler.
* @warning This function should be called from within the context of a request (in a handler or middleware for example).
* @warning While the request is paused, if the client aborts the request, the latter will be disconnected and deleted.
* So it is the responsibility of the user to check the validity of the request pointer (AsyncWebServerRequestPtr) before using it by calling lock() and/or expired().
*/
AsyncWebServerRequestPtr pause();
bool isPaused() const {
return _paused;
}
/**
* @brief Aborts the request and close the client (RST).
* Mark the request as sent.
* If it was paused, it will be unpaused and it won't be possible to resume it.
*/
void abort();
bool isSent() const {
return _sent;
}
/**
* @brief Get the Request parameter by name
*
@ -402,7 +523,9 @@ class AsyncWebServerRequest {
*/
const AsyncWebParameter *getParam(const char *name, bool post = false, bool file = false) const;
const AsyncWebParameter* getParam(const String& name, bool post = false, bool file = false) const { return getParam(name.c_str(), post, file); };
const AsyncWebParameter *getParam(const String &name, bool post = false, bool file = false) const {
return getParam(name.c_str(), post, file);
};
#ifdef ESP8266
const AsyncWebParameter *getParam(const __FlashStringHelper *data, bool post, bool file) const;
#endif
@ -415,19 +538,25 @@ class AsyncWebServerRequest {
*/
const AsyncWebParameter *getParam(size_t num) const;
size_t args() const { return params(); } // get arguments count
size_t args() const {
return params();
} // get arguments count
// get request argument value by name
const String &arg(const char *name) const;
// get request argument value by name
const String& arg(const String& name) const { return arg(name.c_str()); };
const String &arg(const String &name) const {
return arg(name.c_str());
};
#ifdef ESP8266
const String &arg(const __FlashStringHelper *data) const; // get request argument value by F(name)
#endif
const String &arg(size_t i) const; // get request argument value by number
const String &argName(size_t i) const; // get request argument name by number
bool hasArg(const char *name) const; // check if argument exists
bool hasArg(const String& name) const { return hasArg(name.c_str()); };
bool hasArg(const String &name) const {
return hasArg(name.c_str());
};
#ifdef ESP8266
bool hasArg(const __FlashStringHelper *data) const; // check if F(argument) exists
#endif
@ -436,7 +565,9 @@ class AsyncWebServerRequest {
// get request header value by name
const String &header(const char *name) const;
const String& header(const String& name) const { return header(name.c_str()); };
const String &header(const String &name) const {
return header(name.c_str());
};
#ifdef ESP8266
const String &header(const __FlashStringHelper *data) const; // get request header value by F(name)
@ -449,20 +580,26 @@ class AsyncWebServerRequest {
// check if header exists
bool hasHeader(const char *name) const;
bool hasHeader(const String& name) const { return hasHeader(name.c_str()); };
bool hasHeader(const String &name) const {
return hasHeader(name.c_str());
};
#ifdef ESP8266
bool hasHeader(const __FlashStringHelper *data) const; // check if header exists
#endif
const AsyncWebHeader *getHeader(const char *name) const;
const AsyncWebHeader* getHeader(const String& name) const { return getHeader(name.c_str()); };
const AsyncWebHeader *getHeader(const String &name) const {
return getHeader(name.c_str());
};
#ifdef ESP8266
const AsyncWebHeader *getHeader(const __FlashStringHelper *data) const;
#endif
const AsyncWebHeader *getHeader(size_t num) const;
const std::list<AsyncWebHeader>& getHeaders() const { return _headers; }
const std::list<AsyncWebHeader> &getHeaders() const {
return _headers;
}
size_t getHeaderNames(std::vector<const char *> &names) const;
@ -470,24 +607,42 @@ class AsyncWebServerRequest {
// It will free the memory and prevent the header to be seen during request processing.
bool removeHeader(const char *name);
// Remove all request headers.
void removeHeaders() { _headers.clear(); }
void removeHeaders() {
_headers.clear();
}
size_t params() const; // get arguments count
bool hasParam(const char *name, bool post = false, bool file = false) const;
bool hasParam(const String& name, bool post = false, bool file = false) const { return hasParam(name.c_str(), post, file); };
bool hasParam(const String &name, bool post = false, bool file = false) const {
return hasParam(name.c_str(), post, file);
};
#ifdef ESP8266
bool hasParam(const __FlashStringHelper* data, bool post = false, bool file = false) const { return hasParam(String(data).c_str(), post, file); };
bool hasParam(const __FlashStringHelper *data, bool post = false, bool file = false) const {
return hasParam(String(data).c_str(), post, file);
};
#endif
// REQUEST ATTRIBUTES
void setAttribute(const char* name, const char* value) { _attributes[name] = value; }
void setAttribute(const char* name, bool value) { _attributes[name] = value ? "1" : emptyString; }
void setAttribute(const char* name, long value) { _attributes[name] = String(value); }
void setAttribute(const char* name, float value, unsigned int decimalPlaces = 2) { _attributes[name] = String(value, decimalPlaces); }
void setAttribute(const char* name, double value, unsigned int decimalPlaces = 2) { _attributes[name] = String(value, decimalPlaces); }
void setAttribute(const char *name, const char *value) {
_attributes[name] = value;
}
void setAttribute(const char *name, bool value) {
_attributes[name] = value ? "1" : emptyString;
}
void setAttribute(const char *name, long value) {
_attributes[name] = String(value);
}
void setAttribute(const char *name, float value, unsigned int decimalPlaces = 2) {
_attributes[name] = String(value, decimalPlaces);
}
void setAttribute(const char *name, double value, unsigned int decimalPlaces = 2) {
_attributes[name] = String(value, decimalPlaces);
}
bool hasAttribute(const char* name) const { return _attributes.find(name) != _attributes.end(); }
bool hasAttribute(const char *name) const {
return _attributes.find(name) != _attributes.end();
}
const String &getAttribute(const char *name, const String &defaultValue = emptyString) const;
bool getAttribute(const char *name, bool defaultValue) const;
@ -521,7 +676,9 @@ using ArMiddlewareCallback = std::function<void(AsyncWebServerRequest* request,
class AsyncMiddleware {
public:
virtual ~AsyncMiddleware() {}
virtual void run(__unused AsyncWebServerRequest* request, __unused ArMiddlewareNext next) { return next(); };
virtual void run(__unused AsyncWebServerRequest *request, __unused ArMiddlewareNext next) {
return next();
};
private:
friend class AsyncWebHandler;
@ -534,7 +691,9 @@ class AsyncMiddleware {
class AsyncMiddlewareFunction : public AsyncMiddleware {
public:
AsyncMiddlewareFunction(ArMiddlewareCallback fn) : _fn(fn) {}
void run(AsyncWebServerRequest* request, ArMiddlewareNext next) override { return _fn(request, next); };
void run(AsyncWebServerRequest *request, ArMiddlewareNext next) override {
return _fn(request, next);
};
private:
ArMiddlewareCallback _fn;
@ -564,8 +723,12 @@ class AsyncAuthenticationMiddleware : public AsyncMiddleware {
void setPassword(const char *password);
void setPasswordHash(const char *hash);
void setRealm(const char* realm) { _realm = realm; }
void setAuthFailureMessage(const char* message) { _authFailMsg = message; }
void setRealm(const char *realm) {
_realm = realm;
}
void setAuthFailureMessage(const char *message) {
_authFailMsg = message;
}
// set the authentication method to use
// default is AUTH_NONE: no authentication required
@ -575,7 +738,9 @@ class AsyncAuthenticationMiddleware : public AsyncMiddleware {
// AUTH_OTHER: other authentication method
// AUTH_DENIED: always return 401 Unauthorized
// if a method is set but no username or password is set, authentication will be ignored
void setAuthType(AsyncAuthType authMethod) { _authMethod = authMethod; }
void setAuthType(AsyncAuthType authMethod) {
_authMethod = authMethod;
}
// precompute and store the hash value based on the username, password, realm.
// can be used for DIGEST and BASIC to avoid recomputing the hash for each request.
@ -583,7 +748,9 @@ class AsyncAuthenticationMiddleware : public AsyncMiddleware {
bool generateHash();
// returns true if the username and password (or hash) are set
bool hasCredentials() const { return _hasCreds; }
bool hasCredentials() const {
return _hasCreds;
}
bool allowed(AsyncWebServerRequest *request) const;
@ -607,7 +774,9 @@ class AsyncAuthorizationMiddleware : public AsyncMiddleware {
AsyncAuthorizationMiddleware(ArAuthorizeFunction authorizeConnectHandler) : _code(403), _authz(authorizeConnectHandler) {}
AsyncAuthorizationMiddleware(int code, ArAuthorizeFunction authorizeConnectHandler) : _code(code), _authz(authorizeConnectHandler) {}
void run(AsyncWebServerRequest* request, ArMiddlewareNext next) { return _authz && !_authz(request) ? request->send(_code) : next(); }
void run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
return _authz && !_authz(request) ? request->send(_code) : next();
}
private:
int _code;
@ -617,33 +786,47 @@ class AsyncAuthorizationMiddleware : public AsyncMiddleware {
// remove all headers from the incoming request except the ones provided in the constructor
class AsyncHeaderFreeMiddleware : public AsyncMiddleware {
public:
void keep(const char* name) { _toKeep.push_back(name); }
void unKeep(const char* name) { _toKeep.erase(std::remove(_toKeep.begin(), _toKeep.end(), name), _toKeep.end()); }
void keep(const char *name) {
_toKeep.push_back(name);
}
void unKeep(const char *name) {
_toKeep.remove(name);
}
void run(AsyncWebServerRequest *request, ArMiddlewareNext next);
private:
std::vector<const char*> _toKeep;
std::list<const char *> _toKeep;
};
// filter out specific headers from the incoming request
class AsyncHeaderFilterMiddleware : public AsyncMiddleware {
public:
void filter(const char* name) { _toRemove.push_back(name); }
void unFilter(const char* name) { _toRemove.erase(std::remove(_toRemove.begin(), _toRemove.end(), name), _toRemove.end()); }
void filter(const char *name) {
_toRemove.push_back(name);
}
void unFilter(const char *name) {
_toRemove.remove(name);
}
void run(AsyncWebServerRequest *request, ArMiddlewareNext next);
private:
std::vector<const char*> _toRemove;
std::list<const char *> _toRemove;
};
// curl-like logging of incoming requests
class AsyncLoggingMiddleware : public AsyncMiddleware {
public:
void setOutput(Print& output) { _out = &output; }
void setEnabled(bool enabled) { _enabled = enabled; }
bool isEnabled() const { return _enabled && _out; }
void setOutput(Print &output) {
_out = &output;
}
void setEnabled(bool enabled) {
_enabled = enabled;
}
bool isEnabled() const {
return _enabled && _out;
}
void run(AsyncWebServerRequest *request, ArMiddlewareNext next);
@ -655,11 +838,21 @@ class AsyncLoggingMiddleware : public AsyncMiddleware {
// CORS Middleware
class AsyncCorsMiddleware : public AsyncMiddleware {
public:
void setOrigin(const char* origin) { _origin = origin; }
void setMethods(const char* methods) { _methods = methods; }
void setHeaders(const char* headers) { _headers = headers; }
void setAllowCredentials(bool credentials) { _credentials = credentials; }
void setMaxAge(uint32_t seconds) { _maxAge = seconds; }
void setOrigin(const char *origin) {
_origin = origin;
}
void setMethods(const char *methods) {
_methods = methods;
}
void setHeaders(const char *headers) {
_headers = headers;
}
void setAllowCredentials(bool credentials) {
_credentials = credentials;
}
void setMaxAge(uint32_t seconds) {
_maxAge = seconds;
}
void addCORSHeaders(AsyncWebServerResponse *response);
@ -676,8 +869,12 @@ class AsyncCorsMiddleware : public AsyncMiddleware {
// Rate limit Middleware
class AsyncRateLimitMiddleware : public AsyncMiddleware {
public:
void setMaxRequests(size_t maxRequests) { _maxRequests = maxRequests; }
void setWindowSize(uint32_t seconds) { _windowSizeMillis = seconds * 1000; }
void setMaxRequests(size_t maxRequests) {
_maxRequests = maxRequests;
}
void setWindowSize(uint32_t seconds) {
_windowSizeMillis = seconds * 1000;
}
bool isRequestAllowed(uint32_t &retryAfterSeconds);
@ -713,11 +910,21 @@ class AsyncWebRewrite {
_filter = fn;
return *this;
}
bool filter(AsyncWebServerRequest* request) const { return _filter == NULL || _filter(request); }
const String& from(void) const { return _from; }
const String& toUrl(void) const { return _toUrl; }
const String& params(void) const { return _params; }
virtual bool match(AsyncWebServerRequest* request) { return from() == request->url() && filter(request); }
bool filter(AsyncWebServerRequest *request) const {
return _filter == NULL || _filter(request);
}
const String &from(void) const {
return _from;
}
const String &toUrl(void) const {
return _toUrl;
}
const String &params(void) const {
return _params;
}
virtual bool match(AsyncWebServerRequest *request) {
return from() == request->url() && filter(request);
}
};
/*
@ -728,19 +935,42 @@ class AsyncWebHandler : public AsyncMiddlewareChain {
protected:
ArRequestFilterFunction _filter = nullptr;
AsyncAuthenticationMiddleware *_authMiddleware = nullptr;
bool _skipServerMiddlewares = false;
public:
AsyncWebHandler() {}
virtual ~AsyncWebHandler() {}
AsyncWebHandler &setFilter(ArRequestFilterFunction fn);
AsyncWebHandler &setAuthentication(const char *username, const char *password, AsyncAuthType authMethod = AsyncAuthType::AUTH_DIGEST);
AsyncWebHandler& setAuthentication(const String& username, const String& password, AsyncAuthType authMethod = AsyncAuthType::AUTH_DIGEST) { return setAuthentication(username.c_str(), password.c_str(), authMethod); };
bool filter(AsyncWebServerRequest* request) { return _filter == NULL || _filter(request); }
virtual bool canHandle(AsyncWebServerRequest* request __attribute__((unused))) const { return false; }
AsyncWebHandler &setAuthentication(const String &username, const String &password, AsyncAuthType authMethod = AsyncAuthType::AUTH_DIGEST) {
return setAuthentication(username.c_str(), password.c_str(), authMethod);
};
AsyncWebHandler &setSkipServerMiddlewares(bool state) {
_skipServerMiddlewares = state;
return *this;
}
// skip all globally defined server middlewares for this handler and only execute those defined for this handler specifically
AsyncWebHandler &skipServerMiddlewares() {
return setSkipServerMiddlewares(true);
}
bool mustSkipServerMiddlewares() const {
return _skipServerMiddlewares;
}
bool filter(AsyncWebServerRequest *request) {
return _filter == NULL || _filter(request);
}
virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))) const {
return false;
}
virtual void handleRequest(__unused AsyncWebServerRequest *request) {}
virtual void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) {}
virtual void handleUpload(
__unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
__unused bool final
) {}
virtual void handleBody(__unused AsyncWebServerRequest *request, __unused uint8_t *data, __unused size_t len, __unused size_t index, __unused size_t total) {}
virtual bool isRequestHandlerTrivial() const { return true; }
virtual bool isRequestHandlerTrivial() const {
return true;
}
};
/*
@ -770,6 +1000,8 @@ class AsyncWebServerResponse {
size_t _writtenLength;
WebResponseState _state;
static bool headerMustBePresentOnce(const String &name);
public:
static const char *responseCodeToString(int code);
@ -777,17 +1009,30 @@ class AsyncWebServerResponse {
AsyncWebServerResponse();
virtual ~AsyncWebServerResponse() {}
void setCode(int code);
int code() const { return _code; }
int code() const {
return _code;
}
void setContentLength(size_t len);
void setContentType(const String& type) { setContentType(type.c_str()); }
void setContentType(const String &type) {
setContentType(type.c_str());
}
void setContentType(const char *type);
bool addHeader(const char *name, const char *value, bool replaceExisting = true);
bool addHeader(const String& name, const String& value, bool replaceExisting = true) { return addHeader(name.c_str(), value.c_str(), replaceExisting); }
bool addHeader(const char* name, long value, bool replaceExisting = true) { return addHeader(name, String(value), replaceExisting); }
bool addHeader(const String& name, long value, bool replaceExisting = true) { return addHeader(name.c_str(), value, replaceExisting); }
bool addHeader(const String &name, const String &value, bool replaceExisting = true) {
return addHeader(name.c_str(), value.c_str(), replaceExisting);
}
bool addHeader(const char *name, long value, bool replaceExisting = true) {
return addHeader(name, String(value), replaceExisting);
}
bool addHeader(const String &name, long value, bool replaceExisting = true) {
return addHeader(name.c_str(), value, replaceExisting);
}
bool removeHeader(const char *name);
bool removeHeader(const char *name, const char *value);
const AsyncWebHeader *getHeader(const char *name) const;
const std::list<AsyncWebHeader>& getHeaders() const { return _headers; }
const std::list<AsyncWebHeader> &getHeaders() const {
return _headers;
}
#ifndef ESP8266
[[deprecated("Use instead: _assembleHead(String& buffer, uint8_t version)")]]
@ -812,7 +1057,8 @@ class AsyncWebServerResponse {
* */
typedef std::function<void(AsyncWebServerRequest *request)> ArRequestHandlerFunction;
typedef std::function<void(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final)> ArUploadHandlerFunction;
typedef std::function<void(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final)>
ArUploadHandlerFunction;
typedef std::function<void(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction;
class AsyncWebServer : public AsyncMiddlewareChain {
@ -878,14 +1124,21 @@ class AsyncWebServer : public AsyncMiddlewareChain {
AsyncWebHandler &addHandler(AsyncWebHandler *handler);
bool removeHandler(AsyncWebHandler *handler);
AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest) { return on(uri, HTTP_ANY, onRequest); }
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload = nullptr, ArBodyHandlerFunction onBody = nullptr);
AsyncCallbackWebHandler &on(const char *uri, ArRequestHandlerFunction onRequest) {
return on(uri, HTTP_ANY, onRequest);
}
AsyncCallbackWebHandler &on(
const char *uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload = nullptr,
ArBodyHandlerFunction onBody = nullptr
);
AsyncStaticWebHandler &serveStatic(const char *uri, fs::FS &fs, const char *path, const char *cache_control = NULL);
void onNotFound(ArRequestHandlerFunction fn); // called when handler is not assigned
void onFileUpload(ArUploadHandlerFunction fn); // handle file uploads
void onRequestBody(ArBodyHandlerFunction fn); // handle posts with plain body content (JSON often transmitted this way as a request)
// give access to the handler used to catch all requests, so that middleware can be added to it
AsyncWebHandler &catchAllHandler() const;
void reset(); // remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody
@ -907,8 +1160,12 @@ class DefaultHeaders {
_headers.emplace_back(name, value);
}
ConstIterator begin() const { return _headers.begin(); }
ConstIterator end() const { return _headers.end(); }
ConstIterator begin() const {
return _headers.begin();
}
ConstIterator end() const {
return _headers.end();
}
DefaultHeaders(DefaultHeaders const &) = delete;
DefaultHeaders &operator=(DefaultHeaders const &) = delete;

View File

@ -1,11 +1,16 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include "WebAuthentication.h"
#include <ESPAsyncWebServer.h>
AsyncMiddlewareChain::~AsyncMiddlewareChain() {
for (AsyncMiddleware* m : _middlewares)
if (m->_freeOnRemoval)
for (AsyncMiddleware *m : _middlewares) {
if (m->_freeOnRemoval) {
delete m;
}
}
}
void AsyncMiddlewareChain::addMiddleware(ArMiddlewareCallback fn) {
AsyncMiddlewareFunction *m = new AsyncMiddlewareFunction(fn);
@ -14,38 +19,48 @@ void AsyncMiddlewareChain::addMiddleware(ArMiddlewareCallback fn) {
}
void AsyncMiddlewareChain::addMiddleware(AsyncMiddleware *middleware) {
if (middleware)
if (middleware) {
_middlewares.emplace_back(middleware);
}
}
void AsyncMiddlewareChain::addMiddlewares(std::vector<AsyncMiddleware *> middlewares) {
for (AsyncMiddleware* m : middlewares)
for (AsyncMiddleware *m : middlewares) {
addMiddleware(m);
}
}
bool AsyncMiddlewareChain::removeMiddleware(AsyncMiddleware *middleware) {
// remove all middlewares from _middlewares vector being equal to middleware, delete them having _freeOnRemoval flag to true and resize the vector.
const size_t size = _middlewares.size();
_middlewares.erase(std::remove_if(_middlewares.begin(), _middlewares.end(), [middleware](AsyncMiddleware* m) {
_middlewares.erase(
std::remove_if(
_middlewares.begin(), _middlewares.end(),
[middleware](AsyncMiddleware *m) {
if (m == middleware) {
if (m->_freeOnRemoval)
if (m->_freeOnRemoval) {
delete m;
}
return true;
}
return false;
}),
_middlewares.end());
}
),
_middlewares.end()
);
return size != _middlewares.size();
}
void AsyncMiddlewareChain::_runChain(AsyncWebServerRequest *request, ArMiddlewareNext finalizer) {
if (!_middlewares.size())
if (!_middlewares.size()) {
return finalizer();
}
ArMiddlewareNext next;
std::list<AsyncMiddleware *>::iterator it = _middlewares.begin();
next = [this, &next, &it, request, finalizer]() {
if (it == _middlewares.end())
if (it == _middlewares.end()) {
return finalizer();
}
AsyncMiddleware *m = *it;
it++;
return m->run(request, next);
@ -72,38 +87,50 @@ void AsyncAuthenticationMiddleware::setPasswordHash(const char* hash) {
bool AsyncAuthenticationMiddleware::generateHash() {
// ensure we have all the necessary data
if (!_hasCreds)
if (!_hasCreds) {
return false;
}
// if we already have a hash, do nothing
if (_hash)
if (_hash) {
return false;
}
switch (_authMethod) {
case AsyncAuthType::AUTH_DIGEST:
_credentials = generateDigestHash(_username.c_str(), _credentials.c_str(), _realm.c_str());
if (_credentials.length()) {
_hash = true;
return true;
} else {
return false;
}
case AsyncAuthType::AUTH_BASIC:
_credentials = generateBasicHash(_username.c_str(), _credentials.c_str());
if (_credentials.length()) {
_hash = true;
return true;
default:
} else {
return false;
}
default: return false;
}
}
bool AsyncAuthenticationMiddleware::allowed(AsyncWebServerRequest *request) const {
if (_authMethod == AsyncAuthType::AUTH_NONE)
if (_authMethod == AsyncAuthType::AUTH_NONE) {
return true;
}
if (_authMethod == AsyncAuthType::AUTH_DENIED)
if (_authMethod == AsyncAuthType::AUTH_DENIED) {
return false;
}
if (!_hasCreds)
if (!_hasCreds) {
return true;
}
return request->authenticate(_username.c_str(), _credentials.c_str(), _realm.c_str(), _hash);
}
@ -113,26 +140,29 @@ void AsyncAuthenticationMiddleware::run(AsyncWebServerRequest* request, ArMiddle
}
void AsyncHeaderFreeMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
std::vector<const char*> reqHeaders;
request->getHeaderNames(reqHeaders);
for (const char* h : reqHeaders) {
std::list<const char *> toRemove;
for (auto &h : request->getHeaders()) {
bool keep = false;
for (const char *k : _toKeep) {
if (strcasecmp(h, k) == 0) {
if (strcasecmp(h.name().c_str(), k) == 0) {
keep = true;
break;
}
}
if (!keep) {
request->removeHeader(h);
toRemove.push_back(h.name().c_str());
}
}
for (const char *h : toRemove) {
request->removeHeader(h);
}
next();
}
void AsyncHeaderFilterMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
for (auto it = _toRemove.begin(); it != _toRemove.end(); ++it)
for (auto it = _toRemove.begin(); it != _toRemove.end(); ++it) {
request->removeHeader(*it);
}
next();
}
@ -229,8 +259,9 @@ void AsyncCorsMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext n
bool AsyncRateLimitMiddleware::isRequestAllowed(uint32_t &retryAfterSeconds) {
uint32_t now = millis();
while (!_requestTimes.empty() && _requestTimes.front() <= now - _windowSizeMillis)
while (!_requestTimes.empty() && _requestTimes.front() <= now - _windowSizeMillis) {
_requestTimes.pop_front();
}
_requestTimes.push_back(now);

View File

@ -1,26 +1,9 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "WebAuthentication.h"
#include <libb64/cencode.h>
#if defined(ESP32) || defined(TARGET_RP2040)
#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <MD5Builder.h>
#else
#include "md5.h"
@ -32,14 +15,16 @@ using namespace asyncsrv;
// Basic Auth hash = base64("username:password")
bool checkBasicAuthentication(const char *hash, const char *username, const char *password) {
if (username == NULL || password == NULL || hash == NULL)
if (username == NULL || password == NULL || hash == NULL) {
return false;
}
return generateBasicHash(username, password).equalsIgnoreCase(hash);
}
String generateBasicHash(const char *username, const char *password) {
if (username == NULL || password == NULL)
if (username == NULL || password == NULL) {
return emptyString;
}
size_t toencodeLen = strlen(username) + strlen(password) + 1;
@ -65,7 +50,7 @@ String generateBasicHash(const char* username, const char* password) {
}
static bool getMD5(uint8_t *data, uint16_t len, char *output) { // 33 bytes or more
#if defined(ESP32) || defined(TARGET_RP2040)
#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
MD5Builder md5;
md5.begin();
md5.add(data, len);
@ -75,8 +60,9 @@ static bool getMD5(uint8_t* data, uint16_t len, char* output) { // 33 bytes or m
md5_context_t _ctx;
uint8_t *_buf = (uint8_t *)malloc(16);
if (_buf == NULL)
if (_buf == NULL) {
return false;
}
memset(_buf, 0x00, 16);
MD5Init(&_ctx);
@ -99,8 +85,12 @@ String genRandomMD5() {
uint32_t r = rand();
#endif
char *out = (char *)malloc(33);
if (out == NULL || !getMD5((uint8_t*)(&r), 4, out))
if (out == NULL || !getMD5((uint8_t *)(&r), 4, out)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
return emptyString;
}
String res = String(out);
free(out);
return res;
@ -108,8 +98,12 @@ String genRandomMD5() {
static String stringMD5(const String &in) {
char *out = (char *)malloc(33);
if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
if (out == NULL || !getMD5((uint8_t *)(in.c_str()), in.length(), out)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
return emptyString;
}
String res = String(out);
free(out);
return res;
@ -120,27 +114,47 @@ String generateDigestHash(const char* username, const char* password, const char
return emptyString;
}
char *out = (char *)malloc(33);
if (out == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
return emptyString;
}
String in;
in.reserve(strlen(username) + strlen(realm) + strlen(password) + 2);
if (!in.reserve(strlen(username) + strlen(realm) + strlen(password) + 2)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
free(out);
return emptyString;
}
in.concat(username);
in.concat(':');
in.concat(realm);
in.concat(':');
in.concat(password);
if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
if (!getMD5((uint8_t *)(in.c_str()), in.length(), out)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
free(out);
return emptyString;
}
in = String(out);
free(out);
return in;
}
bool checkDigestAuthentication(const char* header, const char* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri)
{
bool checkDigestAuthentication(
const char *header, const char *method, const char *username, const char *password, const char *realm, bool passwordIsHash, const char *nonce,
const char *opaque, const char *uri
) {
if (username == NULL || password == NULL || header == NULL || method == NULL) {
// os_printf("AUTH FAIL: missing requred fields\n");
// os_printf("AUTH FAIL: missing required fields\n");
return false;
}

View File

@ -1,23 +1,5 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#ifndef WEB_AUTHENTICATION_H_
#define WEB_AUTHENTICATION_H_
@ -26,7 +8,10 @@
bool checkBasicAuthentication(const char *header, const char *username, const char *password);
bool checkDigestAuthentication(const char* header, const char* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri);
bool checkDigestAuthentication(
const char *header, const char *method, const char *username, const char *password, const char *realm, bool passwordIsHash, const char *nonce,
const char *opaque, const char *uri
);
// for storing hashed versions on the device that can be authenticated against
String generateDigestHash(const char *username, const char *password, const char *realm);

View File

@ -1,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCWEBSERVERHANDLERIMPL_H_
#define ASYNCWEBSERVERHANDLERIMPL_H_
@ -67,7 +50,7 @@ class AsyncStaticWebHandler : public AsyncWebHandler {
AsyncStaticWebHandler &setLastModified(const char *last_modified);
AsyncStaticWebHandler &setLastModified(struct tm *last_modified);
AsyncStaticWebHandler &setLastModified(time_t last_modified);
// sets to current time. Make sure sntp is runing and time is updated
// sets to current time. Make sure sntp is running and time is updated
AsyncStaticWebHandler &setLastModified();
AsyncStaticWebHandler &setTemplateProcessor(AwsTemplateProcessor newCallback);
@ -86,16 +69,26 @@ class AsyncCallbackWebHandler : public AsyncWebHandler {
public:
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {}
void setUri(const String &uri);
void setMethod(WebRequestMethodComposite method) { _method = method; }
void onRequest(ArRequestHandlerFunction fn) { _onRequest = fn; }
void onUpload(ArUploadHandlerFunction fn) { _onUpload = fn; }
void onBody(ArBodyHandlerFunction fn) { _onBody = fn; }
void setMethod(WebRequestMethodComposite method) {
_method = method;
}
void onRequest(ArRequestHandlerFunction fn) {
_onRequest = fn;
}
void onUpload(ArUploadHandlerFunction fn) {
_onUpload = fn;
}
void onBody(ArBodyHandlerFunction fn) {
_onBody = fn;
}
bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest *request) override final;
void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) override final;
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final;
bool isRequestHandlerTrivial() const override final { return !_onRequest; }
bool isRequestHandlerTrivial() const override final {
return !_onRequest;
}
};
#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */

View File

@ -1,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ESPAsyncWebServer.h"
#include "WebHandlerImpl.h"
@ -42,10 +25,12 @@ AsyncWebHandler& AsyncWebHandler::setAuthentication(const char* username, const
AsyncStaticWebHandler::AsyncStaticWebHandler(const char *uri, FS &fs, const char *path, const char *cache_control)
: _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr) {
// Ensure leading '/'
if (_uri.length() == 0 || _uri[0] != '/')
if (_uri.length() == 0 || _uri[0] != '/') {
_uri = String('/') + _uri;
if (_path.length() == 0 || _path[0] != '/')
}
if (_path.length() == 0 || _path[0] != '/') {
_path = String('/') + _path;
}
// If path ends with '/' we assume a hint that this is a directory to improve performance.
// However - if it does not end with '/' we, can't assume a file, path can still be a directory.
@ -53,11 +38,13 @@ AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char
// Remove the trailing '/' so we can handle default file
// Notice that root will be "" not "/"
if (_uri[_uri.length() - 1] == '/')
if (_uri[_uri.length() - 1] == '/') {
_uri = _uri.substring(0, _uri.length() - 1);
if (_path[_path.length() - 1] == '/')
}
if (_path[_path.length() - 1] == '/') {
_path = _path.substring(0, _path.length() - 1);
}
}
AsyncStaticWebHandler &AsyncStaticWebHandler::setTryGzipFirst(bool value) {
_tryGzipFirst = value;
@ -105,8 +92,9 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modifi
AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified() {
time_t last_modified;
if (time(&last_modified) == 0) // time is not yet set
if (time(&last_modified) == 0) { // time is not yet set
return *this;
}
return setLastModified(last_modified);
}
@ -124,16 +112,19 @@ bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest* request) const {
path = _path + path;
// Do we have a file or .gz file
if (!canSkipFileCheck && const_cast<AsyncStaticWebHandler*>(this)->_searchFile(request, path))
if (!canSkipFileCheck && const_cast<AsyncStaticWebHandler *>(this)->_searchFile(request, path)) {
return true;
}
// Can't handle if not default file
if (_default_file.length() == 0)
if (_default_file.length() == 0) {
return false;
}
// Try to add default file, ensure there is a trailing '/' ot the path.
if (path.length() == 0 || path[path.length() - 1] != '/')
// Try to add default file, ensure there is a trailing '/' to the path.
if (path.length() == 0 || path[path.length() - 1] != '/') {
path += String('/');
}
path += _default_file;
return const_cast<AsyncStaticWebHandler *>(this)->_searchFile(request, path);
@ -181,6 +172,14 @@ bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest* request, const St
// Extract the file name from the path and keep it in _tempObject
size_t pathLen = path.length();
char *_tempPath = (char *)malloc(pathLen + 1);
if (_tempPath == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
request->_tempFile.close();
return false;
}
snprintf_P(_tempPath, pathLen + 1, PSTR("%s"), path.c_str());
request->_tempObject = (void *)_tempPath;
}
@ -191,8 +190,9 @@ bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest* request, const St
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const {
uint8_t w = value;
uint8_t n;
for (n = 0; w != 0; n++)
for (n = 0; w != 0; n++) {
w &= w - 1;
}
return n;
}
@ -212,7 +212,7 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
String etag;
if (lw) {
setLastModified(lw);
#if defined(TARGET_RP2040)
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
// time_t == long long int
constexpr size_t len = 1 + 8 * sizeof(time_t);
char buf[len];
@ -222,16 +222,21 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
etag = lw ^ request->_tempFile.size(); // etag combines file size and lastmod timestamp
#endif
} else {
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
etag = String(request->_tempFile.size());
#else
etag = request->_tempFile.size();
#endif
}
bool not_modified = false;
// if-none-match has precedence over if-modified-since
if (request->hasHeader(T_INM))
if (request->hasHeader(T_INM)) {
not_modified = request->header(T_INM).equals(etag);
else if (_last_modified.length())
} else if (_last_modified.length()) {
not_modified = request->header(T_IMS).equals(_last_modified);
}
AsyncWebServerResponse *response;
@ -242,15 +247,24 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
response = new AsyncFileResponse(request->_tempFile, filename, emptyString, false, _callback);
}
if (!response) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
response->addHeader(T_ETag, etag.c_str());
if (_last_modified.length())
if (_last_modified.length()) {
response->addHeader(T_Last_Modified, _last_modified.c_str());
if (_cache_control.length())
}
if (_cache_control.length()) {
response->addHeader(T_Cache_Control, _cache_control.c_str());
}
request->send(response);
}
AsyncStaticWebHandler &AsyncStaticWebHandler::setTemplateProcessor(AwsTemplateProcessor newCallback) {
@ -264,8 +278,9 @@ void AsyncCallbackWebHandler::setUri(const String& uri) {
}
bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest *request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method()))
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
return false;
}
#ifdef ASYNCWEBSERVER_REGEX
if (_isRegex) {
@ -284,30 +299,37 @@ bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest* request) const {
if (_uri.length() && _uri.startsWith("/*.")) {
String uriTemplate = String(_uri);
uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf("."));
if (!request->url().endsWith(uriTemplate))
if (!request->url().endsWith(uriTemplate)) {
return false;
}
} else if (_uri.length() && _uri.endsWith("*")) {
String uriTemplate = String(_uri);
uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1);
if (!request->url().startsWith(uriTemplate))
if (!request->url().startsWith(uriTemplate)) {
return false;
} else if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
}
} else if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
return false;
}
return true;
}
void AsyncCallbackWebHandler::handleRequest(AsyncWebServerRequest *request) {
if (_onRequest)
if (_onRequest) {
_onRequest(request);
else
request->send(500);
} else {
request->send(404, T_text_plain, "Not found");
}
}
void AsyncCallbackWebHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) {
if (_onUpload)
if (_onUpload) {
_onUpload(request, filename, index, data, len, final);
}
}
void AsyncCallbackWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
if (_onBody)
// ESP_LOGD("AsyncWebServer", "AsyncCallbackWebHandler::handleBody");
if (_onBody) {
_onBody(request, data, len, index, total);
}
}

View File

@ -1,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ESPAsyncWebServer.h"
#include "WebAuthentication.h"
#include "WebResponseImpl.h"
@ -26,25 +9,84 @@
#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '='))
static void doNotDelete(AsyncWebServerRequest *) {}
using namespace asyncsrv;
enum { PARSE_REQ_START = 0,
enum {
PARSE_REQ_START = 0,
PARSE_REQ_HEADERS = 1,
PARSE_REQ_BODY = 2,
PARSE_REQ_END = 3,
PARSE_REQ_FAIL = 4 };
PARSE_REQ_FAIL = 4
};
AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
: _client(c), _server(s), _handler(NULL), _response(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY), _url(), _host(), _contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false), _isPlainPost(false), _expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), _itemSize(0), _itemName(), _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) {
c->onError([](void* r, AsyncClient* c, int8_t error) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this);
c->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this);
c->onDisconnect([](void* r, AsyncClient* c) { AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onDisconnect(); delete c; }, this);
c->onTimeout([](void* r, AsyncClient* c, uint32_t time) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this);
c->onData([](void* r, AsyncClient* c, void* buf, size_t len) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this);
c->onPoll([](void* r, AsyncClient* c) { (void)c; AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this);
: _client(c), _server(s), _handler(NULL), _response(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY), _url(), _host(),
_contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false), _isPlainPost(false),
_expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), _itemSize(0), _itemName(),
_itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) {
c->onError(
[](void *r, AsyncClient *c, int8_t error) {
(void)c;
// log_e("AsyncWebServerRequest::_onError");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onError(error);
},
this
);
c->onAck(
[](void *r, AsyncClient *c, size_t len, uint32_t time) {
(void)c;
// log_e("AsyncWebServerRequest::_onAck");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onAck(len, time);
},
this
);
c->onDisconnect(
[](void *r, AsyncClient *c) {
// log_e("AsyncWebServerRequest::_onDisconnect");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onDisconnect();
delete c;
},
this
);
c->onTimeout(
[](void *r, AsyncClient *c, uint32_t time) {
(void)c;
// log_e("AsyncWebServerRequest::_onTimeout");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onTimeout(time);
},
this
);
c->onData(
[](void *r, AsyncClient *c, void *buf, size_t len) {
(void)c;
// log_e("AsyncWebServerRequest::_onData");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onData(buf, len);
},
this
);
c->onPoll(
[](void *r, AsyncClient *c) {
(void)c;
// log_e("AsyncWebServerRequest::_onPoll");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onPoll();
},
this
);
}
AsyncWebServerRequest::~AsyncWebServerRequest() {
// log_e("AsyncWebServerRequest::~AsyncWebServerRequest");
_this.reset();
_headers.clear();
_pathParams.clear();
@ -74,7 +116,7 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
log_d("SSL/TLS handshake detected: resetting connection");
#endif
_parseState = PARSE_REQ_FAIL;
_client->abort();
abort();
return;
}
#endif
@ -89,7 +131,7 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
// Check for null characters in header
if (!str[i]) {
_parseState = PARSE_REQ_FAIL;
_client->abort();
abort();
return;
}
if (str[i] == '\n') {
@ -99,7 +141,14 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
if (i == len) { // No new line, just add the buffer in _temp
char ch = str[len - 1];
str[len - 1] = 0;
_temp.reserve(_temp.length() + len);
if (!_temp.reserve(_temp.length() + len)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
_parseState = PARSE_REQ_FAIL;
abort();
return;
}
_temp.concat(str);
_temp.concat(ch);
} else { // Found new line - extract it and parse
@ -118,6 +167,8 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
// A handler should be already attached at this point in _parseLine function.
// If handler does nothing (_onRequest is NULL), we don't need to really parse the body.
const bool needParse = _handler && !_handler->isRequestHandlerTrivial();
// Discard any bytes after content length; handlers may overrun their buffers
len = std::min(len, _contentLength - _parsedLength);
if (_isMultipart) {
if (needParse) {
size_t i;
@ -125,24 +176,26 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
_parseMultipartPostByte(((uint8_t *)buf)[i], i == len - 1);
_parsedLength++;
}
} else
} else {
_parsedLength += len;
}
} else {
if (_parsedLength == 0) {
if (_contentType.startsWith(T_app_xform_urlencoded)) {
_isPlainPost = true;
} else if (_contentType == T_text_plain && __is_param_char(((char *)buf)[0])) {
size_t i = 0;
while (i < len && __is_param_char(((char*)buf)[i++]))
;
while (i < len && __is_param_char(((char *)buf)[i++]));
if (i < len && ((char *)buf)[i - 1] == '=') {
_isPlainPost = true;
}
}
}
if (!_isPlainPost) {
if (_handler)
// ESP_LOGD("AsyncWebServer", "_isPlainPost: %d, _handler: %p", _isPlainPost, _handler);
if (_handler) {
_handler->handleBody(this, (uint8_t *)buf, len, _parsedLength, _contentLength);
}
_parsedLength += len;
} else if (needParse) {
size_t i;
@ -156,16 +209,8 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
}
if (_parsedLength == _contentLength) {
_parseState = PARSE_REQ_END;
_server->_runChain(this, [this]() { return _handler ? _handler->_runChain(this, [this]() { _handler->handleRequest(this); }) : send(501); });
if (!_sent) {
if (!_response)
send(501, T_text_plain, "Handler did not handle the request");
else if (!_response->_sourceValid())
send(500, T_text_plain, "Invalid data in handler");
_client->setRxTimeout(0);
_response->_respond(this);
_sent = true;
}
_runMiddlewareChain();
_send();
}
}
break;
@ -232,14 +277,18 @@ void AsyncWebServerRequest::_addGetParams(const String& params) {
size_t start = 0;
while (start < params.length()) {
int end = params.indexOf('&', start);
if (end < 0)
if (end < 0) {
end = params.length();
}
int equal = params.indexOf('=', start);
if (equal < 0 || equal > end)
if (equal < 0 || equal > end) {
equal = end;
String name(params.substring(start, equal));
String value(equal + 1 < end ? params.substring(equal + 1, end) : String());
_params.emplace_back(urlDecode(name), urlDecode(value));
}
String name = urlDecode(params.substring(start, equal));
String value = urlDecode(equal + 1 < end ? params.substring(equal + 1, end) : emptyString);
if (name.length()) {
_params.emplace_back(name, value);
}
start = end + 1;
}
}
@ -279,11 +328,13 @@ bool AsyncWebServerRequest::_parseReqHead() {
_url = urlDecode(u);
_addGetParams(g);
if (!_url.length())
if (!_url.length()) {
return false;
}
if (!_temp.startsWith(T_HTTP_1_0))
if (!_temp.startsWith(T_HTTP_1_0)) {
_version = 1;
}
_temp = emptyString;
return true;
@ -343,18 +394,19 @@ bool AsyncWebServerRequest::_parseReqHeader() {
}
_headers.emplace_back(name, value);
}
#ifndef TARGET_RP2040
_temp.clear();
#else
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
// Ancient PRI core does not have String::clear() method 8-()
_temp = emptyString;
#else
_temp.clear();
#endif
return true;
}
void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) {
if (data && (char)data != '&')
if (data && (char)data != '&') {
_temp += (char)data;
}
if (!data || (char)data == '&' || _parsedLength == _contentLength) {
String name(T_BODY);
String value(_temp);
@ -362,13 +414,16 @@ void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) {
name = _temp.substring(0, _temp.indexOf('='));
value = _temp.substring(_temp.indexOf('=') + 1);
}
_params.emplace_back(urlDecode(name), urlDecode(value), true);
name = urlDecode(name);
if (name.length()) {
_params.emplace_back(name, urlDecode(value), true);
}
#ifndef TARGET_RP2040
_temp.clear();
#else
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
// Ancient PRI core does not have String::clear() method 8-()
_temp = emptyString;
#else
_temp.clear();
#endif
}
}
@ -378,8 +433,9 @@ void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last) {
if (last || _itemBufferIndex == RESPONSE_STREAM_BUFFER_SIZE) {
// check if authenticated before calling the upload
if (_handler)
if (_handler) {
_handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false);
}
_itemBufferIndex = 0;
}
}
@ -441,8 +497,9 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
_itemIsFile = false;
}
} else if (_multiParseState == PARSE_HEADERS) {
if ((char)data != '\r' && (char)data != '\n')
if ((char)data != '\r' && (char)data != '\n') {
_temp += (char)data;
}
if ((char)data == '\n') {
if (_temp.length()) {
if (_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase(T_Content_Type)) {
@ -478,11 +535,16 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
_itemStartIndex = _parsedLength;
_itemValue = emptyString;
if (_itemIsFile) {
if (_itemBuffer)
if (_itemBuffer) {
free(_itemBuffer);
}
_itemBuffer = (uint8_t *)malloc(RESPONSE_STREAM_BUFFER_SIZE);
if (_itemBuffer == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
_multiParseState = PARSE_ERROR;
abort();
return;
}
_itemBufferIndex = 0;
@ -525,8 +587,9 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
itemWriteByte('-');
itemWriteByte('-');
uint8_t i;
for (i = 0; i < _boundaryPosition; i++)
for (i = 0; i < _boundaryPosition; i++) {
itemWriteByte(_boundary.c_str()[i]);
}
_parseMultipartPostByte(data, last);
} else if (_boundaryPosition == _boundary.length() - 1) {
_multiParseState = DASH3_OR_RETURN2;
@ -534,8 +597,9 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
_params.emplace_back(_itemName, _itemValue, true);
} else {
if (_itemSize) {
if (_handler)
if (_handler) {
_handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true);
}
_itemBufferIndex = 0;
_params.emplace_back(_itemName, _itemFilename, true, true, _itemSize);
}
@ -562,8 +626,9 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
itemWriteByte('-');
itemWriteByte('-');
uint8_t i;
for (i = 0; i < _boundary.length(); i++)
for (i = 0; i < _boundary.length(); i++) {
itemWriteByte(_boundary.c_str()[i]);
}
_parseMultipartPostByte(data, last);
}
} else if (_multiParseState == EXPECT_FEED2) {
@ -577,8 +642,9 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
itemWriteByte('-');
itemWriteByte('-');
uint8_t i;
for (i = 0; i < _boundary.length(); i++)
for (i = 0; i < _boundary.length(); i++) {
itemWriteByte(_boundary.c_str()[i]);
}
itemWriteByte('\r');
_parseMultipartPostByte(data, last);
}
@ -589,13 +655,13 @@ void AsyncWebServerRequest::_parseLine() {
if (_parseState == PARSE_REQ_START) {
if (!_temp.length()) {
_parseState = PARSE_REQ_FAIL;
_client->abort();
abort();
} else {
if (_parseReqHead()) {
_parseState = PARSE_REQ_HEADERS;
} else {
_parseState = PARSE_REQ_FAIL;
_client->abort();
abort();
}
}
return;
@ -614,19 +680,71 @@ void AsyncWebServerRequest::_parseLine() {
_parseState = PARSE_REQ_BODY;
} else {
_parseState = PARSE_REQ_END;
_server->_runChain(this, [this]() { return _handler ? _handler->_runChain(this, [this]() { _handler->handleRequest(this); }) : send(501); });
if (!_sent) {
if (!_response)
_runMiddlewareChain();
_send();
}
} else {
_parseReqHeader();
}
}
}
void AsyncWebServerRequest::_runMiddlewareChain() {
if (_handler && _handler->mustSkipServerMiddlewares()) {
_handler->_runChain(this, [this]() {
_handler->handleRequest(this);
});
} else {
_server->_runChain(this, [this]() {
if (_handler) {
_handler->_runChain(this, [this]() {
_handler->handleRequest(this);
});
}
});
}
}
void AsyncWebServerRequest::_send() {
if (!_sent && !_paused) {
// log_d("AsyncWebServerRequest::_send()");
// user did not create a response ?
if (!_response) {
send(501, T_text_plain, "Handler did not handle the request");
else if (!_response->_sourceValid())
}
// response is not valid ?
if (!_response->_sourceValid()) {
send(500, T_text_plain, "Invalid data in handler");
}
// here, we either have a response give nfrom user or one of the two above
_client->setRxTimeout(0);
_response->_respond(this);
_sent = true;
}
}
} else
_parseReqHeader();
AsyncWebServerRequestPtr AsyncWebServerRequest::pause() {
if (_paused) {
return _this;
}
client()->setRxTimeout(0);
// this shared ptr will hold the request pointer until it gets destroyed following a disconnect.
// this is just used as a holder providing weak observers, so the deleter is a no-op.
_this = std::shared_ptr<AsyncWebServerRequest>(this, doNotDelete);
_paused = true;
return _this;
}
void AsyncWebServerRequest::abort() {
if (!_sent) {
_sent = true;
_paused = false;
_this.reset();
// log_e("AsyncWebServerRequest::abort");
_client->abort();
}
}
@ -650,7 +768,9 @@ bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper* data) const {
#endif
const AsyncWebHeader *AsyncWebServerRequest::getHeader(const char *name) const {
auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader& header) { return header.name().equalsIgnoreCase(name); });
auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader &header) {
return header.name().equalsIgnoreCase(name);
});
return (iter == std::end(_headers)) ? nullptr : &(*iter);
}
@ -671,23 +791,25 @@ const AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper
#endif
const AsyncWebHeader *AsyncWebServerRequest::getHeader(size_t num) const {
if (num >= _headers.size())
if (num >= _headers.size()) {
return nullptr;
}
return &(*std::next(_headers.cbegin(), num));
}
size_t AsyncWebServerRequest::getHeaderNames(std::vector<const char *> &names) const {
const size_t size = _headers.size();
names.reserve(size);
const size_t size = names.size();
for (const auto &h : _headers) {
names.push_back(h.name().c_str());
}
return size;
return names.size() - size;
}
bool AsyncWebServerRequest::removeHeader(const char *name) {
const size_t size = _headers.size();
_headers.remove_if([name](const AsyncWebHeader& header) { return header.name().equalsIgnoreCase(name); });
_headers.remove_if([name](const AsyncWebHeader &header) {
return header.name().equalsIgnoreCase(name);
});
return size != _headers.size();
}
@ -720,8 +842,9 @@ const AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelp
#endif
const AsyncWebParameter *AsyncWebServerRequest::getParam(size_t num) const {
if (num >= _params.size())
if (num >= _params.size()) {
return nullptr;
}
return &(*std::next(_params.cbegin(), num));
}
@ -747,24 +870,30 @@ double AsyncWebServerRequest::getAttribute(const char* name, double defaultValue
}
AsyncWebServerResponse *AsyncWebServerRequest::beginResponse(int code, const char *contentType, const char *content, AwsTemplateProcessor callback) {
if (callback)
if (callback) {
return new AsyncProgmemResponse(code, contentType, (const uint8_t *)content, strlen(content), callback);
}
return new AsyncBasicResponse(code, contentType, content);
}
AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) {
AsyncWebServerResponse *
AsyncWebServerRequest::beginResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback) {
return new AsyncProgmemResponse(code, contentType, content, len, callback);
}
AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(FS& fs, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) {
if (fs.exists(path) || (!download && fs.exists(path + T__gz)))
AsyncWebServerResponse *
AsyncWebServerRequest::beginResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback) {
if (fs.exists(path) || (!download && fs.exists(path + T__gz))) {
return new AsyncFileResponse(fs, path, contentType, download, callback);
}
return NULL;
}
AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(File content, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) {
if (content == true)
AsyncWebServerResponse *
AsyncWebServerRequest::beginResponse(File content, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback) {
if (content == true) {
return new AsyncFileResponse(content, path, contentType, download, callback);
}
return NULL;
}
@ -772,13 +901,16 @@ AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(Stream& stream, con
return new AsyncStreamResponse(stream, contentType, len, callback);
}
AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) {
AsyncWebServerResponse *
AsyncWebServerRequest::beginResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) {
return new AsyncCallbackResponse(contentType, len, callback, templateCallback);
}
AsyncWebServerResponse* AsyncWebServerRequest::beginChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) {
if (_version)
AsyncWebServerResponse *
AsyncWebServerRequest::beginChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) {
if (_version) {
return new AsyncChunkedResponse(contentType, callback, templateCallback);
}
return new AsyncCallbackResponse(contentType, 0, callback, templateCallback);
}
@ -791,11 +923,22 @@ AsyncWebServerResponse* AsyncWebServerRequest::beginResponse_P(int code, const S
}
void AsyncWebServerRequest::send(AsyncWebServerResponse *response) {
if (_sent)
// request is already sent on the wire ?
if (_sent) {
return;
if (_response)
}
// if we already had a response, delete it and replace it with the new one
if (_response) {
delete _response;
}
_response = response;
// if request was paused, we need to send the response now
if (_paused) {
_paused = false;
_send();
}
}
void AsyncWebServerRequest::redirect(const char *url, int code) {
@ -806,30 +949,34 @@ void AsyncWebServerRequest::redirect(const char* url, int code) {
bool AsyncWebServerRequest::authenticate(const char *username, const char *password, const char *realm, bool passwordIsHash) const {
if (_authorization.length()) {
if (_authMethod == AsyncAuthType::AUTH_DIGEST)
if (_authMethod == AsyncAuthType::AUTH_DIGEST) {
return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL);
else if (!passwordIsHash)
} else if (!passwordIsHash) {
return checkBasicAuthentication(_authorization.c_str(), username, password);
else
} else {
return _authorization.equals(password);
}
}
return false;
}
bool AsyncWebServerRequest::authenticate(const char *hash) const {
if (!_authorization.length() || hash == NULL)
if (!_authorization.length() || hash == NULL) {
return false;
}
if (_authMethod == AsyncAuthType::AUTH_DIGEST) {
String hStr = String(hash);
int separator = hStr.indexOf(':');
if (separator <= 0)
if (separator <= 0) {
return false;
}
String username = hStr.substring(0, separator);
hStr = hStr.substring(separator + 1);
separator = hStr.indexOf(':');
if (separator <= 0)
if (separator <= 0) {
return false;
}
String realm = hStr.substring(0, separator);
hStr = hStr.substring(separator + 1);
return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL);
@ -840,38 +987,57 @@ bool AsyncWebServerRequest::authenticate(const char* hash) const {
}
void AsyncWebServerRequest::requestAuthentication(AsyncAuthType method, const char *realm, const char *_authFailMsg) {
if (!realm)
if (!realm) {
realm = T_LOGIN_REQ;
}
AsyncWebServerResponse *r = _authFailMsg ? beginResponse(401, T_text_html, _authFailMsg) : beginResponse(401);
switch (method) {
case AsyncAuthType::AUTH_BASIC: {
case AsyncAuthType::AUTH_BASIC:
{
String header;
header.reserve(strlen(T_BASIC_REALM) + strlen(realm) + 1);
if (header.reserve(strlen(T_BASIC_REALM) + strlen(realm) + 1)) {
header.concat(T_BASIC_REALM);
header.concat(realm);
header.concat('"');
r->addHeader(T_WWW_AUTH, header.c_str());
} else {
#ifdef ESP32
log_e("Failed to allocate");
#endif
abort();
}
break;
}
case AsyncAuthType::AUTH_DIGEST: {
case AsyncAuthType::AUTH_DIGEST:
{
size_t len = strlen(T_DIGEST_) + strlen(T_realm__) + strlen(T_auth_nonce) + 32 + strlen(T__opaque) + 32 + 1;
String header;
header.reserve(len + strlen(realm));
if (header.reserve(len + strlen(realm))) {
const String nonce = genRandomMD5();
const String opaque = genRandomMD5();
if (nonce.length() && opaque.length()) {
header.concat(T_DIGEST_);
header.concat(T_realm__);
header.concat(realm);
header.concat(T_auth_nonce);
header.concat(genRandomMD5());
header.concat(nonce);
header.concat(T__opaque);
header.concat(genRandomMD5());
header.concat(opaque);
header.concat((char)0x22); // '"'
r->addHeader(T_WWW_AUTH, header.c_str());
} else {
#ifdef ESP32
log_e("Failed to allocate");
#endif
abort();
}
}
break;
}
default:
break;
default: break;
}
send(r);
@ -916,7 +1082,12 @@ const String& AsyncWebServerRequest::argName(size_t i) const {
}
const String &AsyncWebServerRequest::pathArg(size_t i) const {
return i < _pathParams.size() ? _pathParams[i] : emptyString;
if (i >= _pathParams.size()) {
return emptyString;
}
auto it = _pathParams.begin();
std::advance(it, i);
return *it;
}
const String &AsyncWebServerRequest::header(const char *name) const {
@ -945,7 +1116,13 @@ String AsyncWebServerRequest::urlDecode(const String& text) const {
unsigned int len = text.length();
unsigned int i = 0;
String decoded;
decoded.reserve(len); // Allocate the string internal buffer - never longer from source text
// Allocate the string internal buffer - never longer from source text
if (!decoded.reserve(len)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
return emptyString;
}
while (i < len) {
char decodedChar;
char encodedChar = text.charAt(i++);
@ -964,44 +1141,45 @@ String AsyncWebServerRequest::urlDecode(const String& text) const {
}
const char *AsyncWebServerRequest::methodToString() const {
if (_method == HTTP_ANY)
if (_method == HTTP_ANY) {
return T_ANY;
if (_method & HTTP_GET)
}
if (_method & HTTP_GET) {
return T_GET;
if (_method & HTTP_POST)
}
if (_method & HTTP_POST) {
return T_POST;
if (_method & HTTP_DELETE)
}
if (_method & HTTP_DELETE) {
return T_DELETE;
if (_method & HTTP_PUT)
}
if (_method & HTTP_PUT) {
return T_PUT;
if (_method & HTTP_PATCH)
}
if (_method & HTTP_PATCH) {
return T_PATCH;
if (_method & HTTP_HEAD)
}
if (_method & HTTP_HEAD) {
return T_HEAD;
if (_method & HTTP_OPTIONS)
}
if (_method & HTTP_OPTIONS) {
return T_OPTIONS;
}
return T_UNKNOWN;
}
const char *AsyncWebServerRequest::requestedConnTypeToString() const {
switch (_reqconntype) {
case RCT_NOT_USED:
return T_RCT_NOT_USED;
case RCT_DEFAULT:
return T_RCT_DEFAULT;
case RCT_HTTP:
return T_RCT_HTTP;
case RCT_WS:
return T_RCT_WS;
case RCT_EVENT:
return T_RCT_EVENT;
default:
return T_ERROR;
case RCT_NOT_USED: return T_RCT_NOT_USED;
case RCT_DEFAULT: return T_RCT_DEFAULT;
case RCT_HTTP: return T_RCT_HTTP;
case RCT_WS: return T_RCT_WS;
case RCT_EVENT: return T_RCT_EVENT;
default: return T_ERROR;
}
}
bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) const {
return ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) ||
((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype)) ||
((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype));
return ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) || ((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype))
|| ((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype));
}

View File

@ -1,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_
#define ASYNCWEBSERVERRESPONSEIMPL_H_
@ -39,18 +22,23 @@ class AsyncBasicResponse : public AsyncWebServerResponse {
public:
explicit AsyncBasicResponse(int code, const char *contentType = asyncsrv::empty, const char *content = asyncsrv::empty);
AsyncBasicResponse(int code, const String& contentType, const String& content = emptyString) : AsyncBasicResponse(code, contentType.c_str(), content.c_str()) {}
AsyncBasicResponse(int code, const String &contentType, const String &content = emptyString)
: AsyncBasicResponse(code, contentType.c_str(), content.c_str()) {}
void _respond(AsyncWebServerRequest *request) override final;
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override final;
bool _sourceValid() const override final { return true; }
bool _sourceValid() const override final {
return true;
}
};
class AsyncAbstractResponse : public AsyncWebServerResponse {
private:
// amount of responce data in-flight, i.e. sent, but not acked yet
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
// amount of response data in-flight, i.e. sent, but not acked yet
size_t _in_flight{0};
// in-flight queue credits
size_t _in_flight_credit{2};
#endif
String _head;
// Data is inserted into cache at begin().
// This is inefficient with vector, but if we use some other container,
@ -68,8 +56,12 @@ class AsyncAbstractResponse : public AsyncWebServerResponse {
virtual ~AsyncAbstractResponse() {}
void _respond(AsyncWebServerRequest *request) override final;
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override final;
virtual bool _sourceValid() const { return false; }
virtual size_t _fillBuffer(uint8_t* buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; }
virtual bool _sourceValid() const {
return false;
}
virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) {
return 0;
}
};
#ifndef TEMPLATE_PLACEHOLDER
@ -88,11 +80,19 @@ class AsyncFileResponse : public AsyncAbstractResponse {
public:
AsyncFileResponse(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncFileResponse(FS& fs, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callback = nullptr) : AsyncFileResponse(fs, path, contentType.c_str(), download, callback) {}
AsyncFileResponse(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncFileResponse(File content, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callack = nullptr) : AsyncFileResponse(content, path, contentType.c_str(), download, callack) {}
~AsyncFileResponse() { _content.close(); }
bool _sourceValid() const override final { return !!(_content); }
AsyncFileResponse(FS &fs, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr)
: AsyncFileResponse(fs, path, contentType.c_str(), download, callback) {}
AsyncFileResponse(
File content, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr
);
AsyncFileResponse(File content, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr)
: AsyncFileResponse(content, path, contentType.c_str(), download, callback) {}
~AsyncFileResponse() {
_content.close();
}
bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
};
@ -102,8 +102,11 @@ class AsyncStreamResponse : public AsyncAbstractResponse {
public:
AsyncStreamResponse(Stream &stream, const char *contentType, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncStreamResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) : AsyncStreamResponse(stream, contentType.c_str(), len, callback) {}
bool _sourceValid() const override final { return !!(_content); }
AsyncStreamResponse(Stream &stream, const String &contentType, size_t len, AwsTemplateProcessor callback = nullptr)
: AsyncStreamResponse(stream, contentType.c_str(), len, callback) {}
bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
};
@ -114,8 +117,11 @@ class AsyncCallbackResponse : public AsyncAbstractResponse {
public:
AsyncCallbackResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) : AsyncCallbackResponse(contentType.c_str(), len, callback, templateCallback) {}
bool _sourceValid() const override final { return !!(_content); }
AsyncCallbackResponse(const String &contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr)
: AsyncCallbackResponse(contentType.c_str(), len, callback, templateCallback) {}
bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
};
@ -126,8 +132,11 @@ class AsyncChunkedResponse : public AsyncAbstractResponse {
public:
AsyncChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) : AsyncChunkedResponse(contentType.c_str(), callback, templateCallback) {}
bool _sourceValid() const override final { return !!(_content); }
AsyncChunkedResponse(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr)
: AsyncChunkedResponse(contentType.c_str(), callback, templateCallback) {}
bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
};
@ -138,8 +147,11 @@ class AsyncProgmemResponse : public AsyncAbstractResponse {
public:
AsyncProgmemResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncProgmemResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) : AsyncProgmemResponse(code, contentType.c_str(), content, len, callback) {}
bool _sourceValid() const override final { return true; }
AsyncProgmemResponse(int code, const String &contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr)
: AsyncProgmemResponse(code, contentType.c_str(), content, len, callback) {}
bool _sourceValid() const override final {
return true;
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
};
@ -150,7 +162,9 @@ class AsyncResponseStream : public AsyncAbstractResponse, public Print {
public:
AsyncResponseStream(const char *contentType, size_t bufferSize);
AsyncResponseStream(const String &contentType, size_t bufferSize) : AsyncResponseStream(contentType.c_str(), bufferSize) {}
bool _sourceValid() const override final { return (_state < RESPONSE_END); }
bool _sourceValid() const override final {
return (_state < RESPONSE_END);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
size_t write(const uint8_t *data, size_t len);
size_t write(uint8_t data);

View File

@ -1,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ESPAsyncWebServer.h"
#include "WebResponseImpl.h"
@ -26,9 +9,11 @@ using namespace asyncsrv;
// Since ESP8266 does not link memchr by default, here's its implementation.
void *memchr(void *ptr, int ch, size_t count) {
unsigned char *p = static_cast<unsigned char *>(ptr);
while (count--)
if (*p++ == static_cast<unsigned char>(ch))
while (count--) {
if (*p++ == static_cast<unsigned char>(ch)) {
return --p;
}
}
return nullptr;
}
@ -39,118 +24,93 @@ void* memchr(void* ptr, int ch, size_t count) {
const char *AsyncWebServerResponse::responseCodeToString(int code) {
switch (code) {
case 100:
return T_HTTP_CODE_100;
case 101:
return T_HTTP_CODE_101;
case 200:
return T_HTTP_CODE_200;
case 201:
return T_HTTP_CODE_201;
case 202:
return T_HTTP_CODE_202;
case 203:
return T_HTTP_CODE_203;
case 204:
return T_HTTP_CODE_204;
case 205:
return T_HTTP_CODE_205;
case 206:
return T_HTTP_CODE_206;
case 300:
return T_HTTP_CODE_300;
case 301:
return T_HTTP_CODE_301;
case 302:
return T_HTTP_CODE_302;
case 303:
return T_HTTP_CODE_303;
case 304:
return T_HTTP_CODE_304;
case 305:
return T_HTTP_CODE_305;
case 307:
return T_HTTP_CODE_307;
case 400:
return T_HTTP_CODE_400;
case 401:
return T_HTTP_CODE_401;
case 402:
return T_HTTP_CODE_402;
case 403:
return T_HTTP_CODE_403;
case 404:
return T_HTTP_CODE_404;
case 405:
return T_HTTP_CODE_405;
case 406:
return T_HTTP_CODE_406;
case 407:
return T_HTTP_CODE_407;
case 408:
return T_HTTP_CODE_408;
case 409:
return T_HTTP_CODE_409;
case 410:
return T_HTTP_CODE_410;
case 411:
return T_HTTP_CODE_411;
case 412:
return T_HTTP_CODE_412;
case 413:
return T_HTTP_CODE_413;
case 414:
return T_HTTP_CODE_414;
case 415:
return T_HTTP_CODE_415;
case 416:
return T_HTTP_CODE_416;
case 417:
return T_HTTP_CODE_417;
case 429:
return T_HTTP_CODE_429;
case 500:
return T_HTTP_CODE_500;
case 501:
return T_HTTP_CODE_501;
case 502:
return T_HTTP_CODE_502;
case 503:
return T_HTTP_CODE_503;
case 504:
return T_HTTP_CODE_504;
case 505:
return T_HTTP_CODE_505;
default:
return T_HTTP_CODE_ANY;
case 100: return T_HTTP_CODE_100;
case 101: return T_HTTP_CODE_101;
case 200: return T_HTTP_CODE_200;
case 201: return T_HTTP_CODE_201;
case 202: return T_HTTP_CODE_202;
case 203: return T_HTTP_CODE_203;
case 204: return T_HTTP_CODE_204;
case 205: return T_HTTP_CODE_205;
case 206: return T_HTTP_CODE_206;
case 300: return T_HTTP_CODE_300;
case 301: return T_HTTP_CODE_301;
case 302: return T_HTTP_CODE_302;
case 303: return T_HTTP_CODE_303;
case 304: return T_HTTP_CODE_304;
case 305: return T_HTTP_CODE_305;
case 307: return T_HTTP_CODE_307;
case 400: return T_HTTP_CODE_400;
case 401: return T_HTTP_CODE_401;
case 402: return T_HTTP_CODE_402;
case 403: return T_HTTP_CODE_403;
case 404: return T_HTTP_CODE_404;
case 405: return T_HTTP_CODE_405;
case 406: return T_HTTP_CODE_406;
case 407: return T_HTTP_CODE_407;
case 408: return T_HTTP_CODE_408;
case 409: return T_HTTP_CODE_409;
case 410: return T_HTTP_CODE_410;
case 411: return T_HTTP_CODE_411;
case 412: return T_HTTP_CODE_412;
case 413: return T_HTTP_CODE_413;
case 414: return T_HTTP_CODE_414;
case 415: return T_HTTP_CODE_415;
case 416: return T_HTTP_CODE_416;
case 417: return T_HTTP_CODE_417;
case 429: return T_HTTP_CODE_429;
case 500: return T_HTTP_CODE_500;
case 501: return T_HTTP_CODE_501;
case 502: return T_HTTP_CODE_502;
case 503: return T_HTTP_CODE_503;
case 504: return T_HTTP_CODE_504;
case 505: return T_HTTP_CODE_505;
default: return T_HTTP_CODE_ANY;
}
}
AsyncWebServerResponse::AsyncWebServerResponse()
: _code(0), _contentType(), _contentLength(0), _sendContentLength(true), _chunked(false), _headLength(0), _sentLength(0), _ackedLength(0), _writtenLength(0), _state(RESPONSE_SETUP) {
: _code(0), _contentType(), _contentLength(0), _sendContentLength(true), _chunked(false), _headLength(0), _sentLength(0), _ackedLength(0), _writtenLength(0),
_state(RESPONSE_SETUP) {
for (const auto &header : DefaultHeaders::Instance()) {
_headers.emplace_back(header);
}
}
void AsyncWebServerResponse::setCode(int code) {
if (_state == RESPONSE_SETUP)
if (_state == RESPONSE_SETUP) {
_code = code;
}
}
void AsyncWebServerResponse::setContentLength(size_t len) {
if (_state == RESPONSE_SETUP && addHeader(T_Content_Length, len, true))
if (_state == RESPONSE_SETUP && addHeader(T_Content_Length, len, true)) {
_contentLength = len;
}
}
void AsyncWebServerResponse::setContentType(const char *type) {
if (_state == RESPONSE_SETUP && addHeader(T_Content_Type, type, true))
if (_state == RESPONSE_SETUP && addHeader(T_Content_Type, type, true)) {
_contentType = type;
}
}
bool AsyncWebServerResponse::removeHeader(const char *name) {
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
bool h_erased = false;
for (auto i = _headers.begin(); i != _headers.end();) {
if (i->name().equalsIgnoreCase(name)) {
_headers.erase(i);
h_erased = true;
} else {
++i;
}
}
return h_erased;
}
bool AsyncWebServerResponse::removeHeader(const char *name, const char *value) {
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
if (i->name().equalsIgnoreCase(name) && i->value().equalsIgnoreCase(value)) {
_headers.erase(i);
return true;
}
@ -159,10 +119,21 @@ bool AsyncWebServerResponse::removeHeader(const char* name) {
}
const AsyncWebHeader *AsyncWebServerResponse::getHeader(const char *name) const {
auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader& header) { return header.name().equalsIgnoreCase(name); });
auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader &header) {
return header.name().equalsIgnoreCase(name);
});
return (iter == std::end(_headers)) ? nullptr : &(*iter);
}
bool AsyncWebServerResponse::headerMustBePresentOnce(const String &name) {
for (uint8_t i = 0; i < T_only_once_headers_len; i++) {
if (name.equalsIgnoreCase(T_only_once_headers[i])) {
return true;
}
}
return false;
}
bool AsyncWebServerResponse::addHeader(const char *name, const char *value, bool replaceExisting) {
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
if (i->name().equalsIgnoreCase(name)) {
@ -171,9 +142,11 @@ bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool
// remove, break and add the new one
_headers.erase(i);
break;
} else {
} else if (headerMustBePresentOnce(i->name())) { // we can have only one header with that name
// do not update
return false;
} else {
break; // accept multiple headers with the same name
}
}
}
@ -185,21 +158,25 @@ bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool
void AsyncWebServerResponse::_assembleHead(String &buffer, uint8_t version) {
if (version) {
addHeader(T_Accept_Ranges, T_none, false);
if (_chunked)
if (_chunked) {
addHeader(T_Transfer_Encoding, T_chunked, false);
}
}
if (_sendContentLength)
if (_sendContentLength) {
addHeader(T_Content_Length, String(_contentLength), false);
}
if (_contentType.length())
if (_contentType.length()) {
addHeader(T_Content_Type, _contentType.c_str(), false);
}
// precompute buffer size to avoid reallocations by String class
size_t len = 0;
len += 50; // HTTP/1.1 200 <reason>\r\n
for (const auto& header : _headers)
for (const auto &header : _headers) {
len += header.name().length() + header.value().length() + 4;
}
// prepare buffer
buffer.reserve(len);
@ -233,10 +210,18 @@ void AsyncWebServerResponse::_assembleHead(String& buffer, uint8_t version) {
_headLength = buffer.length();
}
bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; }
bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; }
bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; }
bool AsyncWebServerResponse::_sourceValid() const { return false; }
bool AsyncWebServerResponse::_started() const {
return _state > RESPONSE_SETUP;
}
bool AsyncWebServerResponse::_finished() const {
return _state > RESPONSE_WAIT_ACK;
}
bool AsyncWebServerResponse::_failed() const {
return _state == RESPONSE_FAILED;
}
bool AsyncWebServerResponse::_sourceValid() const {
return false;
}
void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request) {
_state = RESPONSE_END;
request->client()->close();
@ -257,9 +242,10 @@ AsyncBasicResponse::AsyncBasicResponse(int code, const char* contentType, const
_contentType = contentType;
if (_content.length()) {
_contentLength = _content.length();
if (!_contentType.length())
if (!_contentType.length()) {
_contentType = T_text_plain;
}
}
addHeader(T_Connection, T_close, false);
}
@ -352,9 +338,12 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
request->client()->close();
return 0;
}
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
// return a credit for each chunk of acked data (polls does not give any credits)
if (len)
if (len) {
++_in_flight_credit;
}
// for chunked responses ignore acks if there are no _in_flight_credits left
if (_chunked && !_in_flight_credit) {
@ -364,9 +353,11 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
return 0;
}
_ackedLength += len;
_in_flight -= (_in_flight > len) ? len : _in_flight;
// get the size of available sock space
#endif
_ackedLength += len;
size_t space = request->client()->space();
size_t headLen = _head.length();
@ -378,13 +369,16 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
String out = _head.substring(0, space);
_head = _head.substring(space);
_writtenLength += request->client()->write(out.c_str(), out.length());
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
_in_flight += out.length();
--_in_flight_credit; // take a credit
#endif
return out.length();
}
}
if (_state == RESPONSE_CONTENT) {
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
// for response data we need to control the queue and in-flight fragmentation. Sending small chunks could give low latency,
// but flood asynctcp's queue and fragment socket buffer space for large responses.
// Let's ignore polled acks and acks in case when we have more in-flight data then the available socket buff space.
@ -392,10 +386,12 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
if (_in_flight > space) {
// log_d("defer user call %u/%u", _in_flight, space);
// take the credit back since we are ignoring this ack and rely on other inflight data
if (len)
if (len) {
--_in_flight_credit;
}
return 0;
}
#endif
size_t outLen;
if (_chunked) {
@ -412,7 +408,10 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
uint8_t *buf = (uint8_t *)malloc(outLen + headLen);
if (!buf) {
// os_printf("_ack malloc %d failed\n", outLen+headLen);
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return 0;
}
@ -451,8 +450,10 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
if (outLen) {
_writtenLength += request->client()->write((const char *)buf, outLen);
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
_in_flight += outLen;
--_in_flight_credit; // take a credit
#endif
}
if (_chunked) {
@ -471,10 +472,11 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
} else if (_state == RESPONSE_WAIT_ACK) {
if (!_sendContentLength || _ackedLength >= _writtenLength) {
_state = RESPONSE_END;
if (!_chunked && !_sendContentLength)
if (!_chunked && !_sendContentLength) {
request->client()->close(true);
}
}
}
return 0;
}
@ -492,16 +494,19 @@ size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const s
}
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t *data, size_t len) {
if (!_callback)
if (!_callback) {
return _fillBuffer(data, len);
}
const size_t originalLen = len;
len = _readDataFromCacheOrContent(data, len);
// Now we've read 'len' bytes, either from cache or from file
// Search for template placeholders
uint8_t *pTemplateStart = data;
while ((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1]
uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
while ((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t *)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))
) { // data[0] ... data[len - 1]
uint8_t *pTemplateEnd =
(pTemplateStart < &data[len - 1]) ? (uint8_t *)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
// temporary buffer to hold parameter name
uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
String paramName;
@ -519,9 +524,11 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1;
++pTemplateStart;
}
} else if (&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data
} else if (&data[len - 1] - pTemplateStart + 1
< TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data
memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart);
const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
const size_t readFromCacheOrContent =
_readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
if (readFromCacheOrContent) {
pTemplateEnd = (uint8_t *)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
if (pTemplateEnd) {
@ -537,10 +544,12 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
_cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
++pTemplateStart;
}
} else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
} else { // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
++pTemplateStart;
} else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
}
} else { // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
++pTemplateStart;
}
if (paramName.length()) {
// call callback and replace with result.
// Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value.
@ -558,10 +567,11 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
// 2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied);
len = originalLen; // fix issue with truncated data, not sure if it has any side effects
} else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied)
} else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied) {
// 2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit.
// Move the entire data after the placeholder
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
}
// 3. replace placeholder with actual value
memcpy(pTemplateStart, pvstr, numBytesCopied);
// If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer)
@ -594,48 +604,50 @@ void AsyncFileResponse::_setContentTypeFromPath(const String& path) {
#endif
_contentType = getContentType(path);
#else
if (path.endsWith(T__html))
if (path.endsWith(T__html)) {
_contentType = T_text_html;
else if (path.endsWith(T__htm))
} else if (path.endsWith(T__htm)) {
_contentType = T_text_html;
else if (path.endsWith(T__css))
} else if (path.endsWith(T__css)) {
_contentType = T_text_css;
else if (path.endsWith(T__json))
} else if (path.endsWith(T__json)) {
_contentType = T_application_json;
else if (path.endsWith(T__js))
} else if (path.endsWith(T__js)) {
_contentType = T_application_javascript;
else if (path.endsWith(T__png))
} else if (path.endsWith(T__png)) {
_contentType = T_image_png;
else if (path.endsWith(T__gif))
} else if (path.endsWith(T__gif)) {
_contentType = T_image_gif;
else if (path.endsWith(T__jpg))
} else if (path.endsWith(T__jpg)) {
_contentType = T_image_jpeg;
else if (path.endsWith(T__ico))
} else if (path.endsWith(T__ico)) {
_contentType = T_image_x_icon;
else if (path.endsWith(T__svg))
} else if (path.endsWith(T__svg)) {
_contentType = T_image_svg_xml;
else if (path.endsWith(T__eot))
} else if (path.endsWith(T__eot)) {
_contentType = T_font_eot;
else if (path.endsWith(T__woff))
} else if (path.endsWith(T__woff)) {
_contentType = T_font_woff;
else if (path.endsWith(T__woff2))
} else if (path.endsWith(T__woff2)) {
_contentType = T_font_woff2;
else if (path.endsWith(T__ttf))
} else if (path.endsWith(T__ttf)) {
_contentType = T_font_ttf;
else if (path.endsWith(T__xml))
} else if (path.endsWith(T__xml)) {
_contentType = T_text_xml;
else if (path.endsWith(T__pdf))
} else if (path.endsWith(T__pdf)) {
_contentType = T_application_pdf;
else if (path.endsWith(T__zip))
} else if (path.endsWith(T__zip)) {
_contentType = T_application_zip;
else if (path.endsWith(T__gz))
} else if (path.endsWith(T__gz)) {
_contentType = T_application_x_gzip;
else
} else {
_contentType = T_text_plain;
}
#endif
}
AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = 200;
_path = path;
@ -650,10 +662,11 @@ AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* con
_content = fs.open(_path, fs::FileOpenMode::read);
_contentLength = _content.size();
if (strlen(contentType) == 0)
if (strlen(contentType) == 0) {
_setContentTypeFromPath(path);
else
} else {
_contentType = contentType;
}
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26 + path.length() - filenameStart];
@ -669,7 +682,8 @@ AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* con
addHeader(T_Content_Disposition, buf, false);
}
AsyncFileResponse::AsyncFileResponse(File content, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
AsyncFileResponse::AsyncFileResponse(File content, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = 200;
_path = path;
@ -683,10 +697,11 @@ AsyncFileResponse::AsyncFileResponse(File content, const String& path, const cha
_content = content;
_contentLength = _content.size();
if (strlen(contentType) == 0)
if (strlen(contentType) == 0) {
_setContentTypeFromPath(path);
else
} else {
_contentType = contentType;
}
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26 + path.length() - filenameStart];
@ -719,8 +734,9 @@ size_t AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) {
size_t available = _content->available();
size_t outLen = (available > len) ? len : available;
size_t i;
for (i = 0; i < outLen; i++)
for (i = 0; i < outLen; i++) {
data[i] = _content->read();
}
return outLen;
}
@ -728,12 +744,14 @@ size_t AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) {
* Callback Response
* */
AsyncCallbackResponse::AsyncCallbackResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) : AsyncAbstractResponse(templateCallback) {
AsyncCallbackResponse::AsyncCallbackResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback)
: AsyncAbstractResponse(templateCallback) {
_code = 200;
_content = callback;
_contentLength = len;
if (!len)
if (!len) {
_sendContentLength = false;
}
_contentType = contentType;
_filledLength = 0;
}
@ -750,7 +768,8 @@ size_t AsyncCallbackResponse::_fillBuffer(uint8_t* data, size_t len) {
* Chunked Response
* */
AsyncChunkedResponse::AsyncChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback) : AsyncAbstractResponse(processorCallback) {
AsyncChunkedResponse::AsyncChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback)
: AsyncAbstractResponse(processorCallback) {
_code = 200;
_content = callback;
_contentLength = 0;
@ -772,7 +791,8 @@ size_t AsyncChunkedResponse::_fillBuffer(uint8_t* data, size_t len) {
* Progmem Response
* */
AsyncProgmemResponse::AsyncProgmemResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
AsyncProgmemResponse::AsyncProgmemResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = code;
_content = content;
_contentType = contentType;
@ -800,7 +820,11 @@ AsyncResponseStream::AsyncResponseStream(const char* contentType, size_t bufferS
_code = 200;
_contentLength = 0;
_contentType = contentType;
_content.reserve(bufferSize);
if (!_content.reserve(bufferSize)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
}
}
size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen) {
@ -808,8 +832,9 @@ size_t AsyncResponseStream::_fillBuffer(uint8_t* buf, size_t maxLen) {
}
size_t AsyncResponseStream::write(const uint8_t *data, size_t len) {
if (_started())
if (_started()) {
return 0;
}
size_t written = _content.write(data, len);
_contentLength += written;
return written;

View File

@ -1,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ESPAsyncWebServer.h"
#include "WebHandlerImpl.h"
@ -45,14 +28,13 @@ const char* fs::FileOpenMode::write = "w";
const char *fs::FileOpenMode::append = "a";
#endif
AsyncWebServer::AsyncWebServer(uint16_t port)
: _server(port) {
AsyncWebServer::AsyncWebServer(uint16_t port) : _server(port) {
_catchAllHandler = new AsyncCallbackWebHandler();
if (_catchAllHandler == NULL)
return;
_server.onClient([](void* s, AsyncClient* c) {
if (c == NULL)
_server.onClient(
[](void *s, AsyncClient *c) {
if (c == NULL) {
return;
}
c->setRxTimeout(3);
AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer *)s, c);
if (r == NULL) {
@ -60,14 +42,15 @@ AsyncWebServer::AsyncWebServer(uint16_t port)
delete c;
}
},
this);
this
);
}
AsyncWebServer::~AsyncWebServer() {
reset();
end();
if (_catchAllHandler)
delete _catchAllHandler;
_catchAllHandler = nullptr; // Prevent potential use-after-free
}
AsyncWebRewrite &AsyncWebServer::addRewrite(std::shared_ptr<AsyncWebRewrite> rewrite) {
@ -138,6 +121,8 @@ void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest* request) {
}
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request) {
// the last rewrite that matches the request will be used
// we do not break the loop to allow for multiple rewrites to be applied and only the last one to be used (allows overriding)
for (const auto &r : _rewrites) {
if (r->match(request)) {
request->_url = r->toUrl();
@ -153,11 +138,13 @@ void AsyncWebServer::_attachHandler(AsyncWebServerRequest* request) {
return;
}
}
// ESP_LOGD("AsyncWebServer", "No handler found for %s, using _catchAllHandler pointer: %p", request->url().c_str(), _catchAllHandler);
request->setHandler(_catchAllHandler);
}
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody) {
AsyncCallbackWebHandler &AsyncWebServer::on(
const char *uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody
) {
AsyncCallbackWebHandler *handler = new AsyncCallbackWebHandler();
handler->setUri(uri);
handler->setMethod(method);
@ -186,13 +173,15 @@ void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn) {
_catchAllHandler->onBody(fn);
}
AsyncWebHandler &AsyncWebServer::catchAllHandler() const {
return *_catchAllHandler;
}
void AsyncWebServer::reset() {
_rewrites.clear();
_handlers.clear();
if (_catchAllHandler != NULL) {
_catchAllHandler->onRequest(NULL);
_catchAllHandler->onUpload(NULL);
_catchAllHandler->onBody(NULL);
}
}

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#pragma once
namespace asyncsrv {
@ -25,6 +28,7 @@ namespace asyncsrv {
static constexpr const char *T_Content_Encoding = "content-encoding";
static constexpr const char *T_Content_Length = "content-length";
static constexpr const char *T_Content_Type = "content-type";
static constexpr const char *T_Content_Location = "content-location";
static constexpr const char *T_Cookie = "cookie";
static constexpr const char *T_CORS_ACAC = "access-control-allow-credentials";
static constexpr const char *T_CORS_ACAH = "access-control-allow-headers";
@ -33,6 +37,7 @@ namespace asyncsrv {
static constexpr const char *T_CORS_ACMA = "access-control-max-age";
static constexpr const char *T_CORS_O = "origin";
static constexpr const char *T_data_ = "data: ";
static constexpr const char *T_Date = "date";
static constexpr const char *T_DIGEST = "digest";
static constexpr const char *T_DIGEST_ = "digest ";
static constexpr const char *T_ETag = "etag";
@ -68,6 +73,7 @@ namespace asyncsrv {
static constexpr const char *T_nn = "\n\n";
static constexpr const char *T_rn = "\r\n";
static constexpr const char *T_rnrn = "\r\n\r\n";
static constexpr const char *T_Server = "server";
static constexpr const char *T_Transfer_Encoding = "transfer-encoding";
static constexpr const char *T_TRUE = "true";
static constexpr const char *T_UPGRADE = "upgrade";
@ -96,7 +102,7 @@ namespace asyncsrv {
static constexpr const char *T_RCT_EVENT = "RCT_EVENT";
static constexpr const char *T_ERROR = "ERROR";
// extentions & MIME-Types
// extensions & MIME-Types
static constexpr const char *T__css = ".css";
static constexpr const char *T__eot = ".eot";
static constexpr const char *T__gif = ".gif";
@ -136,7 +142,7 @@ namespace asyncsrv {
static constexpr const char *T_text_plain = "text/plain";
static constexpr const char *T_text_xml = "text/xml";
// Responce codes
// Response codes
static constexpr const char *T_HTTP_CODE_100 = "Continue";
static constexpr const char *T_HTTP_CODE_101 = "Switching Protocols";
static constexpr const char *T_HTTP_CODE_200 = "OK";
@ -180,4 +186,8 @@ namespace asyncsrv {
static constexpr const char *T_HTTP_CODE_505 = "HTTP Version not supported";
static constexpr const char *T_HTTP_CODE_ANY = "Unknown code";
} // namespace asyncsrv {}
static constexpr const uint8_t T_only_once_headers_len = 11;
static constexpr const char *T_only_once_headers[] = {T_Content_Length, T_Content_Type, T_Date, T_ETag, T_Last_Modified, T_LOCATION, T_retry_after,
T_Transfer_Encoding, T_Content_Location, T_Server, T_WWW_AUTH};
} // namespace asyncsrv