From 01c691b36a7df8616fa9647364b759dfdb696999 Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Mon, 14 Oct 2024 00:05:31 +0200 Subject: [PATCH] AsyncTCPSock support --- README.md | 32 ++++++++++++++++++-------------- docs/index.md | 32 ++++++++++++++++++-------------- docs/perf-c10-asynctcpsock.png | Bin 0 -> 310017 bytes platformio.ini | 9 ++++++++- 4 files changed, 44 insertions(+), 29 deletions(-) create mode 100644 docs/perf-c10-asynctcpsock.png diff --git a/README.md b/README.md index e1bfd33..4336c10 100644 --- a/README.md +++ b/README.md @@ -17,18 +17,18 @@ This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubo - [Coordinate and dependencies](#coordinate-and-dependencies) - [Changes in this fork](#changes-in-this-fork) +- [Performance](#performance) - [Important recommendations](#important-recommendations) - [`AsyncWebSocketMessageBuffer` and `makeBuffer()`](#asyncwebsocketmessagebuffer-and-makebuffer) - [How to replace a response](#how-to-replace-a-response) - [How to use Middleware](#how-to-use-middleware) - [How to use authentication with AuthenticationMiddleware](#how-to-use-authentication-with-authenticationmiddleware) - [Migration to Middleware to improve performance and memory usage](#migration-to-middleware-to-improve-performance-and-memory-usage) -- [Performance](#performance) - [Original Documentation](#original-documentation) ## Coordinate and dependencies -**WARNING** The library name was changed from `ESP Async WebServer` to `ESPAsyncWebServer` as per the Arduino Lint recommendations. +**WARNING** The library name was changed from `ESP Async WebServer` to `ESPAsyncWebServer` as per the Arduino Lint recommendations, but its name had to stay `ESP Async WebServer` in Arduino Registry. **PlatformIO / pioarduino:** @@ -40,7 +40,7 @@ lib_deps = mathieucarbou/ESPAsyncWebServer @ 3.3.14 **Dependencies:** -- **ESP32**: `mathieucarbou/AsyncTCP @ 3.2.10` (Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.2.10](https://github.com/mathieucarbou/AsyncTCP/releases)) +- **ESP32 with AsyncTCP**: `mathieucarbou/AsyncTCP @ 3.2.10` (Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.2.10](https://github.com/mathieucarbou/AsyncTCP/releases)) - **ESP8266**: `esphome/ESPAsyncTCP-esphome @ 2.0.0` (Arduino IDE: [https://github.com/mathieucarbou/esphome-ESPAsyncTCP#v2.0.0](https://github.com/mathieucarbou/esphome-ESPAsyncTCP/releases/tag/v2.0.0)) - **RP2040**: `khoih-prog/AsyncTCP_RP2040W @ 1.2.0` (Arduino IDE: [https://github.com/khoih-prog/AsyncTCP_RP2040W#v1.2.0](https://github.com/khoih-prog/AsyncTCP_RP2040W/releases/tag/v1.2.0)) @@ -69,6 +69,21 @@ lib_deps = mathieucarbou/ESPAsyncWebServer @ 3.3.14 - (perf) Lot of code cleanup and optimizations - (perf) Performance improvements in terms of memory, speed and size +## Performance + +```bash +> brew install autocannon +> autocannon -c 10 -w 10 -d 20 http://192.168.4.1 +``` + +Here is a capture of the `perf-test-AsyncTCP` PIO environment running with `mathieucarbou/AsyncTCP @ 3.2.10` and `mathieucarbou/ESPAsyncWebServer @ 3.3.14`: + +[![](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10.png)](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10.png) + +Here is a capture of the `perf-test-AsyncTCPSock` PIO environment running with `https://github.com/mathieucarbou/AsyncTCPSock/archive/refs/tags/v1.0.1-dev.zip` and `mathieucarbou/ESPAsyncWebServer @ 3.3.14`: + +[![](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10-asynctcpsock.png)](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10-asynctcpsock.png) + ## Important recommendations Most of the crashes are caused by improper configuration of the library for the project. @@ -222,17 +237,6 @@ myHandler.addMiddleware(&authMiddleware); // add authentication to a specific ha These callbacks can be called multiple times during request parsing, so this is up to the user to now call the `AuthenticationMiddleware.allowed(request)` if needed and ideally when the method is called for the first time. These callbacks are also not triggering the whole middleware chain since they are not part of the request processing workflow (they are not the final handler). -## Performance - -With the `perf-test` PIO environment: - -```bash -> brew install autocannon -> autocannon -c 10 -w 10 -d 20 http://192.168.4.1 -``` - -[![](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10.png)](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10.png) - ## Original Documentation - [Why should you care](#why-should-you-care) diff --git a/docs/index.md b/docs/index.md index e1bfd33..4336c10 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,18 +17,18 @@ This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubo - [Coordinate and dependencies](#coordinate-and-dependencies) - [Changes in this fork](#changes-in-this-fork) +- [Performance](#performance) - [Important recommendations](#important-recommendations) - [`AsyncWebSocketMessageBuffer` and `makeBuffer()`](#asyncwebsocketmessagebuffer-and-makebuffer) - [How to replace a response](#how-to-replace-a-response) - [How to use Middleware](#how-to-use-middleware) - [How to use authentication with AuthenticationMiddleware](#how-to-use-authentication-with-authenticationmiddleware) - [Migration to Middleware to improve performance and memory usage](#migration-to-middleware-to-improve-performance-and-memory-usage) -- [Performance](#performance) - [Original Documentation](#original-documentation) ## Coordinate and dependencies -**WARNING** The library name was changed from `ESP Async WebServer` to `ESPAsyncWebServer` as per the Arduino Lint recommendations. +**WARNING** The library name was changed from `ESP Async WebServer` to `ESPAsyncWebServer` as per the Arduino Lint recommendations, but its name had to stay `ESP Async WebServer` in Arduino Registry. **PlatformIO / pioarduino:** @@ -40,7 +40,7 @@ lib_deps = mathieucarbou/ESPAsyncWebServer @ 3.3.14 **Dependencies:** -- **ESP32**: `mathieucarbou/AsyncTCP @ 3.2.10` (Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.2.10](https://github.com/mathieucarbou/AsyncTCP/releases)) +- **ESP32 with AsyncTCP**: `mathieucarbou/AsyncTCP @ 3.2.10` (Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.2.10](https://github.com/mathieucarbou/AsyncTCP/releases)) - **ESP8266**: `esphome/ESPAsyncTCP-esphome @ 2.0.0` (Arduino IDE: [https://github.com/mathieucarbou/esphome-ESPAsyncTCP#v2.0.0](https://github.com/mathieucarbou/esphome-ESPAsyncTCP/releases/tag/v2.0.0)) - **RP2040**: `khoih-prog/AsyncTCP_RP2040W @ 1.2.0` (Arduino IDE: [https://github.com/khoih-prog/AsyncTCP_RP2040W#v1.2.0](https://github.com/khoih-prog/AsyncTCP_RP2040W/releases/tag/v1.2.0)) @@ -69,6 +69,21 @@ lib_deps = mathieucarbou/ESPAsyncWebServer @ 3.3.14 - (perf) Lot of code cleanup and optimizations - (perf) Performance improvements in terms of memory, speed and size +## Performance + +```bash +> brew install autocannon +> autocannon -c 10 -w 10 -d 20 http://192.168.4.1 +``` + +Here is a capture of the `perf-test-AsyncTCP` PIO environment running with `mathieucarbou/AsyncTCP @ 3.2.10` and `mathieucarbou/ESPAsyncWebServer @ 3.3.14`: + +[![](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10.png)](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10.png) + +Here is a capture of the `perf-test-AsyncTCPSock` PIO environment running with `https://github.com/mathieucarbou/AsyncTCPSock/archive/refs/tags/v1.0.1-dev.zip` and `mathieucarbou/ESPAsyncWebServer @ 3.3.14`: + +[![](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10-asynctcpsock.png)](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10-asynctcpsock.png) + ## Important recommendations Most of the crashes are caused by improper configuration of the library for the project. @@ -222,17 +237,6 @@ myHandler.addMiddleware(&authMiddleware); // add authentication to a specific ha These callbacks can be called multiple times during request parsing, so this is up to the user to now call the `AuthenticationMiddleware.allowed(request)` if needed and ideally when the method is called for the first time. These callbacks are also not triggering the whole middleware chain since they are not part of the request processing workflow (they are not the final handler). -## Performance - -With the `perf-test` PIO environment: - -```bash -> brew install autocannon -> autocannon -c 10 -w 10 -d 20 http://192.168.4.1 -``` - -[![](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10.png)](https://mathieu.carbou.me/ESPAsyncWebServer/perf-c10.png) - ## Original Documentation - [Why should you care](#why-should-you-care) diff --git a/docs/perf-c10-asynctcpsock.png b/docs/perf-c10-asynctcpsock.png new file mode 100644 index 0000000000000000000000000000000000000000..136b54562341a70414076c693c21ecbf567b6e48 GIT binary patch literal 310017 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