Aktualizace na verzi 3.4.5

This commit is contained in:
Pavel Brychta 2025-01-03 16:45:58 +01:00
parent 7c828c70d8
commit 697a3adf08
17 changed files with 893 additions and 345 deletions

View File

@ -75,13 +75,13 @@ So if you need one of these feature, you will have to stick with 3.x or another
```ini
lib_compat_mode = strict
lib_ldf_mode = chain
lib_deps = mathieucarbou/ESPAsyncWebServer @ 3.3.23
lib_deps = mathieucarbou/ESPAsyncWebServer @ 3.4.5
```
**Dependencies:**
- **ESP32 with AsyncTCP**: `mathieucarbou/AsyncTCP @ 3.2.14`
Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.2.14](https://github.com/mathieucarbou/AsyncTCP/releases)
- **ESP32 with AsyncTCP**: `mathieucarbou/AsyncTCP @ 3.3.1`
Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.3.1](https://github.com/mathieucarbou/AsyncTCP/releases)
- **ESP32 with AsyncTCPSock**: `https://github.com/mathieucarbou/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip`
@ -99,9 +99,9 @@ AsyncTCPSock can be used instead of AsyncTCP by excluding AsyncTCP from the libr
lib_compat_mode = strict
lib_ldf_mode = chain
lib_deps =
; mathieucarbou/AsyncTCP @ 3.2.14
; mathieucarbou/AsyncTCP @ 3.3.1
https://github.com/mathieucarbou/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip
mathieucarbou/ESPAsyncWebServer @ 3.3.23
mathieucarbou/ESPAsyncWebServer @ 3.4.5
lib_ignore =
AsyncTCP
mathieucarbou/AsyncTCP
@ -109,14 +109,14 @@ lib_ignore =
## Performance
Performance of `mathieucarbou/ESPAsyncWebServer @ 3.3.23`:
Performance of `mathieucarbou/ESPAsyncWebServer @ 3.4.5`:
```bash
> brew install autocannon
> autocannon -c 10 -w 10 -d 20 http://192.168.4.1
```
With `mathieucarbou/AsyncTCP @ 3.2.14`
With `mathieucarbou/AsyncTCP @ 3.3.1`
[![](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10.png)](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10.png)
@ -133,29 +133,29 @@ Test is running for 20 seconds with 10 connections.
```
// With AsyncTCP, with 10 workers: no message discarded from the queue
//
// Total: 1875 events, 468.75000000000000000000 events / second
// Total: 1870 events, 467.50000000000000000000 events / second
// Total: 1871 events, 467.75000000000000000000 events / second
// Total: 1875 events, 468.75000000000000000000 events / second
// Total: 1871 events, 467.75000000000000000000 events / second
// Total: 1805 events, 451.25000000000000000000 events / second
// Total: 1803 events, 450.75000000000000000000 events / second
// Total: 1873 events, 468.25000000000000000000 events / second
// Total: 1872 events, 468.00000000000000000000 events / second
// Total: 1805 events, 451.25000000000000000000 events / second
// 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 10 workers: no message discarded from the queue
//
// Total: 1242 events, 310.50000000000000000000 events / second
// Total: 1242 events, 310.50000000000000000000 events / second
// Total: 1242 events, 310.50000000000000000000 events / second
// Total: 1242 events, 310.50000000000000000000 events / second
// Total: 1181 events, 295.25000000000000000000 events / second
// Total: 1182 events, 295.50000000000000000000 events / second
// Total: 1240 events, 310.00000000000000000000 events / second
// Total: 1181 events, 295.25000000000000000000 events / second
// Total: 1181 events, 295.25000000000000000000 events / second
// Total: 1183 events, 295.75000000000000000000 events / second
// 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
```
## Important recommendations
@ -163,27 +163,14 @@ Test is running for 20 seconds with 10 connections.
Most of the crashes are caused by improper configuration of the library for the project.
Here are some recommendations to avoid them.
1. Set the running core to be on the same core of your application (usually core 1) `-D CONFIG_ASYNC_TCP_RUNNING_CORE=1`
2. Set the stack size appropriately with `-D CONFIG_ASYNC_TCP_STACK_SIZE=16384`.
The default value of `16384` might be too much for your project.
You can look at the [MycilaTaskMonitor](https://mathieu.carbou.me/MycilaTaskMonitor) project to monitor the stack usage.
3. You can change **if you know what you are doing** the task priority with `-D CONFIG_ASYNC_TCP_PRIORITY=10`.
Default is `10`.
4. You can increase the queue size with `-D CONFIG_ASYNC_TCP_QUEUE_SIZE=128`.
Default is `64`.
5. You can decrease the maximum ack time `-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000`.
Default is `5000`.
I personally use the following configuration in my projects because my WS messages can be big (up to 4k).
If you have smaller messages, you can increase `WS_MAX_QUEUED_MESSAGES` to 128.
I personally use the following configuration in my projects:
```c++
-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000
-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
-D WS_MAX_QUEUED_MESSAGES=64
-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000 // (keep default)
-D CONFIG_ASYNC_TCP_PRIORITY=10 // (keep default)
-D CONFIG_ASYNC_TCP_QUEUE_SIZE=64 // (keep default)
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1 // force async_tcp task to be on same core as the app (default is core 0)
-D CONFIG_ASYNC_TCP_STACK_SIZE=4096 // reduce the stack size (default is 16K)
```
## `AsyncWebSocketMessageBuffer` and `makeBuffer()`

View File

@ -75,13 +75,13 @@ So if you need one of these feature, you will have to stick with 3.x or another
```ini
lib_compat_mode = strict
lib_ldf_mode = chain
lib_deps = mathieucarbou/ESPAsyncWebServer @ 3.3.23
lib_deps = mathieucarbou/ESPAsyncWebServer @ 3.4.5
```
**Dependencies:**
- **ESP32 with AsyncTCP**: `mathieucarbou/AsyncTCP @ 3.2.14`
Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.2.14](https://github.com/mathieucarbou/AsyncTCP/releases)
- **ESP32 with AsyncTCP**: `mathieucarbou/AsyncTCP @ 3.3.1`
Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.3.1](https://github.com/mathieucarbou/AsyncTCP/releases)
- **ESP32 with AsyncTCPSock**: `https://github.com/mathieucarbou/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip`
@ -99,9 +99,9 @@ AsyncTCPSock can be used instead of AsyncTCP by excluding AsyncTCP from the libr
lib_compat_mode = strict
lib_ldf_mode = chain
lib_deps =
; mathieucarbou/AsyncTCP @ 3.2.14
; mathieucarbou/AsyncTCP @ 3.3.1
https://github.com/mathieucarbou/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip
mathieucarbou/ESPAsyncWebServer @ 3.3.23
mathieucarbou/ESPAsyncWebServer @ 3.4.5
lib_ignore =
AsyncTCP
mathieucarbou/AsyncTCP
@ -109,14 +109,14 @@ lib_ignore =
## Performance
Performance of `mathieucarbou/ESPAsyncWebServer @ 3.3.23`:
Performance of `mathieucarbou/ESPAsyncWebServer @ 3.4.5`:
```bash
> brew install autocannon
> autocannon -c 10 -w 10 -d 20 http://192.168.4.1
```
With `mathieucarbou/AsyncTCP @ 3.2.14`
With `mathieucarbou/AsyncTCP @ 3.3.1`
[![](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10.png)](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10.png)
@ -133,29 +133,29 @@ Test is running for 20 seconds with 10 connections.
```
// With AsyncTCP, with 10 workers: no message discarded from the queue
//
// Total: 1875 events, 468.75000000000000000000 events / second
// Total: 1870 events, 467.50000000000000000000 events / second
// Total: 1871 events, 467.75000000000000000000 events / second
// Total: 1875 events, 468.75000000000000000000 events / second
// Total: 1871 events, 467.75000000000000000000 events / second
// Total: 1805 events, 451.25000000000000000000 events / second
// Total: 1803 events, 450.75000000000000000000 events / second
// Total: 1873 events, 468.25000000000000000000 events / second
// Total: 1872 events, 468.00000000000000000000 events / second
// Total: 1805 events, 451.25000000000000000000 events / second
// 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 10 workers: no message discarded from the queue
//
// Total: 1242 events, 310.50000000000000000000 events / second
// Total: 1242 events, 310.50000000000000000000 events / second
// Total: 1242 events, 310.50000000000000000000 events / second
// Total: 1242 events, 310.50000000000000000000 events / second
// Total: 1181 events, 295.25000000000000000000 events / second
// Total: 1182 events, 295.50000000000000000000 events / second
// Total: 1240 events, 310.00000000000000000000 events / second
// Total: 1181 events, 295.25000000000000000000 events / second
// Total: 1181 events, 295.25000000000000000000 events / second
// Total: 1183 events, 295.75000000000000000000 events / second
// 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
```
## Important recommendations
@ -163,27 +163,14 @@ Test is running for 20 seconds with 10 connections.
Most of the crashes are caused by improper configuration of the library for the project.
Here are some recommendations to avoid them.
1. Set the running core to be on the same core of your application (usually core 1) `-D CONFIG_ASYNC_TCP_RUNNING_CORE=1`
2. Set the stack size appropriately with `-D CONFIG_ASYNC_TCP_STACK_SIZE=16384`.
The default value of `16384` might be too much for your project.
You can look at the [MycilaTaskMonitor](https://mathieu.carbou.me/MycilaTaskMonitor) project to monitor the stack usage.
3. You can change **if you know what you are doing** the task priority with `-D CONFIG_ASYNC_TCP_PRIORITY=10`.
Default is `10`.
4. You can increase the queue size with `-D CONFIG_ASYNC_TCP_QUEUE_SIZE=128`.
Default is `64`.
5. You can decrease the maximum ack time `-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000`.
Default is `5000`.
I personally use the following configuration in my projects because my WS messages can be big (up to 4k).
If you have smaller messages, you can increase `WS_MAX_QUEUED_MESSAGES` to 128.
I personally use the following configuration in my projects:
```c++
-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000
-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
-D WS_MAX_QUEUED_MESSAGES=64
-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=5000 // (keep default)
-D CONFIG_ASYNC_TCP_PRIORITY=10 // (keep default)
-D CONFIG_ASYNC_TCP_QUEUE_SIZE=64 // (keep default)
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1 // force async_tcp task to be on same core as the app (default is core 0)
-D CONFIG_ASYNC_TCP_STACK_SIZE=4096 // reduce the stack size (default is 16K)
```
## `AsyncWebSocketMessageBuffer` and `makeBuffer()`
@ -622,7 +609,7 @@ Endpoints which consume JSON can use a special handler to get ready to use JSON
#include "ArduinoJson.h"
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint", [](AsyncWebServerRequest *request, JsonVariant &json) {
JsonObject& jsonObj = json.as<JsonObject>();
JsonObject jsonObj = json.as<JsonObject>();
// ...
});
server.addHandler(handler);

View File

@ -56,13 +56,23 @@ void setup() {
<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);
});
document.addEventListener("DOMContentLoaded", function () {
ws.onopen = function () {
console.log("WebSocket connected");
};
});
function closeAllWsClients() {
fetch("/close_all_ws_clients", {
method : "POST",
@ -79,6 +89,11 @@ void setup() {
server.begin();
}
uint32_t lastTime = 0;
void loop() {
vTaskDelete(NULL);
if (millis() - lastTime > 5000) {
lastTime = millis();
Serial.printf("Client count: %u\n", ws.count());
}
ws.cleanupClients();
}

View File

@ -0,0 +1,257 @@
//
// SSE server with a load generator
// it will auto adjust message push rate to minimize discards across all connected clients
// per second stats is printed to a serial console and also published as SSE ping message
// open /sse URL to start events generator
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040)
#include <WebServer.h>
#include <WiFi.h>
#endif
#include <ESPAsyncWebServer.h>
#if __has_include("ArduinoJson.h")
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <AsyncMessagePack.h>
#endif
#include <LittleFS.h>
const char* htmlContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod
rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper
arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit
accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi.
Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo
dapibus elit, id varius sem dui id lacus.</p>
</body>
</html>
)";
const char* staticContent PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, %IP%</h1>
</body>
</html>
)";
AsyncWebServer server(80);
AsyncEventSource events("/events");
/////////////////////////////////////////////////////////////////////////////////////////////////////
const char* PARAM_MESSAGE PROGMEM = "message";
const char* SSE_HTLM PROGMEM = R"(
<!DOCTYPE html>
<html>
<head>
<title>Server-Sent Events</title>
<script>
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e);
}, false);
source.addEventListener('heartbeat', function(e) {
console.log("heartbeat", e.data);
}, false);
}
</script>
</head>
<body>
<h1>Open your browser console!</h1>
</body>
</html>
)";
static const char* SSE_MSG = R"(Alice felt that this could not be denied, so she tried another question. 'What sort of people live about here?' 'In THAT direction,' the Cat said, waving its right paw round, 'lives a Hatter: and in THAT direction,' waving the other paw, 'lives a March Hare. Visit either you like: they're both mad.'
'But I don't want to go among mad people,' Alice remarked. 'Oh, you can't help that,' said the Cat: 'we're all mad here. I'm mad. You're mad.' 'How do you know I'm mad?' said Alice.
'You must be,' said the Cat, `or you wouldn't have come here.' Alice didn't think that proved it at all; however, she went on 'And how do you know that you're mad?' 'To begin with,' said the Cat, 'a dog's not mad. You grant that?'
)";
void notFound(AsyncWebServerRequest* request) {
request->send(404, "text/plain", "Not found");
}
static const char characters[] = "0123456789ABCDEF";
static size_t charactersIndex = 0;
void setup() {
Serial.begin(115200);
#ifndef CONFIG_IDF_TARGET_ESP32H2
/*
WiFi.mode(WIFI_STA);
WiFi.begin("SSID", "passwd");
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.printf("WiFi Failed!\n");
return;
}
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
*/
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/html", staticContent);
});
events.onConnect([](AsyncEventSourceClient* client) {
if (client->lastId()) {
Serial.printf("SSE Client reconnected! Last message ID that it gat is: %" PRIu32 "\n", client->lastId());
}
client->send("hello!", NULL, millis(), 1000);
});
server.on("/sse", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/html", SSE_HTLM);
});
// go to http://192.168.4.1/sse
server.addHandler(&events);
server.onNotFound(notFound);
server.begin();
}
uint32_t lastSSE = 0;
uint32_t deltaSSE = 25;
uint32_t messagesSSE = 4; // how many messages to q each time
uint32_t sse_disc{0}, sse_enq{0}, sse_penq{0}, sse_second{0};
AsyncEventSource::SendStatus enqueue() {
AsyncEventSource::SendStatus state = events.send(SSE_MSG, "message");
if (state == AsyncEventSource::SendStatus::DISCARDED)
++sse_disc;
else if (state == AsyncEventSource::SendStatus::ENQUEUED) {
++sse_enq;
} else
++sse_penq;
return state;
}
void loop() {
uint32_t now = millis();
if (now - lastSSE >= deltaSSE) {
// enqueue messages
for (uint32_t i = 0; i != messagesSSE; ++i) {
auto err = enqueue();
if (err == AsyncEventSource::SendStatus::DISCARDED || err == AsyncEventSource::SendStatus::PARTIALLY_ENQUEUED) {
// throttle messaging a bit
lastSSE = now + deltaSSE;
break;
}
}
lastSSE = millis();
}
if (now - sse_second > 1000) {
String s;
s.reserve(100);
s = "Ping:";
s += now / 1000;
s += " clients:";
s += events.count();
s += " disc:";
s += sse_disc;
s += " enq:";
s += sse_enq;
s += " partial:";
s += sse_penq;
s += " avg wait:";
s += events.avgPacketsWaiting();
s += " heap:";
s += ESP.getFreeHeap() / 1024;
events.send(s, "heartbeat", now);
Serial.println();
Serial.println(s);
// if we see discards or partial enqueues, let's decrease message rate, else - increase. So that we can come to a max sustained message rate
if (sse_disc || sse_penq)
++deltaSSE;
else if (deltaSSE > 5)
--deltaSSE;
sse_disc = sse_enq = sse_penq = 0;
sse_second = now;
}
}

View File

@ -147,6 +147,8 @@ AsyncMiddlewareFunction complexAuth([](AsyncWebServerRequest* request, ArMiddlew
AuthorizationMiddleware authz([](AsyncWebServerRequest* request) { return request->getAttribute("role") == "staff"; });
int wsClients = 0;
/////////////////////////////////////////////////////////////////////////////////////////////////////
const char* PARAM_MESSAGE PROGMEM = "message";
@ -407,6 +409,7 @@ void setup() {
// 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);
});
@ -503,6 +506,29 @@ void setup() {
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
@ -622,10 +648,14 @@ void setup() {
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");
@ -651,59 +681,78 @@ void setup() {
//
// 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 Too many messages queued: deleting message
// With AsyncTCP, with 16 workers: a lot of "Event message queue overflow: discard message", no crash
//
// Total: 119 events, 29.75000000000000000000 events / second
// Total: 727 events, 181.75000000000000000000 events / second
// Total: 1386 events, 346.50000000000000000000 events / second
// Total: 1385 events, 346.25000000000000000000 events / second
// Total: 1276 events, 319.00000000000000000000 events / second
// Total: 1411 events, 352.75000000000000000000 events / second
// Total: 1276 events, 319.00000000000000000000 events / second
// Total: 1333 events, 333.25000000000000000000 events / second
// Total: 1250 events, 312.50000000000000000000 events / second
// Total: 1275 events, 318.75000000000000000000 events / second
// Total: 1271 events, 317.75000000000000000000 events / second
// Total: 1271 events, 317.75000000000000000000 events / second
// Total: 1254 events, 313.50000000000000000000 events / second
// Total: 1251 events, 312.75000000000000000000 events / second
// Total: 1254 events, 313.50000000000000000000 events / second
// Total: 1262 events, 315.50000000000000000000 events / second
// 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: 1875 events, 468.75000000000000000000 events / second
// Total: 1870 events, 467.50000000000000000000 events / second
// Total: 1871 events, 467.75000000000000000000 events / second
// Total: 1875 events, 468.75000000000000000000 events / second
// Total: 1871 events, 467.75000000000000000000 events / second
// Total: 1805 events, 451.25000000000000000000 events / second
// Total: 1803 events, 450.75000000000000000000 events / second
// Total: 1873 events, 468.25000000000000000000 events / second
// Total: 1872 events, 468.00000000000000000000 events / second
// Total: 1805 events, 451.25000000000000000000 events / second
// 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.50000000000000000000 events / second
// Total: 1242 events, 310.50000000000000000000 events / second
// Total: 1242 events, 310.50000000000000000000 events / second
// Total: 1242 events, 310.50000000000000000000 events / second
// Total: 1181 events, 295.25000000000000000000 events / second
// Total: 1182 events, 295.50000000000000000000 events / second
// Total: 1240 events, 310.00000000000000000000 events / second
// Total: 1181 events, 295.25000000000000000000 events / second
// Total: 1181 events, 295.25000000000000000000 events / second
// Total: 1183 events, 295.75000000000000000000 events / second
// 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: websocat ws://192.168.4.1/ws
server.addHandler(&ws);
// 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);
@ -721,6 +770,8 @@ 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) {
@ -729,9 +780,15 @@ void loop() {
}
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));
}
// for (auto& client : ws.getClients()) {
// client.printf("kp%.4f", (10.0 / 3.0));
// }
lastWS = millis();
}
#ifdef ESP32
if (now - lastHeap >= 2000) {
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
lastHeap = now;
}
#endif
}

View File

@ -1,6 +1,6 @@
{
"name": "ESPAsyncWebServer",
"version": "3.3.23",
"version": "3.4.5",
"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/mathieucarbou/ESPAsyncWebServer",
@ -28,7 +28,7 @@
{
"owner": "mathieucarbou",
"name": "AsyncTCP",
"version": "^3.2.14",
"version": "^3.3.1",
"platforms": "espressif32"
},
{

View File

@ -1,6 +1,6 @@
name=ESP Async WebServer
includes=ESPAsyncWebServer.h
version=3.3.23
version=3.4.5
author=Me-No-Dev
maintainer=Mathieu Carbou <mathieu.carbou@gmail.com>
sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040

View File

@ -2,11 +2,11 @@
default_envs = arduino-2, arduino-3, arduino-310, esp8266, raspberrypi
lib_dir = .
; src_dir = examples/CaptivePortal
; src_dir = examples/SimpleServer
src_dir = examples/SimpleServer
; src_dir = examples/StreamFiles
; src_dir = examples/Filters
; src_dir = examples/Issue85
src_dir = examples/Issue162
; src_dir = examples/Issue162
[env]
framework = arduino
@ -14,11 +14,11 @@ build_flags =
-Og
-Wall -Wextra
-Wno-unused-parameter
-D CONFIG_ARDUHAL_LOG_COLORS
; -D CONFIG_ARDUHAL_LOG_COLORS
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000
-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_QUEUE_SIZE=64
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1
-D CONFIG_ASYNC_TCP_STACK_SIZE=4096
upload_protocol = esptool
@ -31,7 +31,7 @@ lib_deps =
; bblanchon/ArduinoJson @ 5.13.4
; bblanchon/ArduinoJson @ 6.21.5
bblanchon/ArduinoJson @ 7.2.1
mathieucarbou/AsyncTCP @ 3.2.14
mathieucarbou/AsyncTCP @ 3.3.1
board = esp32dev
board_build.partitions = partitions-4MB.csv
board_build.filesystem = littlefs
@ -49,21 +49,21 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/
; board = esp32-s3-devkitc-1
; board = esp32-c6-devkitc-1
lib_deps =
mathieucarbou/AsyncTCP @ 3.2.14
mathieucarbou/AsyncTCP @ 3.3.1
[env:arduino-310]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc3/platform-espressif32.zip
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10/platform-espressif32.zip
; board = esp32-s3-devkitc-1
; board = esp32-c6-devkitc-1
; board = esp32-h2-devkitm-1
[env:perf-test-AsyncTCP]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc3/platform-espressif32.zip
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10/platform-espressif32.zip
build_flags = ${env.build_flags}
-D PERF_TEST=1
[env:perf-test-AsyncTCPSock]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc3/platform-espressif32.zip
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10/platform-espressif32.zip
lib_deps =
https://github.com/mathieucarbou/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip
build_flags = ${env.build_flags}
@ -102,10 +102,10 @@ board = ${sysenv.PIO_BOARD}
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
board = ${sysenv.PIO_BOARD}
lib_deps =
mathieucarbou/AsyncTCP @ 3.2.14
mathieucarbou/AsyncTCP @ 3.3.1
[env:ci-arduino-310]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc3/platform-espressif32.zip
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10/platform-espressif32.zip
board = ${sysenv.PIO_BOARD}
[env:ci-esp8266]

View File

@ -23,116 +23,99 @@
#endif
#include "AsyncEventSource.h"
#define ASYNC_SSE_NEW_LINE_CHAR (char)0xa
using namespace asyncsrv;
static String generateEventMessage(const char* message, const char* event, uint32_t id, uint32_t reconnect) {
String ev;
String str;
size_t len{0};
if (message)
len += strlen(message);
if (event)
len += strlen(event);
len += 42; // give it some overhead
str.reserve(len);
if (reconnect) {
ev += T_retry_;
ev += reconnect;
ev += T_rn;
str += T_retry_;
str += reconnect;
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
}
if (id) {
ev += T_id__;
ev += id;
ev += T_rn;
str += T_id__;
str += id;
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
}
if (event != NULL) {
ev += T_event_;
ev += event;
ev += T_rn;
str += T_event_;
str += event;
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
}
if (message != NULL) {
size_t messageLen = strlen(message);
char* lineStart = (char*)message;
char* lineEnd;
do {
char* nextN = strchr(lineStart, '\n');
char* nextR = strchr(lineStart, '\r');
if (nextN == NULL && nextR == NULL) {
size_t llen = ((char*)message + messageLen) - lineStart;
char* ldata = (char*)malloc(llen + 1);
if (ldata != NULL) {
memcpy(ldata, lineStart, llen);
ldata[llen] = 0;
ev += T_data_;
ev += ldata;
ev += T_rnrn;
free(ldata);
}
lineStart = (char*)message + messageLen;
if (!message)
return str;
size_t messageLen = strlen(message);
char* lineStart = (char*)message;
char* lineEnd;
do {
char* nextN = strchr(lineStart, '\n');
char* nextR = strchr(lineStart, '\r');
if (nextN == NULL && nextR == NULL) {
// a message is a single-line string
str += T_data_;
str += message;
str += T_nn;
return str;
}
// a message is a multi-line string
char* nextLine = NULL;
if (nextN != NULL && nextR != NULL) { // windows line-ending \r\n
if (nextR + 1 == nextN) {
// normal \r\n sequense
lineEnd = nextR;
nextLine = nextN + 1;
} else {
char* nextLine = NULL;
if (nextN != NULL && nextR != NULL) {
if (nextR < nextN) {
lineEnd = nextR;
if (nextN == (nextR + 1))
nextLine = nextN + 1;
else
nextLine = nextR + 1;
} else {
lineEnd = nextN;
if (nextR == (nextN + 1))
nextLine = nextR + 1;
else
nextLine = nextN + 1;
}
} else if (nextN != NULL) {
lineEnd = nextN;
nextLine = nextN + 1;
} else {
lineEnd = nextR;
nextLine = nextR + 1;
}
size_t llen = lineEnd - lineStart;
char* ldata = (char*)malloc(llen + 1);
if (ldata != NULL) {
memcpy(ldata, lineStart, llen);
ldata[llen] = 0;
ev += T_data_;
ev += ldata;
ev += T_rn;
free(ldata);
}
lineStart = nextLine;
if (lineStart == ((char*)message + messageLen))
ev += T_rn;
// some abnormal \n \r mixed sequence
lineEnd = std::min(nextR, nextN);
nextLine = lineEnd + 1;
}
} while (lineStart < ((char*)message + messageLen));
}
} else if (nextN != NULL) { // Unix/Mac OS X LF
lineEnd = nextN;
nextLine = nextN + 1;
} else { // some ancient garbage
lineEnd = nextR;
nextLine = nextR + 1;
}
return ev;
str += T_data_;
str.concat(lineStart, lineEnd - lineStart);
str += ASYNC_SSE_NEW_LINE_CHAR; // \n
lineStart = nextLine;
} while (lineStart < ((char*)message + messageLen));
// append another \n to terminate message
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
return str;
}
// Message
AsyncEventSourceMessage::AsyncEventSourceMessage(const char* data, size_t len)
: _data(nullptr), _len(len), _sent(0), _acked(0) {
_data = (uint8_t*)malloc(_len + 1);
if (_data == nullptr) {
_len = 0;
} else {
memcpy(_data, data, len);
_data[_len] = 0;
}
}
AsyncEventSourceMessage::~AsyncEventSourceMessage() {
if (_data != NULL)
free(_data);
}
size_t AsyncEventSourceMessage::ack(size_t len, __attribute__((unused)) uint32_t time) {
// If the whole message is now acked...
if (_acked + len > _len) {
if (_acked + len > _data->length()) {
// Return the number of extra bytes acked (they will be carried on to the next message)
const size_t extra = _acked + len - _len;
_acked = _len;
const size_t extra = _acked + len - _data->length();
_acked = _data->length();
return extra;
}
// Return that no extra bytes left.
@ -144,13 +127,25 @@ size_t AsyncEventSourceMessage::write(AsyncClient* client) {
if (!client)
return 0;
if (_sent >= _len || !client->canSend()) {
if (_sent >= _data->length() || !client->canSend()) {
return 0;
}
size_t len = min(_len - _sent, client->space());
size_t sent = client->add((const char*)_data + _sent, len);
_sent += sent;
return sent;
size_t len = std::min(_data->length() - _sent, client->space());
/*
add() would call lwip's tcp_write() under the AsyncTCP hood with apiflags argument.
By default apiflags=ASYNC_WRITE_FLAG_COPY
we could have used apiflags with this flag unset to pass data by reference and avoid copy to socket buffer,
but looks like it does not work for Arduino's lwip in ESP32/IDF
it is enforced in https://github.com/espressif/esp-lwip/blob/0606eed9d8b98a797514fdf6eabb4daf1c8c8cd9/src/core/tcp_out.c#L422C5-L422C30
if LWIP_NETIF_TX_SINGLE_PBUF is set, and it is set indeed in IDF
https://github.com/espressif/esp-idf/blob/a0f798cfc4bbd624aab52b2c194d219e242d80c1/components/lwip/port/include/lwipopts.h#L744
So let's just keep it enforced ASYNC_WRITE_FLAG_COPY and keep in mind that there is no zero-copy
*/
size_t written = client->add(_data->c_str() + _sent, len, ASYNC_WRITE_FLAG_COPY); // ASYNC_WRITE_FLAG_MORE
_sent += written;
return written;
}
size_t AsyncEventSourceMessage::send(AsyncClient* client) {
@ -160,20 +155,19 @@ size_t AsyncEventSourceMessage::send(AsyncClient* client) {
// Client
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server) {
_client = request->client();
_server = server;
_lastId = 0;
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server)
: _client(request->client()), _server(server) {
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; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this);
_client->onPoll([](void* r, AsyncClient* c) { (void)c; ((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) { ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this);
_client->onDisconnect([this](void* r, AsyncClient* c) { ((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;
@ -190,29 +184,61 @@ AsyncEventSourceClient::~AsyncEventSourceClient() {
}
bool AsyncEventSourceClient::_queueMessage(const char* message, size_t len) {
if (!_client)
if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
#ifdef ESP8266
ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
#elif defined(ESP32)
log_e("Event message queue overflow: discard message");
#endif
return false;
}
#ifdef ESP32
// length() is not thread-safe, thus acquiring the lock before this call..
std::lock_guard<std::mutex> lock(_lockmq);
#endif
_messageQueue.emplace_back(message, len);
/*
throttle queue run
if Q is filled for >25% then network/CPU is congested, since there is no zero-copy mode for socket buff
forcing Q run will only eat more heap ram and blow the buffer, let's just keep data in our own queue
the queue will be processed at least on each onAck()/onPoll() call from AsyncTCP
*/
if (_messageQueue.size() < SSE_MAX_QUEUED_MESSAGES >> 2 && _client->canSend()) {
_runQueue();
}
return true;
}
bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t&& msg) {
if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
#ifdef ESP8266
ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
#elif defined(ESP32)
log_e("Too many messages queued: deleting message");
log_e("Event message queue overflow: discard message");
#endif
return false;
}
_messageQueue.emplace_back(message, len);
// runqueue trigger when new messages added
if (_client->canSend()) {
#ifdef ESP32
// length() is not thread-safe, thus acquiring the lock before this call..
std::lock_guard<std::mutex> lock(_lockmq);
#endif
_messageQueue.emplace_back(std::move(msg));
/*
throttle queue run
if Q is filled for >25% then network/CPU is congested, since there is no zero-copy mode for socket buff
forcing Q run will only eat more heap ram and blow the buffer, let's just keep data in our own queue
the queue will be processed at least on each onAck()/onPoll() call from AsyncTCP
*/
if (_messageQueue.size() < SSE_MAX_QUEUED_MESSAGES >> 2 && _client->canSend()) {
_runQueue();
}
return true;
}
@ -221,15 +247,33 @@ void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t
// Same here, acquiring the lock early
std::lock_guard<std::mutex> lock(_lockmq);
#endif
_runQueue();
// adjust in-flight len
if (len < _inflight)
_inflight -= len;
else
_inflight = 0;
// acknowledge as much messages's data as we got confirmed len from a AsyncTCP
while (len && _messageQueue.size()) {
len = _messageQueue.front().ack(len);
if (_messageQueue.front().finished()) {
// now we could release full ack'ed messages, we were keeping it unless send confirmed from AsyncTCP
_messageQueue.pop_front();
}
}
// try to send another batch of data
if (_messageQueue.size())
_runQueue();
}
void AsyncEventSourceClient::_onPoll() {
#ifdef ESP32
// Same here, acquiring the lock early
std::lock_guard<std::mutex> lock(_lockmq);
#endif
if (_messageQueue.size()) {
#ifdef ESP32
// Same here, acquiring the lock early
std::lock_guard<std::mutex> lock(_lockmq);
#endif
_runQueue();
}
}
@ -251,50 +295,42 @@ void AsyncEventSourceClient::close() {
_client->close();
}
bool AsyncEventSourceClient::write(const char* message, size_t len) {
return connected() && _queueMessage(message, len);
}
bool AsyncEventSourceClient::send(const char* message, const char* event, uint32_t id, uint32_t reconnect) {
if (!connected())
return false;
String ev = generateEventMessage(message, event, id, reconnect);
return _queueMessage(ev.c_str(), ev.length());
}
size_t AsyncEventSourceClient::packetsWaiting() const {
#ifdef ESP32
std::lock_guard<std::mutex> lock(_lockmq);
#endif
return _messageQueue.size();
return _queueMessage(std::make_shared<String>(generateEventMessage(message, event, id, reconnect)));
}
void AsyncEventSourceClient::_runQueue() {
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;
for (auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i) {
if (!i->sent()) {
const size_t bytes_written = i->write(_client);
total_bytes_written += bytes_written;
if (bytes_written == 0)
_inflight += bytes_written;
if (bytes_written == 0 || _inflight > _max_inflight) {
// Serial.print("_");
break;
}
}
}
if (total_bytes_written > 0)
// flush socket
if (total_bytes_written)
_client->send();
size_t len = total_bytes_written;
while (len && _messageQueue.size()) {
len = _messageQueue.front().ack(len);
if (_messageQueue.front().finished()) {
_messageQueue.pop_front();
}
}
}
void AsyncEventSourceClient::set_max_inflight_bytes(size_t value) {
if (value >= SSE_MIN_INFLIGH && value <= SSE_MAX_INFLIGH)
_max_inflight = value;
}
/* AsyncEventSource */
void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) {
AuthorizationMiddleware* m = new AuthorizationMiddleware(401, cb);
m->_freeOnRemoval = true;
@ -310,18 +346,21 @@ void AsyncEventSource::_addClient(AsyncEventSourceClient* client) {
_clients.emplace_back(client);
if (_connectcb)
_connectcb(client);
_adjust_inflight_window();
}
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient* client) {
if (_disconnectcb)
_disconnectcb(client);
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
if (_disconnectcb)
_disconnectcb(client);
for (auto i = _clients.begin(); i != _clients.end(); ++i) {
if (i->get() == client)
_clients.erase(i);
}
_adjust_inflight_window();
}
void AsyncEventSource::close() {
@ -358,14 +397,14 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
AsyncEventSource::SendStatus AsyncEventSource::send(
const char* message, const char* event, uint32_t id, uint32_t reconnect) {
String ev = generateEventMessage(message, event, id, 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);
#endif
size_t hits = 0;
size_t miss = 0;
for (const auto& c : _clients) {
if (c->write(ev.c_str(), ev.length()))
if (c->write(shared_msg))
++hits;
else
++miss;
@ -393,7 +432,16 @@ void AsyncEventSource::handleRequest(AsyncWebServerRequest* request) {
request->send(new AsyncEventSourceResponse(this));
}
// Response
void AsyncEventSource::_adjust_inflight_window() {
if (_clients.size()) {
size_t inflight = SSE_MAX_INFLIGH / _clients.size();
for (const auto& c : _clients)
c->set_max_inflight_bytes(inflight);
// Serial.printf("adjusted inflight to: %u\n", inflight);
}
}
/* Response */
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource* server) {
_server = server;

View File

@ -21,22 +21,29 @@
#define ASYNCEVENTSOURCE_H_
#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <mutex>
#ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 32
#endif
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
#define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q
#elif defined(ESP8266)
#include <ESPAsyncTCP.h>
#ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 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>
#ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 32
#endif
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
#define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q
#endif
#include <ESPAsyncWebServer.h>
@ -53,58 +60,155 @@ class AsyncEventSourceResponse;
class AsyncEventSourceClient;
using ArEventHandlerFunction = std::function<void(AsyncEventSourceClient* client)>;
using ArAuthorizeConnectHandler = ArAuthorizeFunction;
// shared message object container
using AsyncEvent_SharedData_t = std::shared_ptr<String>;
/**
* @brief Async Event Message container with shared message content data
*
*/
class AsyncEventSourceMessage {
private:
uint8_t* _data;
size_t _len;
size_t _sent;
// size_t _ack;
size_t _acked;
const AsyncEvent_SharedData_t _data;
size_t _sent{0}; // num of bytes already sent
size_t _acked{0}; // num of bytes acked
public:
AsyncEventSourceMessage(const char* data, size_t len);
~AsyncEventSourceMessage();
AsyncEventSourceMessage(AsyncEvent_SharedData_t data) : _data(data) {};
#ifdef ESP32
AsyncEventSourceMessage(const char* data, size_t len) : _data(std::make_shared<String>(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); };
#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 time
* @return size_t number of extra bytes carried over
*/
size_t ack(size_t len, uint32_t time = 0);
/**
* @brief write message data to client's buffer
* @note this method does NOT call client's send
*
* @param client
* @return size_t number of bytes written
*/
size_t write(AsyncClient* client);
/**
* @brief writes message data to client's buffer and calls client's send method
*
* @param client
* @return size_t returns num of bytes the clien was able to send()
*/
size_t send(AsyncClient* client);
bool finished() { return _acked == _len; }
bool sent() { return _sent == _len; }
// returns true if full message's length were acked
bool finished() { return _acked == _data->length(); }
/**
* @brief returns true if all data has been sent already
*
*/
bool sent() { return _sent == _data->length(); }
};
/**
* @brief class holds a sse messages queue for a particular client's connection
*
*/
class AsyncEventSourceClient {
private:
AsyncClient* _client;
AsyncEventSource* _server;
uint32_t _lastId;
uint32_t _lastId{0};
size_t _inflight{0}; // num of unacknowledged bytes that has been written to socket buffer
size_t _max_inflight{SSE_MAX_INFLIGH}; // max num of unacknowledged bytes that could be written to socket buffer
std::list<AsyncEventSourceMessage> _messageQueue;
#ifdef ESP32
mutable std::mutex _lockmq;
#endif
bool _queueMessage(const char* message, size_t len);
bool _queueMessage(AsyncEvent_SharedData_t&& msg);
void _runQueue();
public:
AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server);
~AsyncEventSourceClient();
AsyncClient* client() { return _client; }
void close();
bool write(const char* message, size_t len);
/**
* @brief Send an SSE message to client
* it will craft an SSE message and place it to client's message queue
*
* @param message body string, could be single or multi-line string sepprated by \n, \r, \r\n
* @param event body string, a sinle line string
* @param id sequence id
* @param reconnect client's reconnect timeout
* @return true if message was placed in a queue
* @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 char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
/**
* @brief place supplied preformatted SSE message to the message queue
* @note message must a properly formatted SSE string according to https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
*
* @param message data
* @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)); };
[[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); };
// 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;
size_t packetsWaiting() const { return _messageQueue.size(); };
// system callbacks (do not call)
/**
* @brief Sets max amount of bytes that could be written to client's socket while awaiting delivery acknowledge
* used to throttle message delivery length to tradeoff memory consumption
* @note actual amount of data written could possible be a bit larger but no more than available socket buff space
*
* @param value
*/
void set_max_inflight_bytes(size_t value);
/**
* @brief Get current max inflight bytes value
*
* @return size_t
*/
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);
void _onPoll();
void _onTimeout(uint32_t time);
void _onDisconnect();
};
/**
* @brief a class that maintains all connected HTTP clients subscribed to SSE delivery
* dispatches supplied messages to the client's queues
*
*/
class AsyncEventSource : public AsyncWebHandler {
private:
String _url;
@ -117,6 +221,9 @@ class AsyncEventSource : public AsyncWebHandler {
ArEventHandlerFunction _connectcb = nullptr;
ArEventHandlerFunction _disconnectcb = nullptr;
// this method manipulates in-fligh data size for connected client depending on number of active connections
void _adjust_inflight_window();
public:
typedef enum {
DISCARDED = 0,
@ -124,23 +231,47 @@ class AsyncEventSource : public AsyncWebHandler {
PARTIALLY_ENQUEUED = 2,
} SendStatus;
AsyncEventSource(const char* url) : _url(url) {};
AsyncEventSource(const String& url) : _url(url) {};
~AsyncEventSource() { close(); };
const char* url() const { return _url.c_str(); }
// close all connected clients
void close();
/**
* @brief set on-connect callback for the client
* used to deliver messages to client on first connect
*
* @param cb
*/
void onConnect(ArEventHandlerFunction cb) { _connectcb = cb; }
/**
* @brief Send an SSE message to client
* it will craft an SSE message and place it to all connected client's message queues
*
* @param message body string, could be single or multi-line string sepprated by \n, \r, \r\n
* @param event body string, a sinle line string
* @param id sequence id
* @param reconnect client's reconnect timeout
* @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); }
// 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 authorizeConnect(ArAuthorizeConnectHandler cb);
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 char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
// number of clients connected
// returns number of connected clients
size_t count() const;
// returns average number of messages pending in all client's queues
size_t avgPacketsWaiting() const;
// system callbacks (do not call)
// system callbacks (do not call from user code!)
void _addClient(AsyncEventSourceClient* client);
void _handleDisconnect(AsyncEventSourceClient* client);
bool canHandle(AsyncWebServerRequest* request) const override final;
@ -149,7 +280,6 @@ class AsyncEventSource : public AsyncWebHandler {
class AsyncEventSourceResponse : public AsyncWebServerResponse {
private:
String _content;
AsyncEventSource* _server;
public:

View File

@ -287,7 +287,6 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest* request, Async
_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);
_server->_handleEvent(this, WS_EVT_CONNECT, request, NULL, 0);
delete request;
memset(&_pinfo, 0, sizeof(_pinfo));
}
@ -451,6 +450,8 @@ void AsyncWebSocketClient::close(uint16_t code, const char* message) {
if (_status != WS_CONNECTED)
return;
_status = WS_DISCONNECTING;
if (code) {
uint8_t packetLen = 2;
if (message != NULL) {
@ -496,30 +497,37 @@ void AsyncWebSocketClient::_onDisconnect() {
}
void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
// Serial.println("onData");
_lastMessageTime = millis();
uint8_t* data = (uint8_t*)pbuf;
while (plen > 0) {
if (!_pstate) {
const uint8_t* fdata = data;
_pinfo.index = 0;
_pinfo.final = (fdata[0] & 0x80) != 0;
_pinfo.opcode = fdata[0] & 0x0F;
_pinfo.masked = (fdata[1] & 0x80) != 0;
_pinfo.len = fdata[1] & 0x7F;
// log_d("WS[%" PRIu32 "]: _onData: %" PRIu32, _clientId, plen);
// log_d("WS[%" PRIu32 "]: _status = %" PRIu32, _clientId, _status);
// log_d("WS[%" PRIu32 "]: _pinfo: index: %" PRIu64 ", final: %" PRIu8 ", opcode: %" PRIu8 ", masked: %" PRIu8 ", len: %" PRIu64, _clientId, _pinfo.index, _pinfo.final, _pinfo.opcode, _pinfo.masked, _pinfo.len);
data += 2;
plen -= 2;
if (_pinfo.len == 126) {
if (_pinfo.len == 126 && plen >= 2) {
_pinfo.len = fdata[3] | (uint16_t)(fdata[2]) << 8;
data += 2;
plen -= 2;
} else if (_pinfo.len == 127) {
} 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;
data += 8;
plen -= 8;
}
if (_pinfo.masked) {
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;
@ -772,6 +780,7 @@ void AsyncWebSocket::_handleEvent(AsyncWebSocketClient* client, AwsEventType typ
AsyncWebSocketClient* AsyncWebSocket::_newClient(AsyncWebServerRequest* request) {
_clients.emplace_back(request, this);
_handleEvent(&_clients.back(), WS_EVT_CONNECT, request, NULL, 0);
return &_clients.back();
}

View File

@ -48,10 +48,10 @@
#include "literals.h"
#define ASYNCWEBSERVER_VERSION "3.3.23"
#define ASYNCWEBSERVER_VERSION "3.4.5"
#define ASYNCWEBSERVER_VERSION_MAJOR 3
#define ASYNCWEBSERVER_VERSION_MINOR 3
#define ASYNCWEBSERVER_VERSION_REVISION 23
#define ASYNCWEBSERVER_VERSION_MINOR 4
#define ASYNCWEBSERVER_VERSION_REVISION 5
#define ASYNCWEBSERVER_FORK_mathieucarbou
#ifdef ASYNCWEBSERVER_REGEX

View File

@ -28,14 +28,14 @@
using namespace asyncsrv;
enum { PARSE_REQ_START,
PARSE_REQ_HEADERS,
PARSE_REQ_BODY,
PARSE_REQ_END,
PARSE_REQ_FAIL };
enum { PARSE_REQ_START = 0,
PARSE_REQ_HEADERS = 1,
PARSE_REQ_BODY = 2,
PARSE_REQ_END = 3,
PARSE_REQ_FAIL = 4 };
AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c)
: _client(c), _server(s), _handler(NULL), _response(NULL), _temp(), _parseState(0), _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) {
: _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);
@ -67,6 +67,18 @@ AsyncWebServerRequest::~AsyncWebServerRequest() {
}
void AsyncWebServerRequest::_onData(void* buf, size_t len) {
// SSL/TLS handshake detection
#ifndef ASYNC_TCP_SSL_ENABLED
if (_parseState == PARSE_REQ_START && len && ((uint8_t*)buf)[0] == 0x16) { // 0x16 indicates a Handshake message (SSL/TLS).
#ifdef ESP32
log_d("SSL/TLS handshake detected: resetting connection");
#endif
_parseState = PARSE_REQ_FAIL;
_client->abort();
return;
}
#endif
size_t i = 0;
while (true) {
@ -74,6 +86,12 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
// Find new line in buf
char* str = (char*)buf;
for (i = 0; i < len; i++) {
// Check for null characters in header
if (!str[i]) {
_parseState = PARSE_REQ_FAIL;
_client->abort();
return;
}
if (str[i] == '\n') {
break;
}
@ -142,6 +160,8 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
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;
@ -246,6 +266,8 @@ bool AsyncWebServerRequest::_parseReqHead() {
_method = HTTP_HEAD;
} else if (m == T_OPTIONS) {
_method = HTTP_OPTIONS;
} else {
return false;
}
String g;
@ -257,6 +279,9 @@ bool AsyncWebServerRequest::_parseReqHead() {
_url = urlDecode(u);
_addGetParams(g);
if (!_url.length())
return false;
if (!_temp.startsWith(T_HTTP_1_0))
_version = 1;
@ -564,10 +589,14 @@ void AsyncWebServerRequest::_parseLine() {
if (_parseState == PARSE_REQ_START) {
if (!_temp.length()) {
_parseState = PARSE_REQ_FAIL;
_client->close();
_client->abort();
} else {
_parseReqHead();
_parseState = PARSE_REQ_HEADERS;
if (_parseReqHead()) {
_parseState = PARSE_REQ_HEADERS;
} else {
_parseState = PARSE_REQ_FAIL;
_client->abort();
}
}
return;
}
@ -589,6 +618,8 @@ void AsyncWebServerRequest::_parseLine() {
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;
@ -765,14 +796,6 @@ void AsyncWebServerRequest::send(AsyncWebServerResponse* response) {
if (_response)
delete _response;
_response = response;
if (_response == NULL) {
_client->close(true);
_onDisconnect();
_sent = true;
return;
}
if (!_response->_sourceValid())
send(500);
}
void AsyncWebServerRequest::redirect(const char* url, int code) {

View File

@ -47,6 +47,10 @@ class AsyncBasicResponse : public AsyncWebServerResponse {
class AsyncAbstractResponse : public AsyncWebServerResponse {
private:
// amount of responce 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};
String _head;
// Data is inserted into cache at begin().
// This is inefficient with vector, but if we use some other container,

View File

@ -352,7 +352,21 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
request->client()->close();
return 0;
}
// return a credit for each chunk of acked data (polls does not give any credits)
if (len)
++_in_flight_credit;
// for chunked responses ignore acks if there are no _in_flight_credits left
if (_chunked && !_in_flight_credit) {
#ifdef ESP32
log_d("(chunk) out of in-flight credits");
#endif
return 0;
}
_ackedLength += len;
_in_flight -= (_in_flight > len) ? len : _in_flight;
// get the size of available sock space
size_t space = request->client()->space();
size_t headLen = _head.length();
@ -364,16 +378,31 @@ 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());
_in_flight += out.length();
--_in_flight_credit; // take a credit
return out.length();
}
}
if (_state == RESPONSE_CONTENT) {
// 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.
// That way we could balance on having half the buffer in-flight while another half is filling up, while minimizing events in asynctcp q
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)
--_in_flight_credit;
return 0;
}
size_t outLen;
if (_chunked) {
if (space <= 8) {
return 0;
}
outLen = space;
} else if (!_sendContentLength) {
outLen = space;
@ -422,6 +451,8 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
if (outLen) {
_writtenLength += request->client()->write((const char*)buf, outLen);
_in_flight += outLen;
--_in_flight_credit; // take a credit
}
if (_chunked) {

View File

@ -56,8 +56,7 @@ AsyncWebServer::AsyncWebServer(uint16_t port)
c->setRxTimeout(3);
AsyncWebServerRequest* r = new AsyncWebServerRequest((AsyncWebServer*)s, c);
if (r == NULL) {
c->close(true);
c->free();
c->abort();
delete c;
}
},

View File

@ -65,6 +65,7 @@ namespace asyncsrv {
static constexpr const char* T_response = "response";
static constexpr const char* T_retry_ = "retry: ";
static constexpr const char* T_retry_after = "retry-after";
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_Transfer_Encoding = "transfer-encoding";