Update na verzi 3.7.2. POZOR: vyzaduje do platformio.ini doplnit lib_compat_mode = strict
This commit is contained in:
parent
d55bb23fef
commit
b12c714d5e
@ -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)
|
||||
|
@ -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.
|
||||
|
||||
|
48
data/README.md
Normal file
48
data/README.md
Normal 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
157
examples/Auth/Auth.ino
Normal 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
60
examples/CORS/CORS.ino
Normal 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);
|
||||
}
|
@ -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());
|
||||
|
133
examples/CatchAllHandler/CatchAllHandler.ino
Normal file
133
examples/CatchAllHandler/CatchAllHandler.ino
Normal 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);
|
||||
}
|
140
examples/ChunkResponse/ChunkResponse.ino
Normal file
140
examples/ChunkResponse/ChunkResponse.ino
Normal 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);
|
||||
}
|
216
examples/ChunkRetryResponse/ChunkRetryResponse.ino
Normal file
216
examples/ChunkRetryResponse/ChunkRetryResponse.ino
Normal 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;
|
||||
}
|
||||
}
|
49
examples/EndBegin/EndBegin.ino
Normal file
49
examples/EndBegin/EndBegin.ino
Normal 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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
107
examples/FlashResponse/FlashResponse.ino
Normal file
107
examples/FlashResponse/FlashResponse.ino
Normal 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);
|
||||
}
|
88
examples/HeaderManipulation/HeaderManipulation.ino
Normal file
88
examples/HeaderManipulation/HeaderManipulation.ino
Normal 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);
|
||||
}
|
69
examples/Headers/Headers.ino
Normal file
69
examples/Headers/Headers.ino
Normal 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);
|
||||
}
|
@ -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();
|
||||
}
|
@ -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
90
examples/Json/Json.ino
Normal 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);
|
||||
}
|
49
examples/Logging/Logging.ino
Normal file
49
examples/Logging/Logging.ino
Normal 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);
|
||||
}
|
88
examples/MessagePack/MessagePack.ino
Normal file
88
examples/MessagePack/MessagePack.ino
Normal 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);
|
||||
}
|
82
examples/Middleware/Middleware.ino
Normal file
82
examples/Middleware/Middleware.ino
Normal 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
122
examples/Params/Params.ino
Normal 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);
|
||||
}
|
130
examples/PartitionDownloader/PartitionDownloader.ino
Normal file
130
examples/PartitionDownloader/PartitionDownloader.ino
Normal 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
|
243
examples/PerfTests/PerfTests.ino
Normal file
243
examples/PerfTests/PerfTests.ino
Normal 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
|
||||
}
|
64
examples/RateLimit/RateLimit.ino
Normal file
64
examples/RateLimit/RateLimit.ino
Normal 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);
|
||||
}
|
48
examples/Redirect/Redirect.ino
Normal file
48
examples/Redirect/Redirect.ino
Normal 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);
|
||||
}
|
91
examples/RequestContinuation/RequestContinuation.ino
Normal file
91
examples/RequestContinuation/RequestContinuation.ino
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
61
examples/ResumableDownload/ResumableDownload.ino
Normal file
61
examples/ResumableDownload/ResumableDownload.ino
Normal 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);
|
||||
}
|
52
examples/Rewrite/Rewrite.ino
Normal file
52
examples/Rewrite/Rewrite.ino
Normal 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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
105
examples/ServerSentEvents/ServerSentEvents.ino
Normal file
105
examples/ServerSentEvents/ServerSentEvents.ino
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
73
examples/SkipServerMiddleware/SkipServerMiddleware.ino
Normal file
73
examples/SkipServerMiddleware/SkipServerMiddleware.ino
Normal 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);
|
||||
}
|
152
examples/SlowChunkResponse/SlowChunkResponse.ino
Normal file
152
examples/SlowChunkResponse/SlowChunkResponse.ino
Normal 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
|
||||
}
|
122
examples/StaticFile/StaticFile.ino
Normal file
122
examples/StaticFile/StaticFile.ino
Normal 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);
|
||||
}
|
@ -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;
|
||||
};
|
@ -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();
|
||||
}
|
||||
}
|
99
examples/Templates/Templates.ino
Normal file
99
examples/Templates/Templates.ino
Normal 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
171
examples/Upload/Upload.ino
Normal 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);
|
||||
}
|
113
examples/WebSocket/WebSocket.ino
Normal file
113
examples/WebSocket/WebSocket.ino
Normal 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
37
idf_component.yml
Normal 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
|
8
idf_component_examples/catchall/CMakeLists.txt
Normal file
8
idf_component_examples/catchall/CMakeLists.txt
Normal 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)
|
1
idf_component_examples/catchall/README.md
Normal file
1
idf_component_examples/catchall/README.md
Normal file
@ -0,0 +1 @@
|
||||
### Basic example to show how to catch all requests and send a 404 Not Found response
|
2
idf_component_examples/catchall/main/CMakeLists.txt
Normal file
2
idf_component_examples/catchall/main/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "main.cpp"
|
||||
INCLUDE_DIRS ".")
|
6
idf_component_examples/catchall/main/idf_component.yml
Normal file
6
idf_component_examples/catchall/main/idf_component.yml
Normal file
@ -0,0 +1,6 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
esp32async/espasyncwebserver:
|
||||
version: "*"
|
||||
override_path: "../../../"
|
||||
pre_release: true
|
125
idf_component_examples/catchall/main/main.cpp
Normal file
125
idf_component_examples/catchall/main/main.cpp
Normal 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);
|
||||
}
|
12
idf_component_examples/catchall/sdkconfig.defaults
Normal file
12
idf_component_examples/catchall/sdkconfig.defaults
Normal 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
|
8
idf_component_examples/serversentevents/CMakeLists.txt
Normal file
8
idf_component_examples/serversentevents/CMakeLists.txt
Normal 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)
|
1
idf_component_examples/serversentevents/README.md
Normal file
1
idf_component_examples/serversentevents/README.md
Normal file
@ -0,0 +1 @@
|
||||
### Basic example to show how to use ServerSentEvents
|
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "main.cpp"
|
||||
INCLUDE_DIRS ".")
|
@ -0,0 +1,6 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
esp32async/espasyncwebserver:
|
||||
version: "*"
|
||||
override_path: "../../../"
|
||||
pre_release: true
|
95
idf_component_examples/serversentevents/main/main.cpp
Normal file
95
idf_component_examples/serversentevents/main/main.cpp
Normal 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;
|
||||
}
|
||||
}
|
12
idf_component_examples/serversentevents/sdkconfig.defaults
Normal file
12
idf_component_examples/serversentevents/sdkconfig.defaults
Normal 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
|
8
idf_component_examples/websocket/CMakeLists.txt
Normal file
8
idf_component_examples/websocket/CMakeLists.txt
Normal 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)
|
1
idf_component_examples/websocket/README.md
Normal file
1
idf_component_examples/websocket/README.md
Normal file
@ -0,0 +1 @@
|
||||
### Basic example to show how to use WebSockets
|
2
idf_component_examples/websocket/main/CMakeLists.txt
Normal file
2
idf_component_examples/websocket/main/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "main.cpp"
|
||||
INCLUDE_DIRS ".")
|
6
idf_component_examples/websocket/main/idf_component.yml
Normal file
6
idf_component_examples/websocket/main/idf_component.yml
Normal file
@ -0,0 +1,6 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
esp32async/espasyncwebserver:
|
||||
version: "*"
|
||||
override_path: "../../../"
|
||||
pre_release: true
|
102
idf_component_examples/websocket/main/main.cpp
Normal file
102
idf_component_examples/websocket/main/main.cpp
Normal 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;
|
||||
}
|
||||
}
|
12
idf_component_examples/websocket/sdkconfig.defaults
Normal file
12
idf_component_examples/websocket/sdkconfig.defaults
Normal 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
|
@ -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"
|
||||
}
|
||||
],
|
@ -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
|
||||
|
11
pioarduino_examples/IncreaseMaxSockets/.gitignore
vendored
Normal file
11
pioarduino_examples/IncreaseMaxSockets/.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
.DS_Store
|
||||
.lh
|
||||
/.pio
|
||||
/.vscode
|
||||
/logs
|
||||
|
||||
/sdkconfig.*
|
||||
/CMakeLists.txt
|
||||
/dependencies.lock
|
||||
/.dummy
|
||||
/managed_components
|
26
pioarduino_examples/IncreaseMaxSockets/platformio.ini
Normal file
26
pioarduino_examples/IncreaseMaxSockets/platformio.ini
Normal 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
|
142
pioarduino_examples/IncreaseMaxSockets/src/main.cpp
Normal file
142
pioarduino_examples/IncreaseMaxSockets/src/main.cpp
Normal 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
|
||||
}
|
104
platformio.ini
104
platformio.ini
@ -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}
|
||||
|
1
pre-commit.requirements.txt
Normal file
1
pre-commit.requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
pre-commit==4.1.0
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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_ */
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
40
src/AsyncWebServerVersion.h
Normal file
40
src/AsyncWebServerVersion.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
@ -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_ */
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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 ¶ms(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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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_ */
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user