Aktualizace na verzi 3.0.6

This commit is contained in:
Pavel Brychta 2024-07-02 20:06:33 +02:00
parent 5c12efc75b
commit e0375a1fb6
30 changed files with 2862 additions and 2879 deletions

17
CMakeLists.txt Normal file
View File

@ -0,0 +1,17 @@
set(COMPONENT_SRCDIRS
"src"
)
set(COMPONENT_ADD_INCLUDEDIRS
"src"
)
set(COMPONENT_REQUIRES
"arduino-esp32"
"AsyncTCP"
)
register_component()
target_compile_definitions(${COMPONENT_TARGET} PUBLIC -DESP32)
target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti)

View File

@ -4,34 +4,46 @@
[![Continuous Integration](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml/badge.svg)](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml) [![Continuous Integration](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml/badge.svg)](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml)
[![PlatformIO Registry](https://badges.registry.platformio.org/packages/mathieucarbou/library/ESP%20Async%20WebServer.svg)](https://registry.platformio.org/libraries/mathieucarbou/ESP%20Async%20WebServer) [![PlatformIO Registry](https://badges.registry.platformio.org/packages/mathieucarbou/library/ESP%20Async%20WebServer.svg)](https://registry.platformio.org/libraries/mathieucarbou/ESP%20Async%20WebServer)
Asynchronous HTTP and WebSocket Server Library for ESP32. 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. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.
This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) and includes all the concurrency fixes. This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) and includes all the concurrency fixes.
## Changes in this fork ## Changes in this fork
- Removed SPIFFSEditor - [@ayushsharma82](https://github.com/ayushsharma82) and [@mathieucarbou](https://github.com/mathieucarbou): Add RP2040 support ([#31](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/31))
- Deployed in PlatformIO registry and Arduino IDE library manager - [@mathieucarbou](https://github.com/mathieucarbou): `SSE_MAX_QUEUED_MESSAGES` to control the maximum number of messages that can be queued for a SSE client
- CI - [@mathieucarbou](https://github.com/mathieucarbou): `write()` function public in `AsyncEventSource.h`
- Some code cleanup - [@mathieucarbou](https://github.com/mathieucarbou): `WS_MAX_QUEUED_MESSAGES`: control the maximum number of messages that can be queued for a Websocket client
- `SSE_MAX_QUEUED_MESSAGES` to control the maximum number of messages that can be queued for a SSE client - [@mathieucarbou](https://github.com/mathieucarbou): Added `setAuthentication(const String& username, const String& password)`
- `write()` function public in `AsyncEventSource.h` - [@mathieucarbou](https://github.com/mathieucarbou): Added `setCloseClientOnQueueFull(bool)` which can be set on a client to either close the connection or discard messages but not close the connection when the queue is full
- Arduino Json 7 compatibility and backward compatible with 6 and 6 (changes in `AsyncJson.h`). The API to use Json has not changed. These are only internal changes. - [@mathieucarbou](https://github.com/mathieucarbou): Added `StreamConcat` example to show how to stream multiple files in one response
- `WS_MAX_QUEUED_MESSAGES`: control the maximum number of messages that can be queued for a Websocket client - [@mathieucarbou](https://github.com/mathieucarbou): Added all flavors of `binary()`, `text()`, `binaryAll()` and `textAll()` in `AsyncWebSocket`
- Resurrected `AsyncWebSocketMessageBuffer` and `makeBuffer()` in order to make the fork API-compatible with the original library from me-no-dev regarding WebSocket. - [@mathieucarbou](https://github.com/mathieucarbou): Arduino 3 / ESP-IDF 5.1 compatibility
- [#5](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/5) ([@vortigont](https://github.com/vortigont)): set real "Last-Modified" header based on file's LastWrite time - [@mathieucarbou](https://github.com/mathieucarbou): Arduino Json 7 compatibility and backward compatible with 6 and 6 (changes in `AsyncJson.h`). The API to use Json has not changed. These are only internal changes.
- [#13](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/13) ([@tueddy](https://github.com/tueddy)): Compile with Arduino 3 (ESP-IDF 5.1) - [@mathieucarbou](https://github.com/mathieucarbou): CI
- [#14](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/14) ([@nilo85](https://github.com/nilo85)): Add support for Auth & GET requests in AsyncCallbackJsonWebHandler - [@mathieucarbou](https://github.com/mathieucarbou): Depends on `mathieucarbou/Async TCP @ ^3.1.4`
- Added `setAuthentication(const String& username, const String& password)` - [@mathieucarbou](https://github.com/mathieucarbou): Deployed in PlatformIO registry and Arduino IDE library manager
- Added `StreamConcat` example to show how to stream multiple files in one response - [@mathieucarbou](https://github.com/mathieucarbou): Firmware size optimization: remove mbedtls dependency (accounts for 33KB in firmware)
- Remove filename after inline in Content-Disposition header according to RFC2183 - [@mathieucarbou](https://github.com/mathieucarbou): Made DEFAULT_MAX_SSE_CLIENTS customizable
- Depends on `mathieucarbou/Async TCP @ ^3.1.4` - [@mathieucarbou](https://github.com/mathieucarbou): Made DEFAULT_MAX_WS_CLIENTS customizable
- Arduino 3 / ESP-IDF 5.1 compatibility - [@mathieucarbou](https://github.com/mathieucarbou): Remove filename after inline in Content-Disposition header according to RFC2183
- Added all flavors of `binary()`, `text()`, `binaryAll()` and `textAll()` in `AsyncWebSocket` - [@mathieucarbou](https://github.com/mathieucarbou): Removed SPIFFSEditor to reduce library size and maintainance. SPIFF si also deprecated. If you need it, please copy the files from the original repository in your project. This fork focus on maintaining the server part and the SPIFFEditor is an application which has nothing to do inside a server library.
- Added `setCloseClientOnQueueFull(bool)` which can be set on a client to either close the connection or discard messages but not close the connection when the queue is full - [@mathieucarbou](https://github.com/mathieucarbou): Resurrected `AsyncWebSocketMessageBuffer` and `makeBuffer()` in order to make the fork API-compatible with the original library from me-no-dev regarding WebSocket.
- [#29](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/29) ([@vortigont](https://github.com/vortigont)): Some websocket code cleanup - [@mathieucarbou](https://github.com/mathieucarbou): Some code cleanup
- Use `-D DEFAULT_MAX_WS_CLIENTS` to change the number of allows WebSocket clients and use `cleanupClients()` to help cleanup resources about dead clients - [@mathieucarbou](https://github.com/mathieucarbou): Use `-D DEFAULT_MAX_WS_CLIENTS` to change the number of allows WebSocket clients and use `cleanupClients()` to help cleanup resources about dead clients
- [@nilo85](https://github.com/nilo85): Add support for Auth & GET requests in AsyncCallbackJsonWebHandler ([#14](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/14))
- [@p0p-x](https://github.com/p0p-x): ESP IDF Compatibility (added back CMakeLists.txt) ([#32](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/32))
- [@tueddy](https://github.com/tueddy): Compile with Arduino 3 (ESP-IDF 5.1) ([#13](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/13))
- [@vortigont](https://github.com/vortigont): Set real "Last-Modified" header based on file's LastWrite time ([#5](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/5))
- [@vortigont](https://github.com/vortigont): Some websocket code cleanup ([#29](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/29))
- [@vortigont](https://github.com/vortigont): Refactor code - replace DYI structs with STL objects ([#39](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/39))
## Dependencies:
- **ESP32**: `mathieucarbou/Async TCP @ ^3.1.4` (Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.1.4](https://github.com/mathieucarbou/AsyncTCP/releases/tag/v3.1.4))
- **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))
## Documentation ## Documentation
@ -76,17 +88,30 @@ void send(JsonDocument& doc) {
I recommend to use the official API `AsyncWebSocketMessageBuffer` to retain further compatibility. I recommend to use the official API `AsyncWebSocketMessageBuffer` to retain further compatibility.
## Stack size and queues ## Important recommendations
Here are some important flags to tweak depending on your needs: Most of the crashes are caused by improper configuration of the library for the project.
Here are some recommendations to avoid them.
```cpp 1. Set the running core to be on the same core of your application (usually core 1) `-D CONFIG_ASYNC_TCP_RUNNING_CORE=1`
// Async TCP queue size 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://oss.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.
```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_QUEUE_SIZE=128
// Async TCP async task core
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1 -D CONFIG_ASYNC_TCP_RUNNING_CORE=1
// Async TCP async stac ksize -D CONFIG_ASYNC_TCP_STACK_SIZE=4096
-D CONFIG_ASYNC_TCP_STACK_SIZE=8096
// WebSocket queue size
-D WS_MAX_QUEUED_MESSAGES=64 -D WS_MAX_QUEUED_MESSAGES=64
``` ```

View File

@ -1,7 +1,7 @@
# bundle exec jekyll serve --host=0.0.0.0 # bundle exec jekyll serve --host=0.0.0.0
title: ESP Async WebServer title: ESP Async WebServer
description: "Asynchronous HTTP and WebSocket Server Library for ESP32" description: "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040"
remote_theme: pages-themes/cayman@v0.2.0 remote_theme: pages-themes/cayman@v0.2.0
plugins: plugins:
- jekyll-remote-theme - jekyll-remote-theme

View File

@ -4,34 +4,46 @@
[![Continuous Integration](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml/badge.svg)](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml) [![Continuous Integration](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml/badge.svg)](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml)
[![PlatformIO Registry](https://badges.registry.platformio.org/packages/mathieucarbou/library/ESP%20Async%20WebServer.svg)](https://registry.platformio.org/libraries/mathieucarbou/ESP%20Async%20WebServer) [![PlatformIO Registry](https://badges.registry.platformio.org/packages/mathieucarbou/library/ESP%20Async%20WebServer.svg)](https://registry.platformio.org/libraries/mathieucarbou/ESP%20Async%20WebServer)
Asynchronous HTTP and WebSocket Server Library for ESP32. 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. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.
This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) and includes all the concurrency fixes. This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) and includes all the concurrency fixes.
## Changes in this fork ## Changes in this fork
- Removed SPIFFSEditor - [@ayushsharma82](https://github.com/ayushsharma82) and [@mathieucarbou](https://github.com/mathieucarbou): Add RP2040 support ([#31](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/31))
- Deployed in PlatformIO registry and Arduino IDE library manager - [@mathieucarbou](https://github.com/mathieucarbou): `SSE_MAX_QUEUED_MESSAGES` to control the maximum number of messages that can be queued for a SSE client
- CI - [@mathieucarbou](https://github.com/mathieucarbou): `write()` function public in `AsyncEventSource.h`
- Some code cleanup - [@mathieucarbou](https://github.com/mathieucarbou): `WS_MAX_QUEUED_MESSAGES`: control the maximum number of messages that can be queued for a Websocket client
- `SSE_MAX_QUEUED_MESSAGES` to control the maximum number of messages that can be queued for a SSE client - [@mathieucarbou](https://github.com/mathieucarbou): Added `setAuthentication(const String& username, const String& password)`
- `write()` function public in `AsyncEventSource.h` - [@mathieucarbou](https://github.com/mathieucarbou): Added `setCloseClientOnQueueFull(bool)` which can be set on a client to either close the connection or discard messages but not close the connection when the queue is full
- Arduino Json 7 compatibility and backward compatible with 6 and 6 (changes in `AsyncJson.h`). The API to use Json has not changed. These are only internal changes. - [@mathieucarbou](https://github.com/mathieucarbou): Added `StreamConcat` example to show how to stream multiple files in one response
- `WS_MAX_QUEUED_MESSAGES`: control the maximum number of messages that can be queued for a Websocket client - [@mathieucarbou](https://github.com/mathieucarbou): Added all flavors of `binary()`, `text()`, `binaryAll()` and `textAll()` in `AsyncWebSocket`
- Resurrected `AsyncWebSocketMessageBuffer` and `makeBuffer()` in order to make the fork API-compatible with the original library from me-no-dev regarding WebSocket. - [@mathieucarbou](https://github.com/mathieucarbou): Arduino 3 / ESP-IDF 5.1 compatibility
- [#5](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/5) ([@vortigont](https://github.com/vortigont)): set real "Last-Modified" header based on file's LastWrite time - [@mathieucarbou](https://github.com/mathieucarbou): Arduino Json 7 compatibility and backward compatible with 6 and 6 (changes in `AsyncJson.h`). The API to use Json has not changed. These are only internal changes.
- [#13](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/13) ([@tueddy](https://github.com/tueddy)): Compile with Arduino 3 (ESP-IDF 5.1) - [@mathieucarbou](https://github.com/mathieucarbou): CI
- [#14](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/14) ([@nilo85](https://github.com/nilo85)): Add support for Auth & GET requests in AsyncCallbackJsonWebHandler - [@mathieucarbou](https://github.com/mathieucarbou): Depends on `mathieucarbou/Async TCP @ ^3.1.4`
- Added `setAuthentication(const String& username, const String& password)` - [@mathieucarbou](https://github.com/mathieucarbou): Deployed in PlatformIO registry and Arduino IDE library manager
- Added `StreamConcat` example to show how to stream multiple files in one response - [@mathieucarbou](https://github.com/mathieucarbou): Firmware size optimization: remove mbedtls dependency (accounts for 33KB in firmware)
- Remove filename after inline in Content-Disposition header according to RFC2183 - [@mathieucarbou](https://github.com/mathieucarbou): Made DEFAULT_MAX_SSE_CLIENTS customizable
- Depends on `mathieucarbou/Async TCP @ ^3.1.4` - [@mathieucarbou](https://github.com/mathieucarbou): Made DEFAULT_MAX_WS_CLIENTS customizable
- Arduino 3 / ESP-IDF 5.1 compatibility - [@mathieucarbou](https://github.com/mathieucarbou): Remove filename after inline in Content-Disposition header according to RFC2183
- Added all flavors of `binary()`, `text()`, `binaryAll()` and `textAll()` in `AsyncWebSocket` - [@mathieucarbou](https://github.com/mathieucarbou): Removed SPIFFSEditor to reduce library size and maintainance. SPIFF si also deprecated. If you need it, please copy the files from the original repository in your project. This fork focus on maintaining the server part and the SPIFFEditor is an application which has nothing to do inside a server library.
- Added `setCloseClientOnQueueFull(bool)` which can be set on a client to either close the connection or discard messages but not close the connection when the queue is full - [@mathieucarbou](https://github.com/mathieucarbou): Resurrected `AsyncWebSocketMessageBuffer` and `makeBuffer()` in order to make the fork API-compatible with the original library from me-no-dev regarding WebSocket.
- [#29](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/29) ([@vortigont](https://github.com/vortigont)): Some websocket code cleanup - [@mathieucarbou](https://github.com/mathieucarbou): Some code cleanup
- Use `-D DEFAULT_MAX_WS_CLIENTS` to change the number of allows WebSocket clients and use `cleanupClients()` to help cleanup resources about dead clients - [@mathieucarbou](https://github.com/mathieucarbou): Use `-D DEFAULT_MAX_WS_CLIENTS` to change the number of allows WebSocket clients and use `cleanupClients()` to help cleanup resources about dead clients
- [@nilo85](https://github.com/nilo85): Add support for Auth & GET requests in AsyncCallbackJsonWebHandler ([#14](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/14))
- [@p0p-x](https://github.com/p0p-x): ESP IDF Compatibility (added back CMakeLists.txt) ([#32](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/32))
- [@tueddy](https://github.com/tueddy): Compile with Arduino 3 (ESP-IDF 5.1) ([#13](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/13))
- [@vortigont](https://github.com/vortigont): Set real "Last-Modified" header based on file's LastWrite time ([#5](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/5))
- [@vortigont](https://github.com/vortigont): Some websocket code cleanup ([#29](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/29))
- [@vortigont](https://github.com/vortigont): Refactor code - replace DYI structs with STL objects ([#39](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/39))
## Dependencies:
- **ESP32**: `mathieucarbou/Async TCP @ ^3.1.4` (Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.1.4](https://github.com/mathieucarbou/AsyncTCP/releases/tag/v3.1.4))
- **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))
## Documentation ## Documentation
@ -76,17 +88,30 @@ void send(JsonDocument& doc) {
I recommend to use the official API `AsyncWebSocketMessageBuffer` to retain further compatibility. I recommend to use the official API `AsyncWebSocketMessageBuffer` to retain further compatibility.
## Stack size and queues ## Important recommendations
Here are some important flags to tweak depending on your needs: Most of the crashes are caused by improper configuration of the library for the project.
Here are some recommendations to avoid them.
```cpp 1. Set the running core to be on the same core of your application (usually core 1) `-D CONFIG_ASYNC_TCP_RUNNING_CORE=1`
// Async TCP queue size 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://oss.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.
```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_QUEUE_SIZE=128
// Async TCP async task core
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1 -D CONFIG_ASYNC_TCP_RUNNING_CORE=1
// Async TCP async stac ksize -D CONFIG_ASYNC_TCP_STACK_SIZE=4096
-D CONFIG_ASYNC_TCP_STACK_SIZE=8096
// WebSocket queue size
-D WS_MAX_QUEUED_MESSAGES=64 -D WS_MAX_QUEUED_MESSAGES=64
``` ```

View File

@ -1,10 +1,13 @@
#include <DNSServer.h> #include <DNSServer.h>
#ifdef ESP32 #ifdef ESP32
#include <WiFi.h> #include <AsyncTCP.h>
#include <AsyncTCP.h> #include <WiFi.h>
#elif defined(ESP8266) #elif defined(ESP8266)
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h> #include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040)
#include <WebServer.h>
#include <WiFi.h>
#endif #endif
#include "ESPAsyncWebServer.h" #include "ESPAsyncWebServer.h"
@ -12,43 +15,43 @@ DNSServer dnsServer;
AsyncWebServer server(80); AsyncWebServer server(80);
class CaptiveRequestHandler : public AsyncWebHandler { class CaptiveRequestHandler : public AsyncWebHandler {
public: public:
CaptiveRequestHandler() {} CaptiveRequestHandler() {}
virtual ~CaptiveRequestHandler() {} virtual ~CaptiveRequestHandler() {}
bool canHandle(__unused AsyncWebServerRequest *request){ bool canHandle(__unused AsyncWebServerRequest* request) {
//request->addInterestingHeader("ANY"); // request->addInterestingHeader("ANY");
return true; return true;
} }
void handleRequest(AsyncWebServerRequest *request) { void handleRequest(AsyncWebServerRequest* request) {
AsyncResponseStream *response = request->beginResponseStream("text/html"); AsyncResponseStream* response = request->beginResponseStream("text/html");
response->print("<!DOCTYPE html><html><head><title>Captive Portal</title></head><body>"); 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 out 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()); response->printf("<p>You were trying to reach: http://%s%s</p>", request->host().c_str(), request->url().c_str());
response->printf("<p>Try opening <a href='http://%s'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str()); response->printf("<p>Try opening <a href='http://%s'>this link</a> instead</p>", WiFi.softAPIP().toString().c_str());
response->print("</body></html>"); response->print("</body></html>");
request->send(response); request->send(response);
} }
}; };
void setup() {
void setup(){
Serial.begin(115200); Serial.begin(115200);
Serial.println(); Serial.println();
Serial.println("Configuring access point..."); Serial.println("Configuring access point...");
if (!WiFi.softAP("esp-captive")) { if (!WiFi.softAP("esp-captive")) {
Serial.println("Soft AP creation failed."); Serial.println("Soft AP creation failed.");
while (1); while (1)
;
} }
dnsServer.start(53, "*", WiFi.softAPIP()); dnsServer.start(53, "*", WiFi.softAPIP());
server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);//only when requested from AP server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER); // only when requested from AP
//more handlers... // more handlers...
server.begin(); server.begin();
} }
void loop(){ void loop() {
dnsServer.processNextRequest(); dnsServer.processNextRequest();
} }

View File

@ -1,6 +1,6 @@
#include "mbedtls/md5.h"
#include <Arduino.h> #include <Arduino.h>
#include <MD5Builder.h> #include <MD5Builder.h>
#include "mbedtls/md5.h"
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);

View File

@ -2,11 +2,14 @@
#include <DNSServer.h> #include <DNSServer.h>
#ifdef ESP32 #ifdef ESP32
#include <AsyncTCP.h> #include <AsyncTCP.h>
#include <WiFi.h> #include <WiFi.h>
#elif defined(ESP8266) #elif defined(ESP8266)
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h> #include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040)
#include <WebServer.h>
#include <WiFi.h>
#endif #endif
#include "ESPAsyncWebServer.h" #include "ESPAsyncWebServer.h"

View File

@ -7,11 +7,14 @@
#include <Arduino.h> #include <Arduino.h>
#ifdef ESP32 #ifdef ESP32
#include <WiFi.h> #include <AsyncTCP.h>
#include <AsyncTCP.h> #include <WiFi.h>
#elif defined(ESP8266) #elif defined(ESP8266)
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h> #include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040)
#include <WebServer.h>
#include <WiFi.h>
#endif #endif
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
@ -22,52 +25,52 @@ const char* password = "YOUR_PASSWORD";
const char* PARAM_MESSAGE = "message"; const char* PARAM_MESSAGE = "message";
void notFound(AsyncWebServerRequest *request) { void notFound(AsyncWebServerRequest* request) {
request->send(404, "text/plain", "Not found"); request->send(404, "text/plain", "Not found");
} }
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password); WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED) { if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.printf("WiFi Failed!\n"); Serial.printf("WiFi Failed!\n");
return; return;
}
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send(200, "text/plain", "Hello, world");
});
// 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);
});
Serial.print("IP Address: "); // Send a POST request to <IP>/post with a form field message set to <message>
Serial.println(WiFi.localIP()); 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);
});
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ server.onNotFound(notFound);
request->send(200, "text/plain", "Hello, world");
});
// Send a GET request to <IP>/get?message=<message> server.begin();
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);
});
server.onNotFound(notFound);
server.begin();
} }
void loop() { void loop() {

View File

@ -17,7 +17,11 @@ class StreamConcat : public Stream {
return c != -1 ? c : _s2->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 { size_t readBytes(char* buffer, size_t length) override {
#endif
size_t count = _s1->readBytes(buffer, length); size_t count = _s1->readBytes(buffer, length);
return count > 0 ? count : _s2->readBytes(buffer, length); return count > 0 ? count : _s2->readBytes(buffer, length);
} }

View File

@ -1,11 +1,14 @@
#include <Arduino.h> #include <Arduino.h>
#include <DNSServer.h> #include <DNSServer.h>
#ifdef ESP32 #ifdef ESP32
#include <AsyncTCP.h> #include <AsyncTCP.h>
#include <WiFi.h> #include <WiFi.h>
#elif defined(ESP8266) #elif defined(ESP8266)
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h> #include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040)
#include <WebServer.h>
#include <WiFi.h>
#endif #endif
#include "StreamConcat.h" #include "StreamConcat.h"
#include "StreamString.h" #include "StreamString.h"
@ -42,7 +45,11 @@ void setup() {
StreamConcat stream1(&header, &body); StreamConcat stream1(&header, &body);
StreamString content; StreamString content;
#if defined(TARGET_RP2040)
content.printf("FreeHeap: %d", rp2040.getFreeHeap());
#else
content.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap()); content.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap());
#endif
StreamConcat stream2 = StreamConcat(&stream1, &content); StreamConcat stream2 = StreamConcat(&stream1, &content);
File footer = LittleFS.open("/footer.html", "r"); File footer = LittleFS.open("/footer.html", "r");
@ -67,7 +74,11 @@ void loop() {
// dnsServer.processNextRequest(); // dnsServer.processNextRequest();
if (millis() - last > 2000) { if (millis() - last > 2000) {
#if defined(TARGET_RP2040)
Serial.printf("FreeHeap: %d", rp2040.getFreeHeap());
#else
Serial.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap()); Serial.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap());
#endif
last = millis(); last = millis();
} }
} }

View File

@ -18,7 +18,11 @@ class StreamString : public Stream {
return c; return c;
} }
#if defined(TARGET_RP2040)
size_t readBytes(char* buffer, size_t length) {
#else
size_t readBytes(char* buffer, size_t length) override { size_t readBytes(char* buffer, size_t length) override {
#endif
if (length > _buffer.length()) if (length > _buffer.length())
length = _buffer.length(); length = _buffer.length();
// Don't use _str.ToCharArray() because it inserts a terminator // Don't use _str.ToCharArray() because it inserts a terminator

View File

@ -1,7 +1,7 @@
{ {
"name": "ESP Async WebServer", "name": "ESP Async WebServer",
"version": "2.10.8", "version": "3.0.6",
"description": "Asynchronous HTTP and WebSocket Server Library for ESP32. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.", "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", "keywords": "http,async,websocket,webserver",
"homepage": "https://github.com/mathieucarbou/ESPAsyncWebServer", "homepage": "https://github.com/mathieucarbou/ESPAsyncWebServer",
"repository": { "repository": {
@ -21,7 +21,8 @@
"frameworks": "arduino", "frameworks": "arduino",
"platforms": [ "platforms": [
"espressif32", "espressif32",
"espressif8266" "espressif8266",
"raspberrypi"
], ],
"dependencies": [ "dependencies": [
{ {
@ -39,6 +40,12 @@
{ {
"name": "Hash", "name": "Hash",
"platforms": "espressif8266" "platforms": "espressif8266"
},
{
"owner": "khoih-prog",
"name": "AsyncTCP_RP2040W",
"version": "^1.2.0",
"platforms": "raspberrypi"
} }
], ],
"export": { "export": {
@ -50,5 +57,8 @@
"LICENSE", "LICENSE",
"README.md" "README.md"
] ]
},
"build": {
"libCompatMode": "strict"
} }
} }

View File

@ -1,10 +1,10 @@
name=ESP Async WebServer name=ESP Async WebServer
version=2.10.8 version=3.0.6
author=Me-No-Dev author=Me-No-Dev
maintainer=Mathieu Carbou <mathieu.carbou@gmail.com> maintainer=Mathieu Carbou <mathieu.carbou@gmail.com>
sentence=Asynchronous HTTP and WebSocket Server Library for ESP32 sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040
paragraph=Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc paragraph=Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc
category=Other category=Other
url=https://github.com/mathieucarbou/ESPAsyncWebServer url=https://github.com/mathieucarbou/ESPAsyncWebServer
architectures=esp8266,esp32 architectures=*
license=LGPL-3.0 license=LGPL-3.0

View File

@ -4,42 +4,57 @@ build_flags =
-Wall -Wextra -Wall -Wextra
-D CONFIG_ARDUHAL_LOG_COLORS -D CONFIG_ARDUHAL_LOG_COLORS
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
lib_deps =
bblanchon/ArduinoJson @ 7.0.4
mathieucarbou/Async TCP @ ^3.1.4
; https://github.com/mathieucarbou/AsyncTCP
; https://github.com/me-no-dev/AsyncTCP
esphome/ESPAsyncTCP-esphome @ 2.0.0
upload_protocol = esptool upload_protocol = esptool
monitor_speed = 115200 monitor_speed = 115200
monitor_filters = esp32_exception_decoder, log2file monitor_filters = esp32_exception_decoder, log2file
[platformio] [platformio]
lib_dir = . lib_dir = .
src_dir = examples/CaptivePortal ; src_dir = examples/CaptivePortal
; src_dir = examples/SimpleServer ; src_dir = examples/SimpleServer
; src_dir = examples/StreamFiles src_dir = examples/StreamFiles
; src_dir = examples/Filters ; src_dir = examples/Filters
; src_dir = examples/Draft ; src_dir = examples/Draft
[env:arduino] [env:arduino]
platform = espressif32 platform = espressif32
board = esp32dev board = esp32dev
lib_deps =
bblanchon/ArduinoJson @ 7.1.0
mathieucarbou/Async TCP @ ^3.1.4
[env:arduino-2] [env:arduino-2]
platform = espressif32@6.7.0 platform = espressif32@6.7.0
board = esp32dev board = esp32dev
lib_deps =
bblanchon/ArduinoJson @ 7.1.0
mathieucarbou/Async TCP @ ^3.1.4
[env:arduino-3] [env:arduino-3]
platform = espressif32 platform = espressif32
platform_packages= platform_packages=
platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1 platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.2
platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.2/esp32-arduino-libs-3.0.2.zip
board = esp32dev board = esp32dev
lib_deps =
bblanchon/ArduinoJson @ 7.1.0
mathieucarbou/Async TCP @ ^3.1.4
[env:esp8266] [env:esp8266]
platform = espressif8266 platform = espressif8266
board = huzzah board = huzzah
lib_deps = lib_deps =
bblanchon/ArduinoJson @ 7.0.4 bblanchon/ArduinoJson @ 7.1.0
esphome/ESPAsyncTCP-esphome @ 2.0.0 esphome/ESPAsyncTCP-esphome @ 2.0.0
; PlatformIO support for Raspberry Pi Pico is not official
; https://github.com/platformio/platform-raspberrypi/pull/36
; https://github.com/earlephilhower/arduino-pico/blob/master/docs/platformio.rst
; board settings: https://github.com/earlephilhower/arduino-pico/blob/master/tools/json/rpipico.json
[env:rpipicow]
upload_protocol = picotool
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
board = rpipicow
lib_deps =
bblanchon/ArduinoJson @ 7.1.0
khoih-prog/AsyncTCP_RP2040W @ 1.2.0

View File

@ -18,43 +18,43 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
#include "Arduino.h" #include "Arduino.h"
#include "AsyncEventSource.h" #if defined(ESP32)
#ifndef ESP8266
#include <rom/ets_sys.h> #include <rom/ets_sys.h>
#endif #endif
#include "AsyncEventSource.h"
static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){ static String generateEventMessage(const char* message, const char* event, uint32_t id, uint32_t reconnect) {
String ev; String ev;
if(reconnect){ if (reconnect) {
ev += F("retry: "); ev += F("retry: ");
ev += reconnect; ev += reconnect;
ev += F("\r\n"); ev += F("\r\n");
} }
if(id){ if (id) {
ev += F("id: "); ev += F("id: ");
ev += String(id); ev += String(id);
ev += F("\r\n"); ev += F("\r\n");
} }
if(event != NULL){ if (event != NULL) {
ev += F("event: "); ev += F("event: ");
ev += String(event); ev += String(event);
ev += F("\r\n"); ev += F("\r\n");
} }
if(message != NULL){ if (message != NULL) {
size_t messageLen = strlen(message); size_t messageLen = strlen(message);
char * lineStart = (char *)message; char* lineStart = (char*)message;
char * lineEnd; char* lineEnd;
do { do {
char * nextN = strchr(lineStart, '\n'); char* nextN = strchr(lineStart, '\n');
char * nextR = strchr(lineStart, '\r'); char* nextR = strchr(lineStart, '\r');
if(nextN == NULL && nextR == NULL){ if (nextN == NULL && nextR == NULL) {
size_t llen = ((char *)message + messageLen) - lineStart; size_t llen = ((char*)message + messageLen) - lineStart;
char * ldata = (char *)malloc(llen+1); char* ldata = (char*)malloc(llen + 1);
if(ldata != NULL){ if (ldata != NULL) {
memcpy(ldata, lineStart, llen); memcpy(ldata, lineStart, llen);
ldata[llen] = 0; ldata[llen] = 0;
ev += F("data: "); ev += F("data: ");
@ -62,24 +62,24 @@ static String generateEventMessage(const char *message, const char *event, uint3
ev += F("\r\n\r\n"); ev += F("\r\n\r\n");
free(ldata); free(ldata);
} }
lineStart = (char *)message + messageLen; lineStart = (char*)message + messageLen;
} else { } else {
char * nextLine = NULL; char* nextLine = NULL;
if(nextN != NULL && nextR != NULL){ if (nextN != NULL && nextR != NULL) {
if(nextR < nextN){ if (nextR < nextN) {
lineEnd = nextR; lineEnd = nextR;
if(nextN == (nextR + 1)) if (nextN == (nextR + 1))
nextLine = nextN + 1; nextLine = nextN + 1;
else else
nextLine = nextR + 1; nextLine = nextR + 1;
} else { } else {
lineEnd = nextN; lineEnd = nextN;
if(nextR == (nextN + 1)) if (nextR == (nextN + 1))
nextLine = nextR + 1; nextLine = nextR + 1;
else else
nextLine = nextN + 1; nextLine = nextN + 1;
} }
} else if(nextN != NULL){ } else if (nextN != NULL) {
lineEnd = nextN; lineEnd = nextN;
nextLine = nextN + 1; nextLine = nextN + 1;
} else { } else {
@ -88,8 +88,8 @@ static String generateEventMessage(const char *message, const char *event, uint3
} }
size_t llen = lineEnd - lineStart; size_t llen = lineEnd - lineStart;
char * ldata = (char *)malloc(llen+1); char* ldata = (char*)malloc(llen + 1);
if(ldata != NULL){ if (ldata != NULL) {
memcpy(ldata, lineStart, llen); memcpy(ldata, lineStart, llen);
ldata[llen] = 0; ldata[llen] = 0;
ev += F("data: "); ev += F("data: ");
@ -98,10 +98,10 @@ static String generateEventMessage(const char *message, const char *event, uint3
free(ldata); free(ldata);
} }
lineStart = nextLine; lineStart = nextLine;
if(lineStart == ((char *)message + messageLen)) if (lineStart == ((char*)message + messageLen))
ev += F("\r\n"); ev += F("\r\n");
} }
} while(lineStart < ((char *)message + messageLen)); } while (lineStart < ((char*)message + messageLen));
} }
return ev; return ev;
@ -109,11 +109,10 @@ static String generateEventMessage(const char *message, const char *event, uint3
// Message // Message
AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len) AsyncEventSourceMessage::AsyncEventSourceMessage(const char* data, size_t len)
: _data(nullptr), _len(len), _sent(0), _acked(0) : _data(nullptr), _len(len), _sent(0), _acked(0) {
{ _data = (uint8_t*)malloc(_len + 1);
_data = (uint8_t*)malloc(_len+1); if (_data == nullptr) {
if(_data == nullptr){
_len = 0; _len = 0;
} else { } else {
memcpy(_data, data, len); memcpy(_data, data, len);
@ -122,18 +121,18 @@ AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len)
} }
AsyncEventSourceMessage::~AsyncEventSourceMessage() { AsyncEventSourceMessage::~AsyncEventSourceMessage() {
if(_data != NULL) if (_data != NULL)
free(_data); free(_data);
} }
size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) { size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) {
(void)time; (void)time;
// If the whole message is now acked... // If the whole message is now acked...
if(_acked + len > _len){ if (_acked + len > _len) {
// Return the number of extra bytes acked (they will be carried on to the next message) // Return the number of extra bytes acked (they will be carried on to the next message)
const size_t extra = _acked + len - _len; const size_t extra = _acked + len - _len;
_acked = _len; _acked = _len;
return extra; return extra;
} }
// Return that no extra bytes left. // Return that no extra bytes left.
_acked += len; _acked += len;
@ -142,188 +141,173 @@ size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) {
// This could also return void as the return value is not used. // This could also return void as the return value is not used.
// Leaving as-is for compatibility... // Leaving as-is for compatibility...
size_t AsyncEventSourceMessage::send(AsyncClient *client) { size_t AsyncEventSourceMessage::send(AsyncClient* client) {
if (_sent >= _len) { if (_sent >= _len) {
return 0; return 0;
} }
const size_t len_to_send = _len - _sent; const size_t len_to_send = _len - _sent;
auto position = reinterpret_cast<const char*>(_data + _sent); auto position = reinterpret_cast<const char*>(_data + _sent);
const size_t sent_now = client->write(position, len_to_send); const size_t sent_now = client->write(position, len_to_send);
_sent += sent_now; _sent += sent_now;
return sent_now; return sent_now;
} }
// Client // Client
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server) {
: _messageQueue(LinkedList<AsyncEventSourceMessage *>([](AsyncEventSourceMessage *m){ delete m; }))
{
_client = request->client(); _client = request->client();
_server = server; _server = server;
_lastId = 0; _lastId = 0;
if(request->hasHeader(F("Last-Event-ID"))) if (request->hasHeader(F("Last-Event-ID")))
_lastId = atoi(request->getHeader(F("Last-Event-ID"))->value().c_str()); _lastId = atoi(request->getHeader(F("Last-Event-ID"))->value().c_str());
_client->setRxTimeout(0); _client->setRxTimeout(0);
_client->onError(NULL, NULL); _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->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->onPoll([](void* r, AsyncClient* c) { (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this);
_client->onData(NULL, NULL); _client->onData(NULL, NULL);
_client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this); _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->onDisconnect([this](void* r, AsyncClient* c) { ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this);
_server->_addClient(this); _server->_addClient(this);
delete request; delete request;
} }
AsyncEventSourceClient::~AsyncEventSourceClient(){ AsyncEventSourceClient::~AsyncEventSourceClient() {
_lockmq.lock(); #ifdef ESP32
_messageQueue.free(); std::lock_guard<std::mutex> lock(_lockmq);
_lockmq.unlock(); #endif
_messageQueue.clear();
close(); close();
} }
void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){ void AsyncEventSourceClient::_queueMessage(const char* message, size_t len) {
if(dataMessage == NULL) #ifdef ESP32
return; // length() is not thread-safe, thus acquiring the lock before this call..
if(!connected()){ std::lock_guard<std::mutex> lock(_lockmq);
delete dataMessage; #endif
return;
} if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
//length() is not thread-safe, thus acquiring the lock before this call..
_lockmq.lock();
if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){
#ifdef ESP8266 #ifdef ESP8266
ets_printf(String(F("ERROR: Too many messages queued\n")).c_str()); ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
#else #elif defined(ESP32)
log_e("Too many messages queued: deleting message"); log_e("Too many messages queued: deleting message");
#endif #endif
delete dataMessage; return;
} else {
_messageQueue.add(dataMessage);
// runqueue trigger when new messages added
if(_client->canSend()) {
_runQueue();
}
} }
_lockmq.unlock();
}
void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){ _messageQueue.emplace_back(message, len);
// Same here, acquiring the lock early // runqueue trigger when new messages added
_lockmq.lock(); if (_client->canSend()) {
while(len && !_messageQueue.isEmpty()){
len = _messageQueue.front()->ack(len, time);
if(_messageQueue.front()->finished())
_messageQueue.remove(_messageQueue.front());
}
_runQueue();
_lockmq.unlock();
}
void AsyncEventSourceClient::_onPoll(){
_lockmq.lock();
if(!_messageQueue.isEmpty()){
_runQueue(); _runQueue();
} }
_lockmq.unlock();
} }
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){ void AsyncEventSourceClient::_onAck(size_t len, uint32_t time) {
#ifdef ESP32
// Same here, acquiring the lock early
std::lock_guard<std::mutex> lock(_lockmq);
#endif
while (len && _messageQueue.size()) {
len = _messageQueue.front().ack(len, time);
if (_messageQueue.front().finished())
_messageQueue.pop_front();
}
_runQueue();
}
void AsyncEventSourceClient::_onPoll() {
#ifdef ESP32
// Same here, acquiring the lock early
std::lock_guard<std::mutex> lock(_lockmq);
#endif
if (_messageQueue.size()) {
_runQueue();
}
}
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))) {
_client->close(true); _client->close(true);
} }
void AsyncEventSourceClient::_onDisconnect(){ void AsyncEventSourceClient::_onDisconnect() {
_client = NULL; _client = NULL;
_server->_handleDisconnect(this); _server->_handleDisconnect(this);
} }
void AsyncEventSourceClient::close(){ void AsyncEventSourceClient::close() {
if(_client != NULL) if (_client != NULL)
_client->close(); _client->close();
} }
void AsyncEventSourceClient::write(const char * message, size_t len){ void AsyncEventSourceClient::write(const char* message, size_t len) {
_queueMessage(new AsyncEventSourceMessage(message, len)); if (!connected())
return;
_queueMessage(message, len);
} }
void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){ void AsyncEventSourceClient::send(const char* message, const char* event, uint32_t id, uint32_t reconnect) {
if (!connected())
return;
String ev = generateEventMessage(message, event, id, reconnect); String ev = generateEventMessage(message, event, id, reconnect);
_queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length())); _queueMessage(ev.c_str(), ev.length());
} }
size_t AsyncEventSourceClient::packetsWaiting() const { size_t AsyncEventSourceClient::packetsWaiting() const {
size_t len; #ifdef ESP32
_lockmq.lock(); std::lock_guard<std::mutex> lock(_lockmq);
len = _messageQueue.length(); #endif
_lockmq.unlock(); return _messageQueue.size();
return len;
} }
void AsyncEventSourceClient::_runQueue() { void AsyncEventSourceClient::_runQueue() {
// Calls to this private method now already protected by _lockmq acquisition // Calls to this private method now already protected by _lockmq acquisition
// so no extra call of _lockmq.lock() here.. // so no extra call of _lockmq.lock() here..
for (auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i) { for (auto& i : _messageQueue) {
// If it crashes here, iterator (i) has been invalidated as _messageQueue if (!i.sent())
// has been changed... (UL 2020-11-15: Not supposed to happen any more ;-) ) i.send(_client);
if (!(*i)->sent()) {
(*i)->send(_client);
}
} }
} }
// Handler // Handler
void AsyncEventSource::onConnect(ArEventHandlerFunction cb) {
AsyncEventSource::AsyncEventSource(const String& url)
: _url(url)
, _clients(LinkedList<AsyncEventSourceClient *>([](AsyncEventSourceClient *c){ delete c; }))
, _connectcb(NULL)
{}
AsyncEventSource::~AsyncEventSource(){
close();
}
void AsyncEventSource::onConnect(ArEventHandlerFunction cb){
_connectcb = cb; _connectcb = cb;
} }
void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb){ void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) {
_authorizeConnectHandler = cb; _authorizeConnectHandler = cb;
} }
void AsyncEventSource::_addClient(AsyncEventSourceClient * client){ void AsyncEventSource::_addClient(AsyncEventSourceClient* client) {
/*char * temp = (char *)malloc(2054); if (!client)
if(temp != NULL){ return;
memset(temp+1,' ',2048); #ifdef ESP32
temp[0] = ':'; std::lock_guard<std::mutex> lock(_client_queue_lock);
temp[2049] = '\r'; #endif
temp[2050] = '\n'; _clients.emplace_back(client);
temp[2051] = '\r'; if (_connectcb)
temp[2052] = '\n';
temp[2053] = 0;
client->write((const char *)temp, 2053);
free(temp);
}*/
AsyncWebLockGuard l(_client_queue_lock);
_clients.add(client);
if(_connectcb)
_connectcb(client); _connectcb(client);
} }
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){ void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient* client) {
AsyncWebLockGuard l(_client_queue_lock); #ifdef ESP32
_clients.remove(client); std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
for (auto i = _clients.begin(); i != _clients.end(); ++i) {
if (i->get() == client)
_clients.erase(i);
}
} }
void AsyncEventSource::close(){ void AsyncEventSource::close() {
// While the whole loop is not done, the linked list is locked and so the // While the whole loop is not done, the linked list is locked and so the
// iterator should remain valid even when AsyncEventSource::_handleDisconnect() // iterator should remain valid even when AsyncEventSource::_handleDisconnect()
// is called very early // is called very early
AsyncWebLockGuard l(_client_queue_lock); #ifdef ESP32
for(const auto &c: _clients){ std::lock_guard<std::mutex> lock(_client_queue_lock);
if(c->connected()) #endif
for (const auto& c : _clients) {
if (c->connected())
c->close(); c->close();
} }
} }
@ -332,41 +316,48 @@ void AsyncEventSource::close(){
size_t AsyncEventSource::avgPacketsWaiting() const { size_t AsyncEventSource::avgPacketsWaiting() const {
size_t aql = 0; size_t aql = 0;
uint32_t nConnectedClients = 0; uint32_t nConnectedClients = 0;
AsyncWebLockGuard l(_client_queue_lock); #ifdef ESP32
if (_clients.isEmpty()) { std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
if (!_clients.size())
return 0; return 0;
}
for(const auto &c: _clients){ for (const auto& c : _clients) {
if(c->connected()) { if (c->connected()) {
aql += c->packetsWaiting(); aql += c->packetsWaiting();
++nConnectedClients; ++nConnectedClients;
} }
} }
return ((aql) + (nConnectedClients/2)) / (nConnectedClients); // round up return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up
} }
void AsyncEventSource::send( void AsyncEventSource::send(
const char *message, const char *event, uint32_t id, uint32_t reconnect){ const char* message, const char* event, uint32_t id, uint32_t reconnect) {
String ev = generateEventMessage(message, event, id, reconnect); String ev = generateEventMessage(message, event, id, reconnect);
AsyncWebLockGuard l(_client_queue_lock); #ifdef ESP32
for(const auto &c: _clients){ std::lock_guard<std::mutex> lock(_client_queue_lock);
if(c->connected()) { #endif
for (const auto& c : _clients) {
if (c->connected()) {
c->write(ev.c_str(), ev.length()); c->write(ev.c_str(), ev.length());
} }
} }
} }
size_t AsyncEventSource::count() const { size_t AsyncEventSource::count() const {
size_t n_clients; #ifdef ESP32
AsyncWebLockGuard l(_client_queue_lock); std::lock_guard<std::mutex> lock(_client_queue_lock);
n_clients = _clients.count_if([](AsyncEventSourceClient *c){ #endif
return c->connected(); size_t n_clients{0};
}); for (const auto& i : _clients)
if (i->connected())
++n_clients;
return n_clients; return n_clients;
} }
bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){ bool AsyncEventSource::canHandle(AsyncWebServerRequest* request) {
if(request->method() != HTTP_GET || !request->url().equals(_url)) { if (request->method() != HTTP_GET || !request->url().equals(_url)) {
return false; return false;
} }
request->addInterestingHeader(F("Last-Event-ID")); request->addInterestingHeader(F("Last-Event-ID"));
@ -374,12 +365,12 @@ bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){
return true; return true;
} }
void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){ void AsyncEventSource::handleRequest(AsyncWebServerRequest* request) {
if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) { if ((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) {
return request->requestAuthentication(); return request->requestAuthentication();
} }
if(_authorizeConnectHandler != NULL){ if (_authorizeConnectHandler != NULL) {
if(!_authorizeConnectHandler(request)){ if (!_authorizeConnectHandler(request)) {
return request->send(401); return request->send(401);
} }
} }
@ -388,7 +379,7 @@ void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){
// Response // Response
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){ AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource* server) {
_server = server; _server = server;
_code = 200; _code = 200;
_contentType = F("text/event-stream"); _contentType = F("text/event-stream");
@ -397,16 +388,15 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){
addHeader(F("Connection"), F("keep-alive")); addHeader(F("Connection"), F("keep-alive"));
} }
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){ void AsyncEventSourceResponse::_respond(AsyncWebServerRequest* request) {
String out = _assembleHead(request->version()); String out = _assembleHead(request->version());
request->client()->write(out.c_str(), _headLength); request->client()->write(out.c_str(), _headLength);
_state = RESPONSE_WAIT_ACK; _state = RESPONSE_WAIT_ACK;
} }
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))){ size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time __attribute__((unused))) {
if(len){ if (len) {
new AsyncEventSourceClient(request, _server); new AsyncEventSourceClient(request, _server);
} }
return 0; return 0;
} }

View File

@ -21,129 +21,138 @@
#define ASYNCEVENTSOURCE_H_ #define ASYNCEVENTSOURCE_H_
#include <Arduino.h> #include <Arduino.h>
#include <list>
#ifdef ESP32 #ifdef ESP32
#include <AsyncTCP.h> #include <AsyncTCP.h>
#ifndef SSE_MAX_QUEUED_MESSAGES #include <mutex>
#define SSE_MAX_QUEUED_MESSAGES 32 #ifndef SSE_MAX_QUEUED_MESSAGES
#endif #define SSE_MAX_QUEUED_MESSAGES 32
#else #endif
#include <ESPAsyncTCP.h> #elif defined(ESP8266)
#ifndef SSE_MAX_QUEUED_MESSAGES #include <ESPAsyncTCP.h>
#define SSE_MAX_QUEUED_MESSAGES 8 #ifndef SSE_MAX_QUEUED_MESSAGES
#endif #define SSE_MAX_QUEUED_MESSAGES 8
#endif
#elif defined(TARGET_RP2040)
#include <AsyncTCP_RP2040W.h>
#ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 32
#endif
#endif #endif
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include "AsyncWebSynchronization.h"
#ifdef ESP8266 #ifdef ESP8266
#include <Hash.h> #include <Hash.h>
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
#include <../src/Hash.h> #include <../src/Hash.h>
#endif #endif
#endif #endif
#ifndef DEFAULT_MAX_SSE_CLIENTS #ifndef DEFAULT_MAX_SSE_CLIENTS
#ifdef ESP32 #ifdef ESP32
#define DEFAULT_MAX_SSE_CLIENTS 8 #define DEFAULT_MAX_SSE_CLIENTS 8
#else #else
#define DEFAULT_MAX_SSE_CLIENTS 4 #define DEFAULT_MAX_SSE_CLIENTS 4
#endif #endif
#endif #endif
class AsyncEventSource; class AsyncEventSource;
class AsyncEventSourceResponse; class AsyncEventSourceResponse;
class AsyncEventSourceClient; class AsyncEventSourceClient;
typedef std::function<void(AsyncEventSourceClient *client)> ArEventHandlerFunction; using ArEventHandlerFunction = std::function<void(AsyncEventSourceClient* client)>;
typedef std::function<bool(AsyncWebServerRequest *request)> ArAuthorizeConnectHandler; using ArAuthorizeConnectHandler = std::function<bool(AsyncWebServerRequest* request)>;
class AsyncEventSourceMessage { class AsyncEventSourceMessage {
private: private:
uint8_t * _data; uint8_t* _data;
size_t _len; size_t _len;
size_t _sent; size_t _sent;
//size_t _ack; // size_t _ack;
size_t _acked; size_t _acked;
public: public:
AsyncEventSourceMessage(const char * data, size_t len); AsyncEventSourceMessage(const char* data, size_t len);
~AsyncEventSourceMessage(); ~AsyncEventSourceMessage();
size_t ack(size_t len, uint32_t time __attribute__((unused))); size_t ack(size_t len, uint32_t time __attribute__((unused)));
size_t send(AsyncClient *client); size_t send(AsyncClient* client);
bool finished(){ return _acked == _len; } bool finished() { return _acked == _len; }
bool sent() { return _sent == _len; } bool sent() { return _sent == _len; }
}; };
class AsyncEventSourceClient { class AsyncEventSourceClient {
private: private:
AsyncClient *_client; AsyncClient* _client;
AsyncEventSource *_server; AsyncEventSource* _server;
uint32_t _lastId; uint32_t _lastId;
LinkedList<AsyncEventSourceMessage *> _messageQueue; std::list<AsyncEventSourceMessage> _messageQueue;
// ArFi 2020-08-27 for protecting/serializing _messageQueue #ifdef ESP32
AsyncPlainLock _lockmq; mutable std::mutex _lockmq;
void _queueMessage(AsyncEventSourceMessage *dataMessage); #endif
void _queueMessage(const char* message, size_t len);
void _runQueue(); void _runQueue();
public: public:
AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server);
AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
~AsyncEventSourceClient(); ~AsyncEventSourceClient();
AsyncClient* client(){ return _client; } AsyncClient* client() { return _client; }
void close(); void close();
void write(const char * message, size_t len); void write(const char* message, size_t len);
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); void send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
bool connected() const { return (_client != NULL) && _client->connected(); } bool connected() const { return (_client != NULL) && _client->connected(); }
uint32_t lastId() const { return _lastId; } uint32_t lastId() const { return _lastId; }
size_t packetsWaiting() const; size_t packetsWaiting() const;
//system callbacks (do not call) // system callbacks (do not call)
void _onAck(size_t len, uint32_t time); void _onAck(size_t len, uint32_t time);
void _onPoll(); void _onPoll();
void _onTimeout(uint32_t time); void _onTimeout(uint32_t time);
void _onDisconnect(); void _onDisconnect();
}; };
class AsyncEventSource: public AsyncWebHandler { class AsyncEventSource : public AsyncWebHandler {
private: private:
String _url; String _url;
LinkedList<AsyncEventSourceClient *> _clients; std::list<std::unique_ptr<AsyncEventSourceClient>> _clients;
#ifdef ESP32
// Same as for individual messages, protect mutations of _clients list // Same as for individual messages, protect mutations of _clients list
// since simultaneous access from different tasks is possible // since simultaneous access from different tasks is possible
AsyncWebLock _client_queue_lock; mutable std::mutex _client_queue_lock;
ArEventHandlerFunction _connectcb; #endif
ArAuthorizeConnectHandler _authorizeConnectHandler; ArEventHandlerFunction _connectcb{nullptr};
public: ArAuthorizeConnectHandler _authorizeConnectHandler;
AsyncEventSource(const String& url);
~AsyncEventSource();
const char * url() const { return _url.c_str(); } public:
AsyncEventSource(const String& url) : _url(url){};
~AsyncEventSource() { close(); };
const char* url() const { return _url.c_str(); }
void close(); void close();
void onConnect(ArEventHandlerFunction cb); void onConnect(ArEventHandlerFunction cb);
void authorizeConnect(ArAuthorizeConnectHandler cb); void authorizeConnect(ArAuthorizeConnectHandler cb);
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); void send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
// number of clients connected // number of clients connected
size_t count() const; size_t count() const;
size_t avgPacketsWaiting() const; size_t avgPacketsWaiting() const;
//system callbacks (do not call) // system callbacks (do not call)
void _addClient(AsyncEventSourceClient * client); void _addClient(AsyncEventSourceClient* client);
void _handleDisconnect(AsyncEventSourceClient * client); void _handleDisconnect(AsyncEventSourceClient* client);
virtual bool canHandle(AsyncWebServerRequest *request) override final; virtual bool canHandle(AsyncWebServerRequest* request) override final;
virtual void handleRequest(AsyncWebServerRequest *request) override final; virtual void handleRequest(AsyncWebServerRequest* request) override final;
}; };
class AsyncEventSourceResponse: public AsyncWebServerResponse { class AsyncEventSourceResponse : public AsyncWebServerResponse {
private: private:
String _content; String _content;
AsyncEventSource *_server; AsyncEventSource* _server;
public: public:
AsyncEventSourceResponse(AsyncEventSource *server); AsyncEventSourceResponse(AsyncEventSource* server);
void _respond(AsyncWebServerRequest *request); void _respond(AsyncWebServerRequest* request);
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
bool _sourceValid() const { return true; } bool _sourceValid() const { return true; }
}; };
#endif /* ASYNCEVENTSOURCE_H_ */ #endif /* ASYNCEVENTSOURCE_H_ */

View File

@ -39,9 +39,9 @@
#include <Print.h> #include <Print.h>
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE #ifndef DYNAMIC_JSON_DOCUMENT_SIZE
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024 #define DYNAMIC_JSON_DOCUMENT_SIZE 1024
#endif #endif
#endif #endif
constexpr const char* JSON_MIMETYPE = "application/json"; constexpr const char* JSON_MIMETYPE = "application/json";
@ -120,7 +120,6 @@ class AsyncJsonResponse : public AsyncAbstractResponse {
} }
#endif #endif
~AsyncJsonResponse() {}
JsonVariant& getRoot() { return _root; } JsonVariant& getRoot() { return _root; }
bool _sourceValid() const { return _isValid; } bool _sourceValid() const { return _isValid; }
size_t setLength() { size_t setLength() {
@ -230,7 +229,7 @@ class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
} }
virtual void handleRequest(AsyncWebServerRequest* request) override final { virtual void handleRequest(AsyncWebServerRequest* request) override final {
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
return request->requestAuthentication(); return request->requestAuthentication();
if (_onRequest) { if (_onRequest) {
if (request->method() == HTTP_GET) { if (request->method() == HTTP_GET) {

File diff suppressed because it is too large Load Diff

View File

@ -23,37 +23,42 @@
#include <Arduino.h> #include <Arduino.h>
#ifdef ESP32 #ifdef ESP32
#include <AsyncTCP.h> #include <AsyncTCP.h>
#ifndef WS_MAX_QUEUED_MESSAGES #include <mutex>
#define WS_MAX_QUEUED_MESSAGES 32 #ifndef WS_MAX_QUEUED_MESSAGES
#endif #define WS_MAX_QUEUED_MESSAGES 32
#else #endif
#include <ESPAsyncTCP.h> #elif defined(ESP8266)
#ifndef WS_MAX_QUEUED_MESSAGES #include <ESPAsyncTCP.h>
#define WS_MAX_QUEUED_MESSAGES 8 #ifndef WS_MAX_QUEUED_MESSAGES
#endif #define WS_MAX_QUEUED_MESSAGES 8
#endif
#elif defined(TARGET_RP2040)
#include <AsyncTCP_RP2040W.h>
#ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 32
#endif
#endif #endif
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include "AsyncWebSynchronization.h"
#include <list>
#include <deque> #include <deque>
#include <list>
#include <memory> #include <memory>
#ifdef ESP8266 #ifdef ESP8266
#include <Hash.h> #include <Hash.h>
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
#include <../src/Hash.h> #include <../src/Hash.h>
#endif #endif
#endif #endif
#ifndef DEFAULT_MAX_WS_CLIENTS #ifndef DEFAULT_MAX_WS_CLIENTS
#ifdef ESP32 #ifdef ESP32
#define DEFAULT_MAX_WS_CLIENTS 8 #define DEFAULT_MAX_WS_CLIENTS 8
#else #else
#define DEFAULT_MAX_WS_CLIENTS 4 #define DEFAULT_MAX_WS_CLIENTS 4
#endif #endif
#endif #endif
using AsyncWebSocketSharedBuffer = std::shared_ptr<std::vector<uint8_t>>; using AsyncWebSocketSharedBuffer = std::shared_ptr<std::vector<uint8_t>>;
@ -87,20 +92,33 @@ typedef struct {
uint64_t index; uint64_t index;
} AwsFrameInfo; } AwsFrameInfo;
typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus; typedef enum { WS_DISCONNECTED,
typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType; WS_CONNECTED,
typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus; WS_DISCONNECTING } AwsClientStatus;
typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType; typedef enum { WS_CONTINUATION,
WS_TEXT,
WS_BINARY,
WS_DISCONNECT = 0x08,
WS_PING,
WS_PONG } AwsFrameType;
typedef enum { WS_MSG_SENDING,
WS_MSG_SENT,
WS_MSG_ERROR } AwsMessageStatus;
typedef enum { WS_EVT_CONNECT,
WS_EVT_DISCONNECT,
WS_EVT_PONG,
WS_EVT_ERROR,
WS_EVT_DATA } AwsEventType;
class AsyncWebSocketMessageBuffer { class AsyncWebSocketMessageBuffer {
friend AsyncWebSocket; friend AsyncWebSocket;
friend AsyncWebSocketClient; friend AsyncWebSocketClient;
private: private:
AsyncWebSocketSharedBuffer _buffer; AsyncWebSocketSharedBuffer _buffer;
public: public:
AsyncWebSocketMessageBuffer(){} AsyncWebSocketMessageBuffer() {}
explicit AsyncWebSocketMessageBuffer(size_t size); explicit AsyncWebSocketMessageBuffer(size_t size);
AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size); AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size);
//~AsyncWebSocketMessageBuffer(); //~AsyncWebSocketMessageBuffer();
@ -109,9 +127,8 @@ class AsyncWebSocketMessageBuffer {
size_t length() const { return _buffer->size(); } size_t length() const { return _buffer->size(); }
}; };
class AsyncWebSocketMessage class AsyncWebSocketMessage {
{ private:
private:
AsyncWebSocketSharedBuffer _WSbuffer; AsyncWebSocketSharedBuffer _WSbuffer;
uint8_t _opcode{WS_TEXT}; uint8_t _opcode{WS_TEXT};
bool _mask{false}; bool _mask{false};
@ -120,25 +137,25 @@ private:
size_t _ack{}; size_t _ack{};
size_t _acked{}; size_t _acked{};
public: public:
AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode=WS_TEXT, bool mask=false); AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
bool finished() const { return _status != WS_MSG_SENDING; } bool finished() const { return _status != WS_MSG_SENDING; }
bool betweenFrames() const { return _acked == _ack; } bool betweenFrames() const { return _acked == _ack; }
void ack(size_t len, uint32_t time); void ack(size_t len, uint32_t time);
size_t send(AsyncClient *client); size_t send(AsyncClient* client);
}; };
class AsyncWebSocketClient { class AsyncWebSocketClient {
private: private:
AsyncClient *_client; AsyncClient* _client;
AsyncWebSocket *_server; AsyncWebSocket* _server;
uint32_t _clientId; uint32_t _clientId;
AwsClientStatus _status; AwsClientStatus _status;
#ifdef ESP32
AsyncWebLock _lock; mutable std::mutex _lock;
#endif
std::deque<AsyncWebSocketControl> _controlQueue; std::deque<AsyncWebSocketControl> _controlQueue;
std::deque<AsyncWebSocketMessage> _messageQueue; std::deque<AsyncWebSocketMessage> _messageQueue;
bool closeWhenFull = true; bool closeWhenFull = true;
@ -149,25 +166,25 @@ class AsyncWebSocketClient {
uint32_t _lastMessageTime; uint32_t _lastMessageTime;
uint32_t _keepAlivePeriod; uint32_t _keepAlivePeriod;
void _queueControl(uint8_t opcode, const uint8_t *data=NULL, size_t len=0, bool mask=false); void _queueControl(uint8_t opcode, const uint8_t* data = NULL, size_t len = 0, bool mask = false);
void _queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode=WS_TEXT, bool mask=false); void _queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
void _runQueue(); void _runQueue();
void _clearQueue(); void _clearQueue();
public: public:
void *_tempObject; void* _tempObject;
AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server); AsyncWebSocketClient(AsyncWebServerRequest* request, AsyncWebSocket* server);
~AsyncWebSocketClient(); ~AsyncWebSocketClient();
//client id increments for the given server // client id increments for the given server
uint32_t id() const { return _clientId; } uint32_t id() const { return _clientId; }
AwsClientStatus status() const { return _status; } AwsClientStatus status() const { return _status; }
AsyncClient* client() { return _client; } AsyncClient* client() { return _client; }
const AsyncClient* client() const { return _client; } const AsyncClient* client() const { return _client; }
AsyncWebSocket *server(){ return _server; } AsyncWebSocket* server() { return _server; }
const AsyncWebSocket *server() const { return _server; } const AsyncWebSocket* server() const { return _server; }
AwsFrameInfo const &pinfo() const { return _pinfo; } AwsFrameInfo const& pinfo() const { return _pinfo; }
// - If "true" (default), the connection will be closed if the message queue is full. // - 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. // This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection.
@ -191,181 +208,183 @@ class AsyncWebSocketClient {
bool willCloseClientOnQueueFull() const { return closeWhenFull; } bool willCloseClientOnQueueFull() const { return closeWhenFull; }
IPAddress remoteIP() const; IPAddress remoteIP() const;
uint16_t remotePort() const; uint16_t remotePort() const;
bool shouldBeDeleted() const { return !_client; } bool shouldBeDeleted() const { return !_client; }
//control frames // control frames
void close(uint16_t code=0, const char * message=NULL); void close(uint16_t code = 0, const char* message = NULL);
void ping(const uint8_t *data=NULL, size_t len=0); void ping(const uint8_t* data = NULL, size_t len = 0);
//set auto-ping period in seconds. disabled if zero (default) // set auto-ping period in seconds. disabled if zero (default)
void keepAlivePeriod(uint16_t seconds){ void keepAlivePeriod(uint16_t seconds) {
_keepAlivePeriod = seconds * 1000; _keepAlivePeriod = seconds * 1000;
} }
uint16_t keepAlivePeriod(){ uint16_t keepAlivePeriod() {
return (uint16_t)(_keepAlivePeriod / 1000); return (uint16_t)(_keepAlivePeriod / 1000);
} }
//data packets // 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; bool queueIsFull() const;
size_t queueLen() const; size_t queueLen() const;
size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3))); size_t printf(const char* format, ...) __attribute__((format(printf, 2, 3)));
#ifndef ESP32 #ifndef ESP32
size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
#endif #endif
void text(AsyncWebSocketSharedBuffer buffer); void text(AsyncWebSocketSharedBuffer buffer);
void text(const uint8_t *message, size_t len); void text(const uint8_t* message, size_t len);
void text(const char *message, size_t len); void text(const char* message, size_t len);
void text(const char *message); void text(const char* message);
void text(const String &message); void text(const String& message);
#ifndef ESP32 #ifndef ESP32
void text(const __FlashStringHelper *message); void text(const __FlashStringHelper* message);
#endif // ESP32 #endif // ESP32
void text(AsyncWebSocketMessageBuffer *buffer); void text(AsyncWebSocketMessageBuffer* buffer);
void binary(AsyncWebSocketSharedBuffer buffer); void binary(AsyncWebSocketSharedBuffer buffer);
void binary(const uint8_t *message, size_t len); void binary(const uint8_t* message, size_t len);
void binary(const char * message, size_t len); void binary(const char* message, size_t len);
void binary(const char * message); void binary(const char* message);
void binary(const String &message); void binary(const String& message);
#ifndef ESP32 #ifndef ESP32
void binary(const __FlashStringHelper *message, size_t len); void binary(const __FlashStringHelper* message, size_t len);
#endif // ESP32 #endif // ESP32
void binary(AsyncWebSocketMessageBuffer *buffer); void binary(AsyncWebSocketMessageBuffer* buffer);
bool canSend() const; bool canSend() const;
//system callbacks (do not call) // system callbacks (do not call)
void _onAck(size_t len, uint32_t time); void _onAck(size_t len, uint32_t time);
void _onError(int8_t); void _onError(int8_t);
void _onPoll(); void _onPoll();
void _onTimeout(uint32_t time); void _onTimeout(uint32_t time);
void _onDisconnect(); void _onDisconnect();
void _onData(void *pbuf, size_t plen); void _onData(void* pbuf, size_t plen);
}; };
using AwsHandshakeHandler = std::function<bool(AsyncWebServerRequest *request)>; using AwsHandshakeHandler = std::function<bool(AsyncWebServerRequest* request)>;
using AwsEventHandler = std::function<void(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)>; using AwsEventHandler = std::function<void(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)>;
//WebServer Handler implementation that plays the role of a socket server // WebServer Handler implementation that plays the role of a socket server
class AsyncWebSocket: public AsyncWebHandler { class AsyncWebSocket : public AsyncWebHandler {
private: private:
String _url; String _url;
std::list<AsyncWebSocketClient> _clients; std::list<AsyncWebSocketClient> _clients;
uint32_t _cNextId; uint32_t _cNextId;
AwsEventHandler _eventHandler{nullptr}; AwsEventHandler _eventHandler{nullptr};
AwsHandshakeHandler _handshakeHandler; AwsHandshakeHandler _handshakeHandler;
bool _enabled; bool _enabled;
AsyncWebLock _lock; #ifdef ESP32
mutable std::mutex _lock;
#endif
public: public:
explicit AsyncWebSocket(const char* url) : _url(url) ,_cNextId(1), _enabled(true) {} explicit AsyncWebSocket(const char* url) : _url(url), _cNextId(1), _enabled(true) {}
AsyncWebSocket(const String& url) :_url(url) ,_cNextId(1),_enabled(true) {} AsyncWebSocket(const String& url) : _url(url), _cNextId(1), _enabled(true) {}
~AsyncWebSocket(){}; ~AsyncWebSocket(){};
const char * url() const { return _url.c_str(); } const char* url() const { return _url.c_str(); }
void enable(bool e){ _enabled = e; } void enable(bool e) { _enabled = e; }
bool enabled() const { return _enabled; } bool enabled() const { return _enabled; }
bool availableForWriteAll(); bool availableForWriteAll();
bool availableForWrite(uint32_t id); bool availableForWrite(uint32_t id);
size_t count() const; size_t count() const;
AsyncWebSocketClient * client(uint32_t id); 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 close(uint32_t id, uint16_t code = 0, const char* message = NULL);
void closeAll(uint16_t code=0, const char * message=NULL); void closeAll(uint16_t code = 0, const char* message = NULL);
void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS); void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);
void ping(uint32_t id, const uint8_t *data=NULL, size_t len=0); void ping(uint32_t id, const uint8_t* data = NULL, size_t len = 0);
void pingAll(const uint8_t *data=NULL, size_t len=0); // done void pingAll(const uint8_t* data = NULL, size_t len = 0); // done
void text(uint32_t id, const uint8_t * message, size_t len); void text(uint32_t id, const uint8_t* message, size_t len);
void text(uint32_t id, const char *message, size_t len); void text(uint32_t id, const char* message, size_t len);
void text(uint32_t id, const char *message); void text(uint32_t id, const char* message);
void text(uint32_t id, const String &message); void text(uint32_t id, const String& message);
#ifndef ESP32 #ifndef ESP32
void text(uint32_t id, const __FlashStringHelper *message); void text(uint32_t id, const __FlashStringHelper* message);
#endif // ESP32 #endif // ESP32
void text(uint32_t id, AsyncWebSocketMessageBuffer *buffer); void text(uint32_t id, AsyncWebSocketMessageBuffer* buffer);
void text(uint32_t id, AsyncWebSocketSharedBuffer buffer); void text(uint32_t id, AsyncWebSocketSharedBuffer buffer);
void textAll(const uint8_t *message, size_t len); void textAll(const uint8_t* message, size_t len);
void textAll(const char * message, size_t len); void textAll(const char* message, size_t len);
void textAll(const char * message); void textAll(const char* message);
void textAll(const String &message); void textAll(const String& message);
#ifndef ESP32 #ifndef ESP32
void textAll(const __FlashStringHelper *message); void textAll(const __FlashStringHelper* message);
#endif // ESP32 #endif // ESP32
void textAll(AsyncWebSocketMessageBuffer *buffer); void textAll(AsyncWebSocketMessageBuffer* buffer);
void textAll(AsyncWebSocketSharedBuffer buffer); void textAll(AsyncWebSocketSharedBuffer buffer);
void binary(uint32_t id, const uint8_t *message, size_t len); void binary(uint32_t id, const uint8_t* message, size_t len);
void binary(uint32_t id, const char *message, size_t len); void binary(uint32_t id, const char* message, size_t len);
void binary(uint32_t id, const char *message); void binary(uint32_t id, const char* message);
void binary(uint32_t id, const String &message); void binary(uint32_t id, const String& message);
#ifndef ESP32 #ifndef ESP32
void binary(uint32_t id, const __FlashStringHelper *message, size_t len); void binary(uint32_t id, const __FlashStringHelper* message, size_t len);
#endif // ESP32 #endif // ESP32
void binary(uint32_t id, AsyncWebSocketMessageBuffer *buffer); void binary(uint32_t id, AsyncWebSocketMessageBuffer* buffer);
void binary(uint32_t id, AsyncWebSocketSharedBuffer buffer); void binary(uint32_t id, AsyncWebSocketSharedBuffer buffer);
void binaryAll(const uint8_t *message, size_t len); void binaryAll(const uint8_t* message, size_t len);
void binaryAll(const char *message, size_t len); void binaryAll(const char* message, size_t len);
void binaryAll(const char *message); void binaryAll(const char* message);
void binaryAll(const String &message); void binaryAll(const String& message);
#ifndef ESP32 #ifndef ESP32
void binaryAll(const __FlashStringHelper *message, size_t len); void binaryAll(const __FlashStringHelper* message, size_t len);
#endif // ESP32 #endif // ESP32
void binaryAll(AsyncWebSocketMessageBuffer *buffer); void binaryAll(AsyncWebSocketMessageBuffer* buffer);
void binaryAll(AsyncWebSocketSharedBuffer buffer); void binaryAll(AsyncWebSocketSharedBuffer buffer);
size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4))); size_t printf(uint32_t id, const char* format, ...) __attribute__((format(printf, 3, 4)));
size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3))); size_t printfAll(const char* format, ...) __attribute__((format(printf, 2, 3)));
#ifndef ESP32
size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__ ((format (printf, 3, 4)));
#endif
size_t printfAll_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3)));
//event listener #ifndef ESP32
void onEvent(AwsEventHandler handler){ size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__((format(printf, 3, 4)));
size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
#endif
// event listener
void onEvent(AwsEventHandler handler) {
_eventHandler = handler; _eventHandler = handler;
} }
// Handshake Handler // Handshake Handler
void handleHandshake(AwsHandshakeHandler handler){ void handleHandshake(AwsHandshakeHandler handler) {
_handshakeHandler = handler; _handshakeHandler = handler;
} }
//system callbacks (do not call) // system callbacks (do not call)
uint32_t _getNextId(){ return _cNextId++; } uint32_t _getNextId() { return _cNextId++; }
AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request); AsyncWebSocketClient* _newClient(AsyncWebServerRequest* request);
void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); void _handleEvent(AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
virtual bool canHandle(AsyncWebServerRequest *request) override final; virtual bool canHandle(AsyncWebServerRequest* request) override final;
virtual void handleRequest(AsyncWebServerRequest *request) override final; virtual void handleRequest(AsyncWebServerRequest* request) override final;
// messagebuffer functions/objects. // messagebuffer functions/objects.
AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0); AsyncWebSocketMessageBuffer* makeBuffer(size_t size = 0);
AsyncWebSocketMessageBuffer * makeBuffer(const uint8_t * data, size_t size); AsyncWebSocketMessageBuffer* makeBuffer(const uint8_t* data, size_t size);
const std::list<AsyncWebSocketClient> &getClients() const { return _clients; } const std::list<AsyncWebSocketClient>& getClients() const { return _clients; }
}; };
//WebServer response to authenticate the socket and detach the tcp client from the web server request // WebServer response to authenticate the socket and detach the tcp client from the web server request
class AsyncWebSocketResponse: public AsyncWebServerResponse { class AsyncWebSocketResponse : public AsyncWebServerResponse {
private: private:
String _content; String _content;
AsyncWebSocket *_server; AsyncWebSocket* _server;
public: public:
AsyncWebSocketResponse(const String& key, AsyncWebSocket *server); AsyncWebSocketResponse(const String& key, AsyncWebSocket* server);
void _respond(AsyncWebServerRequest *request); void _respond(AsyncWebServerRequest* request);
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
bool _sourceValid() const { return true; } bool _sourceValid() const { return true; }
}; };
#endif /* ASYNCWEBSOCKET_H_ */ #endif /* ASYNCWEBSOCKET_H_ */

View File

@ -1,134 +0,0 @@
#ifndef ASYNCWEBSYNCHRONIZATION_H_
#define ASYNCWEBSYNCHRONIZATION_H_
// Synchronisation is only available on ESP32, as the ESP8266 isn't using FreeRTOS by default
#include <ESPAsyncWebServer.h>
#ifdef ESP32
// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore
// Modified 'AsyncWebLock' to just only use mutex since pxCurrentTCB is not
// always available. According to example by Arjan Filius, changed name,
// added unimplemented version for ESP8266
class AsyncPlainLock
{
private:
SemaphoreHandle_t _lock;
public:
AsyncPlainLock() {
_lock = xSemaphoreCreateBinary();
// In this fails, the system is likely that much out of memory that
// we should abort anyways. If assertions are disabled, nothing is lost..
assert(_lock);
xSemaphoreGive(_lock);
}
~AsyncPlainLock() {
vSemaphoreDelete(_lock);
}
bool lock() const {
xSemaphoreTake(_lock, portMAX_DELAY);
return true;
}
void unlock() const {
xSemaphoreGive(_lock);
}
};
// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore
class AsyncWebLock
{
private:
SemaphoreHandle_t _lock;
mutable TaskHandle_t _lockedBy{};
public:
AsyncWebLock()
{
_lock = xSemaphoreCreateBinary();
// In this fails, the system is likely that much out of memory that
// we should abort anyways. If assertions are disabled, nothing is lost..
assert(_lock);
_lockedBy = NULL;
xSemaphoreGive(_lock);
}
~AsyncWebLock() {
vSemaphoreDelete(_lock);
}
bool lock() const {
const auto currentTask = xTaskGetCurrentTaskHandle();
if (_lockedBy != currentTask) {
xSemaphoreTake(_lock, portMAX_DELAY);
_lockedBy = currentTask;
return true;
}
return false;
}
void unlock() const {
_lockedBy = NULL;
xSemaphoreGive(_lock);
}
};
#else
// This is the 8266 version of the Sync Lock which is currently unimplemented
class AsyncWebLock
{
public:
AsyncWebLock() {
}
~AsyncWebLock() {
}
bool lock() const {
return false;
}
void unlock() const {
}
};
// Same for AsyncPlainLock, for ESP8266 this is just the unimplemented version above.
using AsyncPlainLock = AsyncWebLock;
#endif
class AsyncWebLockGuard
{
private:
const AsyncWebLock *_lock;
public:
AsyncWebLockGuard(const AsyncWebLock &l) {
if (l.lock()) {
_lock = &l;
} else {
_lock = NULL;
}
}
~AsyncWebLockGuard() {
if (_lock) {
_lock->unlock();
}
}
void unlock() {
if (_lock) {
_lock->unlock();
_lock = NULL;
}
}
};
#endif // ASYNCWEBSYNCHRONIZATION_H_

View File

@ -23,33 +23,36 @@
#include "Arduino.h" #include "Arduino.h"
#include "FS.h"
#include <functional> #include <functional>
#include <list> #include <list>
#include <vector> #include <vector>
#include "FS.h"
#include "StringArray.h"
#ifdef ESP32 #ifdef ESP32
#include <WiFi.h> #include <AsyncTCP.h>
#include <AsyncTCP.h> #include <WiFi.h>
#elif defined(ESP8266) #elif defined(ESP8266)
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h> #include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040)
#include <WiFi.h>
#include <AsyncTCP_RP2040W.h>
#include <http_parser.h>
#include <HTTP_Method.h>
#else #else
#error Platform not supported #error Platform not supported
#endif #endif
#define ASYNCWEBSERVER_VERSION "2.10.8" #define ASYNCWEBSERVER_VERSION "3.0.6"
#define ASYNCWEBSERVER_VERSION_MAJOR 2 #define ASYNCWEBSERVER_VERSION_MAJOR 3
#define ASYNCWEBSERVER_VERSION_MINOR 10 #define ASYNCWEBSERVER_VERSION_MINOR 0
#define ASYNCWEBSERVER_VERSION_REVISION 8 #define ASYNCWEBSERVER_VERSION_REVISION 6
#define ASYNCWEBSERVER_FORK_mathieucarbou #define ASYNCWEBSERVER_FORK_mathieucarbou
#ifdef ASYNCWEBSERVER_REGEX #ifdef ASYNCWEBSERVER_REGEX
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE #define ASYNCWEBSERVER_REGEX_ATTRIBUTE
#else #else
#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined"))) #define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined")))
#endif #endif
class AsyncWebServer; class AsyncWebServer;
@ -63,33 +66,37 @@ class AsyncStaticWebHandler;
class AsyncCallbackWebHandler; class AsyncCallbackWebHandler;
class AsyncResponseStream; class AsyncResponseStream;
#ifndef WEBSERVER_H #if defined (TARGET_RP2040)
typedef enum { typedef enum http_method WebRequestMethod;
HTTP_GET = 0b00000001, #else
HTTP_POST = 0b00000010, #ifndef WEBSERVER_H
HTTP_DELETE = 0b00000100, typedef enum {
HTTP_PUT = 0b00001000, HTTP_GET = 0b00000001,
HTTP_PATCH = 0b00010000, HTTP_POST = 0b00000010,
HTTP_HEAD = 0b00100000, HTTP_DELETE = 0b00000100,
HTTP_OPTIONS = 0b01000000, HTTP_PUT = 0b00001000,
HTTP_ANY = 0b01111111, HTTP_PATCH = 0b00010000,
} WebRequestMethod; HTTP_HEAD = 0b00100000,
HTTP_OPTIONS = 0b01000000,
HTTP_ANY = 0b01111111,
} WebRequestMethod;
#endif
#endif #endif
#ifndef HAVE_FS_FILE_OPEN_MODE #ifndef HAVE_FS_FILE_OPEN_MODE
namespace fs { namespace fs {
class FileOpenMode { class FileOpenMode {
public: public:
static const char *read; static const char* read;
static const char *write; static const char* write;
static const char *append; static const char* append;
}; };
}; };
#else #else
#include "FileOpenMode.h" #include "FileOpenMode.h"
#endif #endif
//if this value is returned when asked for data, packet will not be sent and you will be asked for data again // if this value is returned when asked for data, packet will not be sent and you will be asked for data again
#define RESPONSE_TRY_AGAIN 0xFFFFFFFF #define RESPONSE_TRY_AGAIN 0xFFFFFFFF
typedef uint8_t WebRequestMethodComposite; typedef uint8_t WebRequestMethodComposite;
@ -108,8 +115,7 @@ class AsyncWebParameter {
bool _isFile; bool _isFile;
public: 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) {}
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& name() const { return _name; }
const String& value() const { return _value; } const String& value() const { return _value; }
size_t size() const { return _size; } size_t size() const { return _size; }
@ -128,18 +134,20 @@ class AsyncWebHeader {
public: public:
AsyncWebHeader() = default; AsyncWebHeader() = default;
AsyncWebHeader(const AsyncWebHeader &) = default; AsyncWebHeader(const AsyncWebHeader&) = default;
AsyncWebHeader(const String& name, const String& value): _name(name), _value(value){} AsyncWebHeader(const String& name, const String& value) : _name(name), _value(value) {}
AsyncWebHeader(const String& data): _name(), _value(){ AsyncWebHeader(const String& data) {
if(!data) return; if (!data)
return;
int index = data.indexOf(':'); int index = data.indexOf(':');
if (index < 0) return; if (index < 0)
return;
_name = data.substring(0, index); _name = data.substring(0, index);
_value = data.substring(index + 2); _value = data.substring(index + 2);
} }
AsyncWebHeader &operator=(const AsyncWebHeader &) = default; AsyncWebHeader& operator=(const AsyncWebHeader&) = default;
const String& name() const { return _name; } const String& name() const { return _name; }
const String& value() const { return _value; } const String& value() const { return _value; }
@ -150,16 +158,22 @@ class AsyncWebHeader {
* REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect * REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect
* */ * */
typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, RCT_MAX } RequestedConnectionType; typedef enum { RCT_NOT_USED = -1,
RCT_DEFAULT = 0,
RCT_HTTP,
RCT_WS,
RCT_EVENT,
RCT_MAX } RequestedConnectionType;
typedef std::function<size_t(uint8_t*, size_t, size_t)> AwsResponseFiller; typedef std::function<size_t(uint8_t*, size_t, size_t)> AwsResponseFiller;
typedef std::function<String(const String&)> AwsTemplateProcessor; typedef std::function<String(const String&)> AwsTemplateProcessor;
class AsyncWebServerRequest { class AsyncWebServerRequest {
using File = fs::File; using File = fs::File;
using FS = fs::FS; using FS = fs::FS;
friend class AsyncWebServer; friend class AsyncWebServer;
friend class AsyncCallbackWebHandler; friend class AsyncCallbackWebHandler;
private: private:
AsyncClient* _client; AsyncClient* _client;
AsyncWebServer* _server; AsyncWebServer* _server;
@ -188,7 +202,7 @@ class AsyncWebServerRequest {
size_t _parsedLength; size_t _parsedLength;
std::list<AsyncWebHeader> _headers; std::list<AsyncWebHeader> _headers;
LinkedList<AsyncWebParameter *> _params; std::list<AsyncWebParameter> _params;
std::vector<String> _pathParams; std::vector<String> _pathParams;
uint8_t _multiParseState; uint8_t _multiParseState;
@ -199,7 +213,7 @@ class AsyncWebServerRequest {
String _itemFilename; String _itemFilename;
String _itemType; String _itemType;
String _itemValue; String _itemValue;
uint8_t *_itemBuffer; uint8_t* _itemBuffer;
size_t _itemBufferIndex; size_t _itemBufferIndex;
bool _itemIsFile; bool _itemIsFile;
@ -208,10 +222,9 @@ class AsyncWebServerRequest {
void _onError(int8_t error); void _onError(int8_t error);
void _onTimeout(uint32_t time); void _onTimeout(uint32_t time);
void _onDisconnect(); void _onDisconnect();
void _onData(void *buf, size_t len); void _onData(void* buf, size_t len);
void _addParam(AsyncWebParameter*); void _addPathParam(const char* param);
void _addPathParam(const char *param);
bool _parseReqHead(); bool _parseReqHead();
bool _parseReqHeader(); bool _parseReqHeader();
@ -226,12 +239,12 @@ class AsyncWebServerRequest {
public: public:
File _tempFile; File _tempFile;
void *_tempObject; void* _tempObject;
AsyncWebServerRequest(AsyncWebServer*, AsyncClient*); AsyncWebServerRequest(AsyncWebServer*, AsyncClient*);
~AsyncWebServerRequest(); ~AsyncWebServerRequest();
AsyncClient* client(){ return _client; } AsyncClient* client() { return _client; }
uint8_t version() const { return _version; } uint8_t version() const { return _version; }
WebRequestMethodComposite method() const { return _method; } WebRequestMethodComposite method() const { return _method; }
const String& url() const { return _url; } const String& url() const { return _url; }
@ -239,77 +252,156 @@ class AsyncWebServerRequest {
const String& contentType() const { return _contentType; } const String& contentType() const { return _contentType; }
size_t contentLength() const { return _contentLength; } size_t contentLength() const { return _contentLength; }
bool multipart() const { return _isMultipart; } bool multipart() const { return _isMultipart; }
const __FlashStringHelper *methodToString() const;
const __FlashStringHelper *requestedConnTypeToString() const; #ifndef ESP8266
const char* methodToString() const;
const char* requestedConnTypeToString() const;
#else
const __FlashStringHelper* methodToString() const;
const __FlashStringHelper* requestedConnTypeToString() const;
#endif
RequestedConnectionType requestedConnType() const { return _reqconntype; } RequestedConnectionType requestedConnType() const { return _reqconntype; }
bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED); bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED);
void onDisconnect (ArDisconnectHandler fn); void onDisconnect(ArDisconnectHandler fn);
//hash is the string representation of: // hash is the string representation of:
// base64(user:pass) for basic or // base64(user:pass) for basic or
// user:realm:md5(user:realm:pass) for digest // user:realm:md5(user:realm:pass) for digest
bool authenticate(const char * hash); bool authenticate(const char* hash);
bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false); bool authenticate(const char* username, const char* password, const char* realm = NULL, bool passwordIsHash = false);
void requestAuthentication(const char * realm = NULL, bool isDigest = true); void requestAuthentication(const char* realm = NULL, bool isDigest = true);
void setHandler(AsyncWebHandler *handler){ _handler = handler; } void setHandler(AsyncWebHandler* handler) { _handler = handler; }
void addInterestingHeader(const String& name);
void redirect(const String& url); /**
* @brief add header to collect from a response
*
* @param name
*/
void addInterestingHeader(const char* name);
void addInterestingHeader(const String& name) { return addInterestingHeader(name.c_str()); };
void send(AsyncWebServerResponse *response); /**
void send(int code, const String& contentType=String(), const String& content=String()); * @brief issue 302 redirect response
void send(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); *
void send(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); * @param url
void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); */
void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); void redirect(const char* url);
void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); void redirect(const String& url) { return redirect(url.c_str()); };
void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr);
void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr);
AsyncWebServerResponse *beginResponse(int code, const String& contentType=String(), const String& content=String()); void send(AsyncWebServerResponse* response);
AsyncWebServerResponse *beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); void send(int code, const String& contentType = String(), const String& content = String());
AsyncWebServerResponse *beginResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); void send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); void send(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); void send(FS& fs, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); void send(File content, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncResponseStream *beginResponseStream(const String& contentType, size_t bufferSize=1460); void send(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
size_t headers() const; // get header count [[deprecated("Replaced by send(...)")]]
bool hasHeader(const String& name) const; // check if header exists void send_P(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) {
bool hasHeader(const __FlashStringHelper * data) const; // check if header exists send(code, contentType, content, len, callback);
}
[[deprecated("Replaced by send(...)")]]
void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr) {
send(code, contentType, content, callback);
}
AsyncWebHeader* getHeader(const String& name); AsyncWebServerResponse* beginResponse(int code, const String& contentType = String(), const String& content = String());
const AsyncWebHeader* getHeader(const String& name) const; AsyncWebServerResponse* beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncWebHeader* getHeader(const __FlashStringHelper * data); AsyncWebServerResponse* beginResponse(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);
const AsyncWebHeader* getHeader(const __FlashStringHelper * data) const; AsyncWebServerResponse* beginResponse(FS& fs, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncWebHeader* getHeader(size_t num); AsyncWebServerResponse* beginResponse(File content, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse* beginResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse* beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncWebServerResponse* beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncResponseStream* beginResponseStream(const String& contentType, size_t bufferSize = 1460);
[[deprecated("Replaced by beginResponse(...)")]]
AsyncWebServerResponse* beginResponse_P(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) {
return beginResponse(code, contentType, content, len, callback);
}
[[deprecated("Replaced by beginResponse(...)")]]
AsyncWebServerResponse* beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr) {
return beginResponse(code, contentType, content, callback);
}
size_t headers() const; // get header count
// check if header exists
bool hasHeader(const char* name) const;
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()); };
#ifdef ESP8266
const AsyncWebHeader* getHeader(const __FlashStringHelper* data) const;
#endif
const AsyncWebHeader* getHeader(size_t num) const; const AsyncWebHeader* getHeader(size_t num) const;
size_t params() const; // get arguments count size_t params() const; // get arguments count
bool hasParam(const String& name, bool post=false, bool file=false) const; bool hasParam(const String& name, bool post = false, bool file = false) const;
bool hasParam(const __FlashStringHelper * data, bool post=false, bool file=false) const; bool hasParam(const __FlashStringHelper* data, bool post = false, bool file = false) const;
AsyncWebParameter* getParam(const String& name, bool post=false, bool file=false) const; /**
AsyncWebParameter* getParam(const __FlashStringHelper * data, bool post, bool file) const; * @brief Get the Request parameter by name
AsyncWebParameter* getParam(size_t num) const; *
* @param name
* @param post
* @param file
* @return const AsyncWebParameter*
*/
const AsyncWebParameter* getParam(const char* name, bool post = false, bool file = false) const;
size_t args() const { return params(); } // get arguments count const AsyncWebParameter* getParam(const String& name, bool post = false, bool file = false) const { return getParam(name.c_str(), post, file); };
const String& arg(const String& name) const; // get request argument value by name #ifdef ESP8266
const String& arg(const __FlashStringHelper * data) const; // get request argument value by F(name) const AsyncWebParameter* getParam(const __FlashStringHelper* data, bool post, bool file) const;
const String& arg(size_t i) const; // get request argument value by number #endif
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 __FlashStringHelper * data) const; // check if F(argument) exists * @brief Get request parameter by number
* i.e., n-th parameter
* @param num
* @return const AsyncWebParameter*
*/
const AsyncWebParameter* getParam(size_t num) const;
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()); };
#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()); };
#ifdef ESP8266
bool hasArg(const __FlashStringHelper* data) const; // check if F(argument) exists
#endif
const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const; const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const;
const String& header(const char* name) const;// get request header value by name // get request header value by name
const String& header(const __FlashStringHelper * data) const;// get request header value by F(name) const String& header(const char* name) const;
const String& header(size_t i) const; // get request header value by number const String& header(const String& name) const { return header(name.c_str()); };
const String& headerName(size_t i) const; // get request header name by number
#ifdef ESP8266
const String& header(const __FlashStringHelper* data) const; // get request header value by F(name)
#endif
const String& header(size_t i) const; // get request header value by number
const String& headerName(size_t i) const; // get request header name by number
String urlDecode(const String& text) const; String urlDecode(const String& text) const;
}; };
@ -317,11 +409,11 @@ class AsyncWebServerRequest {
* FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server) * FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server)
* */ * */
typedef std::function<bool(AsyncWebServerRequest *request)> ArRequestFilterFunction; using ArRequestFilterFunction = std::function<bool(AsyncWebServerRequest* request)>;
bool ON_STA_FILTER(AsyncWebServerRequest *request); bool ON_STA_FILTER(AsyncWebServerRequest* request);
bool ON_AP_FILTER(AsyncWebServerRequest *request); bool ON_AP_FILTER(AsyncWebServerRequest* request);
/* /*
* REWRITE :: One instance can be handle any Request (done by the Server) * REWRITE :: One instance can be handle any Request (done by the Server)
@ -332,22 +424,26 @@ class AsyncWebRewrite {
String _from; String _from;
String _toUrl; String _toUrl;
String _params; String _params;
ArRequestFilterFunction _filter; ArRequestFilterFunction _filter{nullptr};
public: public:
AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL){ AsyncWebRewrite(const char* from, const char* to) : _from(from), _toUrl(to) {
int index = _toUrl.indexOf('?'); int index = _toUrl.indexOf('?');
if (index > 0) { if (index > 0) {
_params = _toUrl.substring(index +1); _params = _toUrl.substring(index + 1);
_toUrl = _toUrl.substring(0, index); _toUrl = _toUrl.substring(0, index);
} }
} }
virtual ~AsyncWebRewrite(){} virtual ~AsyncWebRewrite() {}
AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) {
bool filter(AsyncWebServerRequest *request) const { return _filter == NULL || _filter(request); } _filter = fn;
return *this;
}
bool filter(AsyncWebServerRequest* request) const { return _filter == NULL || _filter(request); }
const String& from(void) const { return _from; } const String& from(void) const { return _from; }
const String& toUrl(void) const { return _toUrl; } const String& toUrl(void) const { return _toUrl; }
const String& params(void) const { return _params; } const String& params(void) const { return _params; }
virtual bool match(AsyncWebServerRequest *request) { return from() == request->url() && filter(request); } virtual bool match(AsyncWebServerRequest* request) { return from() == request->url() && filter(request); }
}; };
/* /*
@ -356,23 +452,35 @@ class AsyncWebRewrite {
class AsyncWebHandler { class AsyncWebHandler {
protected: protected:
ArRequestFilterFunction _filter; ArRequestFilterFunction _filter{nullptr};
String _username; String _username;
String _password; String _password;
public: public:
AsyncWebHandler():_username(""), _password(""){} AsyncWebHandler() {}
AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } AsyncWebHandler& setFilter(ArRequestFilterFunction fn) {
AsyncWebHandler& setAuthentication(const char *username, const char *password){ _username = String(username);_password = String(password); return *this; }; _filter = fn;
AsyncWebHandler& setAuthentication(const String& username, const String& password){ _username = username;_password = password; return *this; }; return *this;
bool filter(AsyncWebServerRequest *request){ return _filter == NULL || _filter(request); } }
virtual ~AsyncWebHandler(){} AsyncWebHandler& setAuthentication(const char* username, const char* password) {
virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))){ _username = username;
_password = password;
return *this;
};
AsyncWebHandler& setAuthentication(const String& username, const String& password) {
_username = username;
_password = password;
return *this;
};
bool filter(AsyncWebServerRequest* request) { return _filter == NULL || _filter(request); }
virtual ~AsyncWebHandler() {}
virtual bool canHandle(AsyncWebServerRequest* request __attribute__((unused))) {
return false; return false;
} }
virtual void handleRequest(AsyncWebServerRequest *request __attribute__((unused))){} virtual void handleRequest(AsyncWebServerRequest* request __attribute__((unused))) {}
virtual void handleUpload(AsyncWebServerRequest *request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))){} virtual void handleUpload(AsyncWebServerRequest* request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t* data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))) {}
virtual void handleBody(AsyncWebServerRequest *request __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))){} virtual void handleBody(AsyncWebServerRequest* request __attribute__((unused)), uint8_t* data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))) {}
virtual bool isRequestHandlerTrivial(){return true;} virtual bool isRequestHandlerTrivial() { return true; }
}; };
/* /*
@ -380,7 +488,12 @@ class AsyncWebHandler {
* */ * */
typedef enum { typedef enum {
RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED RESPONSE_SETUP,
RESPONSE_HEADERS,
RESPONSE_CONTENT,
RESPONSE_WAIT_ACK,
RESPONSE_END,
RESPONSE_FAILED
} WebResponseState; } WebResponseState;
class AsyncWebServerResponse { class AsyncWebServerResponse {
@ -397,8 +510,9 @@ class AsyncWebServerResponse {
size_t _writtenLength; size_t _writtenLength;
WebResponseState _state; WebResponseState _state;
const char* _responseCodeToString(int code); const char* _responseCodeToString(int code);
public:
static const __FlashStringHelper *responseCodeToString(int code); public:
static const __FlashStringHelper* responseCodeToString(int code);
public: public:
AsyncWebServerResponse(); AsyncWebServerResponse();
@ -412,23 +526,23 @@ public:
virtual bool _finished() const; virtual bool _finished() const;
virtual bool _failed() const; virtual bool _failed() const;
virtual bool _sourceValid() const; virtual bool _sourceValid() const;
virtual void _respond(AsyncWebServerRequest *request); virtual void _respond(AsyncWebServerRequest* request);
virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); virtual size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
}; };
/* /*
* SERVER :: One instance * SERVER :: One instance
* */ * */
typedef std::function<void(AsyncWebServerRequest *request)> ArRequestHandlerFunction; 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; typedef std::function<void(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction;
class AsyncWebServer { class AsyncWebServer {
protected: protected:
AsyncServer _server; AsyncServer _server;
LinkedList<AsyncWebRewrite*> _rewrites; std::list<std::shared_ptr<AsyncWebRewrite>> _rewrites;
LinkedList<AsyncWebHandler*> _handlers; std::list<std::unique_ptr<AsyncWebHandler>> _handlers;
AsyncCallbackWebHandler* _catchAllHandler; AsyncCallbackWebHandler* _catchAllHandler;
public: public:
@ -440,13 +554,50 @@ class AsyncWebServer {
#if ASYNC_TCP_SSL_ENABLED #if ASYNC_TCP_SSL_ENABLED
void onSslFileRequest(AcSSlFileHandler cb, void* arg); void onSslFileRequest(AcSSlFileHandler cb, void* arg);
void beginSecure(const char *cert, const char *private_key_file, const char *password); void beginSecure(const char* cert, const char* private_key_file, const char* password);
#endif #endif
AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite); AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite);
bool removeRewrite(AsyncWebRewrite* rewrite);
/**
* @brief (compat) Add url rewrite rule by pointer
* a deep copy of the pounter object will be created,
* it is up to user to manage further lifetime of the object in argument
*
* @param rewrite pointer to rewrite object to copy setting from
* @return AsyncWebRewrite& reference to a newly created rewrite rule
*/
AsyncWebRewrite& addRewrite(std::shared_ptr<AsyncWebRewrite> rewrite);
/**
* @brief add url rewrite rule
*
* @param from
* @param to
* @return AsyncWebRewrite&
*/
AsyncWebRewrite& rewrite(const char* from, const char* to); AsyncWebRewrite& rewrite(const char* from, const char* to);
/**
* @brief (compat) remove rewrite rule via referenced object
* this will NOT deallocate pointed object itself, internal rule with same from/to urls will be removed if any
* it's a compat method, better use `removeRewrite(const char* from, const char* to)`
* @param rewrite
* @return true
* @return false
*/
bool removeRewrite(AsyncWebRewrite* rewrite);
/**
* @brief remove rewrite rule
*
* @param from
* @param to
* @return true
* @return false
*/
bool removeRewrite(const char* from, const char* to);
AsyncWebHandler& addHandler(AsyncWebHandler* handler); AsyncWebHandler& addHandler(AsyncWebHandler* handler);
bool removeHandler(AsyncWebHandler* handler); bool removeHandler(AsyncWebHandler* handler);
@ -457,45 +608,45 @@ class AsyncWebServer {
AsyncStaticWebHandler& serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL); 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 onNotFound(ArRequestHandlerFunction fn); // called when handler is not assigned
void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads 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) void onRequestBody(ArBodyHandlerFunction fn); // handle posts with plain body content (JSON often transmitted this way as a request)
void reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody void reset(); // remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody
void _handleDisconnect(AsyncWebServerRequest *request); void _handleDisconnect(AsyncWebServerRequest* request);
void _attachHandler(AsyncWebServerRequest *request); void _attachHandler(AsyncWebServerRequest* request);
void _rewriteRequest(AsyncWebServerRequest *request); void _rewriteRequest(AsyncWebServerRequest* request);
}; };
class DefaultHeaders { class DefaultHeaders {
using headers_t = std::list<AsyncWebHeader>; using headers_t = std::list<AsyncWebHeader>;
headers_t _headers; headers_t _headers;
public: public:
DefaultHeaders() = default; DefaultHeaders() = default;
using ConstIterator = headers_t::const_iterator; using ConstIterator = headers_t::const_iterator;
void addHeader(const String& name, const String& value){ void addHeader(const String& name, const String& value) {
_headers.emplace_back(name, value); _headers.emplace_back(name, value);
} }
ConstIterator begin() const { return _headers.begin(); } ConstIterator begin() const { return _headers.begin(); }
ConstIterator end() const { return _headers.end(); } ConstIterator end() const { return _headers.end(); }
DefaultHeaders(DefaultHeaders const &) = delete; DefaultHeaders(DefaultHeaders const&) = delete;
DefaultHeaders &operator=(DefaultHeaders const &) = delete; DefaultHeaders& operator=(DefaultHeaders const&) = delete;
static DefaultHeaders &Instance() { static DefaultHeaders& Instance() {
static DefaultHeaders instance; static DefaultHeaders instance;
return instance; return instance;
} }
}; };
#include "WebResponseImpl.h"
#include "WebHandlerImpl.h"
#include "AsyncWebSocket.h"
#include "AsyncEventSource.h" #include "AsyncEventSource.h"
#include "AsyncWebSocket.h"
#include "WebHandlerImpl.h"
#include "WebResponseImpl.h"
#endif /* _AsyncWebServer_H_ */ #endif /* _AsyncWebServer_H_ */

View File

@ -1,174 +0,0 @@
/*
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
*/
#ifndef STRINGARRAY_H_
#define STRINGARRAY_H_
#include "stddef.h"
#include "WString.h"
template <typename T>
class LinkedListNode {
T _value;
public:
LinkedListNode<T>* next;
LinkedListNode(const T val): _value(val), next(nullptr) {}
~LinkedListNode(){}
const T& value() const { return _value; };
T& value(){ return _value; }
};
template <typename T, template<typename> class Item = LinkedListNode>
class LinkedList {
public:
typedef Item<T> ItemType;
typedef std::function<void(const T&)> OnRemove;
typedef std::function<bool(const T&)> Predicate;
private:
ItemType* _root;
OnRemove _onRemove;
class Iterator {
ItemType* _node;
public:
Iterator(ItemType* current = nullptr) : _node(current) {}
Iterator(const Iterator& i) : _node(i._node) {}
Iterator& operator ++() { _node = _node->next; return *this; }
bool operator != (const Iterator& i) const { return _node != i._node; }
const T& operator * () const { return _node->value(); }
const T* operator -> () const { return &_node->value(); }
};
public:
typedef const Iterator ConstIterator;
ConstIterator begin() const { return ConstIterator(_root); }
ConstIterator end() const { return ConstIterator(nullptr); }
LinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {}
~LinkedList(){}
void add(const T& t){
auto it = new ItemType(t);
if(!_root){
_root = it;
} else {
auto i = _root;
while(i->next) i = i->next;
i->next = it;
}
}
T& front() const {
return _root->value();
}
bool isEmpty() const {
return _root == nullptr;
}
size_t length() const {
size_t i = 0;
auto it = _root;
while(it){
i++;
it = it->next;
}
return i;
}
size_t count_if(Predicate predicate) const {
size_t i = 0;
auto it = _root;
while(it){
if (!predicate){
i++;
}
else if (predicate(it->value())) {
i++;
}
it = it->next;
}
return i;
}
const T* nth(size_t N) const {
size_t i = 0;
auto it = _root;
while(it){
if(i++ == N)
return &(it->value());
it = it->next;
}
return nullptr;
}
bool remove(const T& t){
auto it = _root;
auto pit = _root;
while(it){
if(it->value() == t){
if(it == _root){
_root = _root->next;
} else {
pit->next = it->next;
}
if (_onRemove) {
_onRemove(it->value());
}
delete it;
return true;
}
pit = it;
it = it->next;
}
return false;
}
bool remove_first(Predicate predicate){
auto it = _root;
auto pit = _root;
while(it){
if(predicate(it->value())){
if(it == _root){
_root = _root->next;
} else {
pit->next = it->next;
}
if (_onRemove) {
_onRemove(it->value());
}
delete it;
return true;
}
pit = it;
it = it->next;
}
return false;
}
void free(){
while(_root != nullptr){
auto it = _root;
_root = _root->next;
if (_onRemove) {
_onRemove(it->value());
}
delete it;
}
_root = nullptr;
}
};
#endif /* STRINGARRAY_H_ */

View File

@ -20,41 +20,40 @@
*/ */
#include "WebAuthentication.h" #include "WebAuthentication.h"
#include <libb64/cencode.h> #include <libb64/cencode.h>
#ifdef ESP32 #if defined(ESP32) || defined(TARGET_RP2040)
#include <MD5Builder.h> #include <MD5Builder.h>
#else #else
#include "md5.h" #include "md5.h"
#endif #endif
// Basic Auth hash = base64("username:password") // Basic Auth hash = base64("username:password")
bool checkBasicAuthentication(const char * hash, const char * username, const char * 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 false;
size_t toencodeLen = strlen(username)+strlen(password)+1; size_t toencodeLen = strlen(username) + strlen(password) + 1;
size_t encodedLen = base64_encode_expected_len(toencodeLen); size_t encodedLen = base64_encode_expected_len(toencodeLen);
if(strlen(hash) != encodedLen) if (strlen(hash) != encodedLen)
// Fix from https://github.com/me-no-dev/ESPAsyncWebServer/issues/667 // Fix from https://github.com/me-no-dev/ESPAsyncWebServer/issues/667
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
if(strlen(hash) != encodedLen) if (strlen(hash) != encodedLen)
#else #else
if (strlen(hash) != encodedLen - 1) if (strlen(hash) != encodedLen - 1)
#endif #endif
return false; return false;
char *toencode = new char[toencodeLen+1]; char* toencode = new char[toencodeLen + 1];
if(toencode == NULL){ if (toencode == NULL) {
return false; return false;
} }
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; char* encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
if(encoded == NULL){ if (encoded == NULL) {
delete[] toencode; delete[] toencode;
return false; return false;
} }
sprintf_P(toencode, PSTR("%s:%s"), username, password); sprintf_P(toencode, PSTR("%s:%s"), username, password);
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){ if (base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0) {
delete[] toencode; delete[] toencode;
delete[] encoded; delete[] encoded;
return true; return true;
@ -64,8 +63,8 @@ bool checkBasicAuthentication(const char * hash, const char * username, const ch
return false; return false;
} }
static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more static bool getMD5(uint8_t* data, uint16_t len, char* output) { // 33 bytes or more
#ifdef ESP32 #if defined(ESP32) || defined(TARGET_RP2040)
MD5Builder md5; MD5Builder md5;
md5.begin(); md5.begin();
md5.add(data, len); md5.add(data, len);
@ -74,8 +73,8 @@ static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or mo
#else #else
md5_context_t _ctx; md5_context_t _ctx;
uint8_t * _buf = (uint8_t*)malloc(16); uint8_t* _buf = (uint8_t*)malloc(16);
if(_buf == NULL) if (_buf == NULL)
return false; return false;
memset(_buf, 0x00, 16); memset(_buf, 0x00, 16);
@ -83,7 +82,7 @@ static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or mo
MD5Update(&_ctx, data, len); MD5Update(&_ctx, data, len);
MD5Final(_buf, &_ctx); MD5Final(_buf, &_ctx);
for(uint8_t i = 0; i < 16; i++) { for (uint8_t i = 0; i < 16; i++) {
sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]); sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]);
} }
@ -92,50 +91,50 @@ static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or mo
return true; return true;
} }
static String genRandomMD5(){ static String genRandomMD5() {
#ifdef ESP8266 #ifdef ESP8266
uint32_t r = RANDOM_REG32; uint32_t r = RANDOM_REG32;
#else #else
uint32_t r = rand(); uint32_t r = rand();
#endif #endif
char * out = (char*)malloc(33); char* out = (char*)malloc(33);
if(out == NULL || !getMD5((uint8_t*)(&r), 4, out)) if (out == NULL || !getMD5((uint8_t*)(&r), 4, out))
return emptyString; return emptyString;
String res = String(out); String res = String(out);
free(out); free(out);
return res; return res;
} }
static String stringMD5(const String& in){ static String stringMD5(const String& in) {
char * out = (char*)malloc(33); 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))
return emptyString; return emptyString;
String res = String(out); String res = String(out);
free(out); free(out);
return res; return res;
} }
String generateDigestHash(const char * username, const char * password, const char * realm){ String generateDigestHash(const char* username, const char* password, const char* realm) {
if(username == NULL || password == NULL || realm == NULL){ if (username == NULL || password == NULL || realm == NULL) {
return emptyString; return emptyString;
} }
char * out = (char*)malloc(33); char* out = (char*)malloc(33);
String res = String(username); String res = String(username);
res += ':'; res += ':';
res.concat(realm); res.concat(realm);
res += ':'; res += ':';
String in = res; String in = res;
in.concat(password); in.concat(password);
if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out))
return emptyString; return emptyString;
res.concat(out); res.concat(out);
free(out); free(out);
return res; return res;
} }
String requestDigestAuthentication(const char * realm){ String requestDigestAuthentication(const char* realm) {
String header = F("realm=\""); String header = F("realm=\"");
if(realm == NULL) if (realm == NULL)
header.concat(F("asyncesp")); header.concat(F("asyncesp"));
else else
header.concat(realm); header.concat(realm);
@ -147,95 +146,100 @@ String requestDigestAuthentication(const char * realm){
return header; return header;
} }
bool checkDigestAuthentication(const char * header, const __FlashStringHelper *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){ #ifndef ESP8266
if(username == NULL || password == NULL || header == NULL || method == NULL){ 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)
//os_printf("AUTH FAIL: missing requred fields\n"); #else
bool checkDigestAuthentication(const char* header, const __FlashStringHelper* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri)
#endif
{
if (username == NULL || password == NULL || header == NULL || method == NULL) {
// os_printf("AUTH FAIL: missing requred fields\n");
return false; return false;
} }
String myHeader = String(header); String myHeader(header);
int nextBreak = myHeader.indexOf(','); int nextBreak = myHeader.indexOf(',');
if(nextBreak < 0){ if (nextBreak < 0) {
//os_printf("AUTH FAIL: no variables\n"); // os_printf("AUTH FAIL: no variables\n");
return false; return false;
} }
String myUsername = String(); String myUsername;
String myRealm = String(); String myRealm;
String myNonce = String(); String myNonce;
String myUri = String(); String myUri;
String myResponse = String(); String myResponse;
String myQop = String(); String myQop;
String myNc = String(); String myNc;
String myCnonce = String(); String myCnonce;
myHeader += F(", "); myHeader += F(", ");
do { do {
String avLine = myHeader.substring(0, nextBreak); String avLine(myHeader.substring(0, nextBreak));
avLine.trim(); avLine.trim();
myHeader = myHeader.substring(nextBreak+1); myHeader = myHeader.substring(nextBreak + 1);
nextBreak = myHeader.indexOf(','); nextBreak = myHeader.indexOf(',');
int eqSign = avLine.indexOf('='); int eqSign = avLine.indexOf('=');
if(eqSign < 0){ if (eqSign < 0) {
//os_printf("AUTH FAIL: no = sign\n"); // os_printf("AUTH FAIL: no = sign\n");
return false; return false;
} }
String varName = avLine.substring(0, eqSign); String varName(avLine.substring(0, eqSign));
avLine = avLine.substring(eqSign + 1); avLine = avLine.substring(eqSign + 1);
if(avLine.startsWith(String('"'))){ if (avLine.startsWith(String('"'))) {
avLine = avLine.substring(1, avLine.length() - 1); avLine = avLine.substring(1, avLine.length() - 1);
} }
if(varName.equals(F("username"))){ if (varName.equals(F("username"))) {
if(!avLine.equals(username)){ if (!avLine.equals(username)) {
//os_printf("AUTH FAIL: username\n"); // os_printf("AUTH FAIL: username\n");
return false; return false;
} }
myUsername = avLine; myUsername = avLine;
} else if(varName.equals(F("realm"))){ } else if (varName.equals(F("realm"))) {
if(realm != NULL && !avLine.equals(realm)){ if (realm != NULL && !avLine.equals(realm)) {
//os_printf("AUTH FAIL: realm\n"); // os_printf("AUTH FAIL: realm\n");
return false; return false;
} }
myRealm = avLine; myRealm = avLine;
} else if(varName.equals(F("nonce"))){ } else if (varName.equals(F("nonce"))) {
if(nonce != NULL && !avLine.equals(nonce)){ if (nonce != NULL && !avLine.equals(nonce)) {
//os_printf("AUTH FAIL: nonce\n"); // os_printf("AUTH FAIL: nonce\n");
return false; return false;
} }
myNonce = avLine; myNonce = avLine;
} else if(varName.equals(F("opaque"))){ } else if (varName.equals(F("opaque"))) {
if(opaque != NULL && !avLine.equals(opaque)){ if (opaque != NULL && !avLine.equals(opaque)) {
//os_printf("AUTH FAIL: opaque\n"); // os_printf("AUTH FAIL: opaque\n");
return false; return false;
} }
} else if(varName.equals(F("uri"))){ } else if (varName.equals(F("uri"))) {
if(uri != NULL && !avLine.equals(uri)){ if (uri != NULL && !avLine.equals(uri)) {
//os_printf("AUTH FAIL: uri\n"); // os_printf("AUTH FAIL: uri\n");
return false; return false;
} }
myUri = avLine; myUri = avLine;
} else if(varName.equals(F("response"))){ } else if (varName.equals(F("response"))) {
myResponse = avLine; myResponse = avLine;
} else if(varName.equals(F("qop"))){ } else if (varName.equals(F("qop"))) {
myQop = avLine; myQop = avLine;
} else if(varName.equals(F("nc"))){ } else if (varName.equals(F("nc"))) {
myNc = avLine; myNc = avLine;
} else if(varName.equals(F("cnonce"))){ } else if (varName.equals(F("cnonce"))) {
myCnonce = avLine; myCnonce = avLine;
} }
} while(nextBreak > 0); } while (nextBreak > 0);
String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + String(password)); String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + password);
String ha2 = String(method) + ':' + myUri; String ha2 = String(method) + ':' + myUri;
String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + stringMD5(ha2); String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + stringMD5(ha2);
if(myResponse.equals(stringMD5(response))){ if (myResponse.equals(stringMD5(response))) {
//os_printf("AUTH SUCCESS\n"); // os_printf("AUTH SUCCESS\n");
return true; return true;
} }
//os_printf("AUTH FAIL: password\n"); // os_printf("AUTH FAIL: password\n");
return false; return false;
} }

View File

@ -24,11 +24,16 @@
#include "Arduino.h" #include "Arduino.h"
bool checkBasicAuthentication(const char * header, const char * username, const char * password); bool checkBasicAuthentication(const char* header, const char* username, const char* password);
String requestDigestAuthentication(const char * realm); String requestDigestAuthentication(const char* realm);
bool checkDigestAuthentication(const char * header, const __FlashStringHelper *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 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);
String generateDigestHash(const char * username, const char * password, const char * realm);
#ifdef ESP8266
bool checkDigestAuthentication(const char* header, const __FlashStringHelper* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri);
#endif
// for storing hashed versions on the device that can be authenticated against
String generateDigestHash(const char* username, const char* password, const char* realm);
#endif #endif

View File

@ -23,19 +23,21 @@
#include <string> #include <string>
#ifdef ASYNCWEBSERVER_REGEX #ifdef ASYNCWEBSERVER_REGEX
#include <regex> #include <regex>
#endif #endif
#include "stddef.h" #include "stddef.h"
#include <time.h> #include <time.h>
class AsyncStaticWebHandler: public AsyncWebHandler { class AsyncStaticWebHandler : public AsyncWebHandler {
using File = fs::File; using File = fs::File;
using FS = fs::FS; using FS = fs::FS;
private: private:
bool _getFile(AsyncWebServerRequest *request); bool _getFile(AsyncWebServerRequest* request);
bool _fileExists(AsyncWebServerRequest *request, const String& path); bool _fileExists(AsyncWebServerRequest* request, const String& path);
uint8_t _countBits(const uint8_t value) const; uint8_t _countBits(const uint8_t value) const;
protected: protected:
FS _fs; FS _fs;
String _uri; String _uri;
@ -47,23 +49,27 @@ class AsyncStaticWebHandler: public AsyncWebHandler {
bool _isDir; bool _isDir;
bool _gzipFirst; bool _gzipFirst;
uint8_t _gzipStats; uint8_t _gzipStats;
public: public:
AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control); AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control);
virtual bool canHandle(AsyncWebServerRequest *request) override final; virtual bool canHandle(AsyncWebServerRequest* request) override final;
virtual void handleRequest(AsyncWebServerRequest *request) override final; virtual void handleRequest(AsyncWebServerRequest* request) override final;
AsyncStaticWebHandler& setIsDir(bool isDir); AsyncStaticWebHandler& setIsDir(bool isDir);
AsyncStaticWebHandler& setDefaultFile(const char* filename); AsyncStaticWebHandler& setDefaultFile(const char* filename);
AsyncStaticWebHandler& setCacheControl(const char* cache_control); AsyncStaticWebHandler& setCacheControl(const char* cache_control);
AsyncStaticWebHandler& setLastModified(const char* last_modified); AsyncStaticWebHandler& setLastModified(const char* last_modified);
AsyncStaticWebHandler& setLastModified(struct tm* last_modified); AsyncStaticWebHandler& setLastModified(struct tm* last_modified);
#ifdef ESP8266 #ifdef ESP8266
AsyncStaticWebHandler& setLastModified(time_t last_modified); AsyncStaticWebHandler& setLastModified(time_t last_modified);
AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated AsyncStaticWebHandler& setLastModified(); // sets to current time. Make sure sntp is runing and time is updated
#endif #endif
AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {
_callback = newCallback;
return *this;
}
}; };
class AsyncCallbackWebHandler: public AsyncWebHandler { class AsyncCallbackWebHandler : public AsyncWebHandler {
private: private:
protected: protected:
String _uri; String _uri;
@ -72,23 +78,24 @@ class AsyncCallbackWebHandler: public AsyncWebHandler {
ArUploadHandlerFunction _onUpload; ArUploadHandlerFunction _onUpload;
ArBodyHandlerFunction _onBody; ArBodyHandlerFunction _onBody;
bool _isRegex; bool _isRegex;
public: public:
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {}
void setUri(const String& uri){ void setUri(const String& uri) {
_uri = uri; _uri = uri;
_isRegex = uri.startsWith("^") && uri.endsWith("$"); _isRegex = uri.startsWith("^") && uri.endsWith("$");
} }
void setMethod(WebRequestMethodComposite method){ _method = method; } void setMethod(WebRequestMethodComposite method) { _method = method; }
void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; } void onRequest(ArRequestHandlerFunction fn) { _onRequest = fn; }
void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; } void onUpload(ArUploadHandlerFunction fn) { _onUpload = fn; }
void onBody(ArBodyHandlerFunction fn){ _onBody = fn; } void onBody(ArBodyHandlerFunction fn) { _onBody = fn; }
virtual bool canHandle(AsyncWebServerRequest *request) override final{ virtual bool canHandle(AsyncWebServerRequest* request) override final {
if(!_onRequest) if (!_onRequest)
return false; return false;
if(!(_method & request->method())) if (!(_method & request->method()))
return false; return false;
#ifdef ASYNCWEBSERVER_REGEX #ifdef ASYNCWEBSERVER_REGEX
@ -96,7 +103,7 @@ class AsyncCallbackWebHandler: public AsyncWebHandler {
std::regex pattern(_uri.c_str()); std::regex pattern(_uri.c_str());
std::smatch matches; std::smatch matches;
std::string s(request->url().c_str()); std::string s(request->url().c_str());
if(std::regex_search(s, matches, pattern)) { if (std::regex_search(s, matches, pattern)) {
for (size_t i = 1; i < matches.size(); ++i) { // start from 1 for (size_t i = 1; i < matches.size(); ++i) { // start from 1
request->_addPathParam(matches[i].str().c_str()); request->_addPathParam(matches[i].str().c_str());
} }
@ -105,47 +112,44 @@ class AsyncCallbackWebHandler: public AsyncWebHandler {
} }
} else } else
#endif #endif
if (_uri.length() && _uri.startsWith("/*.")) { if (_uri.length() && _uri.startsWith("/*.")) {
String uriTemplate = String (_uri);
uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf("."));
if (!request->url().endsWith(uriTemplate))
return false;
}
else
if (_uri.length() && _uri.endsWith("*")) {
String uriTemplate = String(_uri); String uriTemplate = String(_uri);
uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1); uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf("."));
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; 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 false;
request->addInterestingHeader("ANY"); request->addInterestingHeader("ANY");
return true; return true;
} }
virtual void handleRequest(AsyncWebServerRequest *request) override final { virtual void handleRequest(AsyncWebServerRequest* request) override final {
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
return request->requestAuthentication(); return request->requestAuthentication();
if(_onRequest) if (_onRequest)
_onRequest(request); _onRequest(request);
else else
request->send(500); request->send(500);
} }
virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final { virtual void handleUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) override final {
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
return request->requestAuthentication(); return request->requestAuthentication();
if(_onUpload) if (_onUpload)
_onUpload(request, filename, index, data, len, final); _onUpload(request, filename, index, data, len, final);
} }
virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { virtual void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final {
if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
return request->requestAuthentication(); return request->requestAuthentication();
if(_onBody) if (_onBody)
_onBody(request, data, len, index, total); _onBody(request, data, len, index, total);
} }
virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;} virtual bool isRequestHandlerTrivial() override final { return _onRequest ? false : true; }
}; };
#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */ #endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */

View File

@ -22,73 +22,73 @@
#include "WebHandlerImpl.h" #include "WebHandlerImpl.h"
AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control) 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) : _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr) {
{
// Ensure leading '/' // Ensure leading '/'
if (_uri.length() == 0 || _uri[0] != '/') _uri = String('/') + _uri; if (_uri.length() == 0 || _uri[0] != '/')
if (_path.length() == 0 || _path[0] != '/') _path = String('/') + _path; _uri = String('/') + _uri;
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. // 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. // However - if it does not end with '/' we, can't assume a file, path can still be a directory.
_isDir = _path[_path.length()-1] == '/'; _isDir = _path[_path.length() - 1] == '/';
// Remove the trailing '/' so we can handle default file // Remove the trailing '/' so we can handle default file
// Notice that root will be "" not "/" // Notice that root will be "" not "/"
if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1); if (_uri[_uri.length() - 1] == '/')
if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1); _uri = _uri.substring(0, _uri.length() - 1);
if (_path[_path.length() - 1] == '/')
_path = _path.substring(0, _path.length() - 1);
// Reset stats // Reset stats
_gzipFirst = false; _gzipFirst = false;
_gzipStats = 0xF8; _gzipStats = 0xF8;
} }
AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){ AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir) {
_isDir = isDir; _isDir = isDir;
return *this; return *this;
} }
AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){ AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename) {
_default_file = String(filename); _default_file = String(filename);
return *this; return *this;
} }
AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){ AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control) {
_cache_control = String(cache_control); _cache_control = String(cache_control);
return *this; return *this;
} }
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified) {
_last_modified = last_modified; _last_modified = last_modified;
return *this; return *this;
} }
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified){ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified) {
auto formatP = PSTR("%a, %d %b %Y %H:%M:%S %Z"); auto formatP = PSTR("%a, %d %b %Y %H:%M:%S %Z");
char format[strlen_P(formatP) + 1]; char format[strlen_P(formatP) + 1];
strcpy_P(format, formatP); strcpy_P(format, formatP);
char result[30]; char result[30];
strftime(result, sizeof(result), format, last_modified); strftime(result, sizeof(result), format, last_modified);
return setLastModified((const char *)result); return setLastModified((const char*)result);
} }
#ifdef ESP8266 #ifdef ESP8266
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified){ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified) {
return setLastModified((struct tm *)gmtime(&last_modified)); return setLastModified((struct tm*)gmtime(&last_modified));
} }
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified() {
time_t last_modified; 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 *this;
return setLastModified(last_modified); return setLastModified(last_modified);
} }
#endif #endif
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){ bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest* request) {
if(request->method() != HTTP_GET if (request->method() != HTTP_GET || !request->url().startsWith(_uri) || !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP)) {
|| !request->url().startsWith(_uri)
|| !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP)
){
return false; return false;
} }
if (_getFile(request)) { if (_getFile(request)) {
@ -96,7 +96,7 @@ bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){
if (_last_modified.length()) if (_last_modified.length())
request->addInterestingHeader(F("If-Modified-Since")); request->addInterestingHeader(F("If-Modified-Since"));
if(_cache_control.length()) if (_cache_control.length())
request->addInterestingHeader(F("If-None-Match")); request->addInterestingHeader(F("If-None-Match"));
return true; return true;
@ -105,13 +105,12 @@ bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){
return false; return false;
} }
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest* request) {
{
// Remove the found uri // Remove the found uri
String path = request->url().substring(_uri.length()); String path = request->url().substring(_uri.length());
// We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/' // We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/'
bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/'); bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length() - 1] == '/');
path = _path + path; path = _path + path;
@ -124,7 +123,7 @@ bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request)
return false; return false;
// Try to add default file, ensure there is a trailing '/' ot the path. // Try to add default file, ensure there is a trailing '/' ot the path.
if (path.length() == 0 || path[path.length()-1] != '/') if (path.length() == 0 || path[path.length() - 1] != '/')
path += String('/'); path += String('/');
path += _default_file; path += _default_file;
@ -132,13 +131,12 @@ bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request)
} }
#ifdef ESP32 #ifdef ESP32
#define FILE_IS_REAL(f) (f == true && !f.isDirectory()) #define FILE_IS_REAL(f) (f == true && !f.isDirectory())
#else #else
#define FILE_IS_REAL(f) (f == true) #define FILE_IS_REAL(f) (f == true)
#endif #endif
bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String& path) bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest* request, const String& path) {
{
bool fileFound = false; bool fileFound = false;
bool gzipFound = false; bool gzipFound = false;
@ -146,25 +144,25 @@ bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const St
if (_gzipFirst) { if (_gzipFirst) {
if (_fs.exists(gzip)) { if (_fs.exists(gzip)) {
request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read); request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
gzipFound = FILE_IS_REAL(request->_tempFile); gzipFound = FILE_IS_REAL(request->_tempFile);
} }
if (!gzipFound){ if (!gzipFound) {
if (_fs.exists(path)) { if (_fs.exists(path)) {
request->_tempFile = _fs.open(path, fs::FileOpenMode::read); request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
fileFound = FILE_IS_REAL(request->_tempFile); fileFound = FILE_IS_REAL(request->_tempFile);
} }
} }
} else { } else {
if (_fs.exists(path)) { if (_fs.exists(path)) {
request->_tempFile = _fs.open(path, fs::FileOpenMode::read); request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
fileFound = FILE_IS_REAL(request->_tempFile); fileFound = FILE_IS_REAL(request->_tempFile);
} }
if (!fileFound){ if (!fileFound) {
if (_fs.exists(gzip)) { if (_fs.exists(gzip)) {
request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read); request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
gzipFound = FILE_IS_REAL(request->_tempFile); gzipFound = FILE_IS_REAL(request->_tempFile);
} }
} }
} }
@ -173,55 +171,71 @@ bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const St
if (found) { if (found) {
// Extract the file name from the path and keep it in _tempObject // Extract the file name from the path and keep it in _tempObject
size_t pathLen = path.length(); size_t pathLen = path.length();
char * _tempPath = (char*)malloc(pathLen+1); char* _tempPath = (char*)malloc(pathLen + 1);
snprintf_P(_tempPath, pathLen+1, PSTR("%s"), path.c_str()); snprintf_P(_tempPath, pathLen + 1, PSTR("%s"), path.c_str());
request->_tempObject = (void*)_tempPath; request->_tempObject = (void*)_tempPath;
// Calculate gzip statistic // Calculate gzip statistic
_gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); _gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0);
if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip if (_gzipStats == 0x00)
else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip _gzipFirst = false; // All files are not gzip
else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first else if (_gzipStats == 0xFF)
_gzipFirst = true; // All files are gzip
else
_gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first
} }
return found; return found;
} }
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const {
{
uint8_t w = value; uint8_t w = value;
uint8_t n; uint8_t n;
for (n=0; w!=0; n++) w&=w-1; for (n = 0; w != 0; n++)
w &= w - 1;
return n; return n;
} }
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
{
// Get the filename from request->_tempObject and free it // Get the filename from request->_tempObject and free it
String filename = String((char*)request->_tempObject); String filename = String((char*)request->_tempObject);
free(request->_tempObject); free(request->_tempObject);
request->_tempObject = NULL; request->_tempObject = NULL;
if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) if ((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str()))
return request->requestAuthentication(); return request->requestAuthentication();
if (request->_tempFile == true) { if (request->_tempFile == true) {
time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS) time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS)
if (lw) setLastModified(gmtime(&lw)); // set etag to lastmod timestamp if available, otherwise to size
String etag(lw ? lw : request->_tempFile.size()); // set etag to lastmod timestamp if available, otherwise to size String etag;
if (lw) {
setLastModified(gmtime(&lw));
#if defined(TARGET_RP2040)
// time_t == long long int
const size_t len = 1 + 8 * sizeof(time_t);
char buf[len];
char* ret = lltoa(lw, buf, len, 10);
etag = ret ? String(ret) : String(request->_tempFile.size());
#else
etag = String(lw);
#endif
} else {
etag = String(request->_tempFile.size());
}
if (_last_modified.length() && _last_modified == request->header(F("If-Modified-Since"))) { if (_last_modified.length() && _last_modified == request->header(F("If-Modified-Since"))) {
request->_tempFile.close(); request->_tempFile.close();
request->send(304); // Not modified request->send(304); // Not modified
} else if (_cache_control.length() && request->hasHeader(F("If-None-Match")) && request->header(F("If-None-Match")).equals(etag)) { } else if (_cache_control.length() && request->hasHeader(F("If-None-Match")) && request->header(F("If-None-Match")).equals(etag)) {
request->_tempFile.close(); request->_tempFile.close();
AsyncWebServerResponse * response = new AsyncBasicResponse(304); // Not modified AsyncWebServerResponse* response = new AsyncBasicResponse(304); // Not modified
response->addHeader(F("Cache-Control"), _cache_control); response->addHeader(F("Cache-Control"), _cache_control);
response->addHeader(F("ETag"), etag); response->addHeader(F("ETag"), etag);
request->send(response); request->send(response);
} else { } else {
AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback); AsyncWebServerResponse* response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback);
if (_last_modified.length()) if (_last_modified.length())
response->addHeader(F("Last-Modified"), _last_modified); response->addHeader(F("Last-Modified"), _last_modified);
if (_cache_control.length()){ if (_cache_control.length()) {
response->addHeader(F("Cache-Control"), _cache_control); response->addHeader(F("Cache-Control"), _cache_control);
response->addHeader(F("ETag"), etag); response->addHeader(F("ETag"), etag);
} }

File diff suppressed because it is too large Load Diff

View File

@ -22,26 +22,27 @@
#define ASYNCWEBSERVERRESPONSEIMPL_H_ #define ASYNCWEBSERVERRESPONSEIMPL_H_
#ifdef Arduino_h #ifdef Arduino_h
// arduino is not compatible with std::vector // arduino is not compatible with std::vector
#undef min #undef min
#undef max #undef max
#endif #endif
#include <vector>
#include <memory> #include <memory>
#include <vector>
// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max. // It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max.
class AsyncBasicResponse: public AsyncWebServerResponse { class AsyncBasicResponse : public AsyncWebServerResponse {
private: private:
String _content; String _content;
public: public:
AsyncBasicResponse(int code, const String& contentType=String(), const String& content=String()); AsyncBasicResponse(int code, const String& contentType = String(), const String& content = String());
void _respond(AsyncWebServerRequest *request); void _respond(AsyncWebServerRequest* request);
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
bool _sourceValid() const { return true; } bool _sourceValid() const { return true; }
}; };
class AsyncAbstractResponse: public AsyncWebServerResponse { class AsyncAbstractResponse : public AsyncWebServerResponse {
private: private:
String _head; String _head;
// Data is inserted into cache at begin(). // Data is inserted into cache at begin().
@ -51,86 +52,95 @@ class AsyncAbstractResponse: public AsyncWebServerResponse {
std::vector<uint8_t> _cache; std::vector<uint8_t> _cache;
size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len); size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len);
size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen); size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen);
protected: protected:
AwsTemplateProcessor _callback; AwsTemplateProcessor _callback;
public: public:
AsyncAbstractResponse(AwsTemplateProcessor callback=nullptr); AsyncAbstractResponse(AwsTemplateProcessor callback = nullptr);
void _respond(AsyncWebServerRequest *request); void _respond(AsyncWebServerRequest* request);
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time);
bool _sourceValid() const { return false; } bool _sourceValid() const { return false; }
virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; } virtual size_t _fillBuffer(uint8_t* buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; }
}; };
#ifndef TEMPLATE_PLACEHOLDER #ifndef TEMPLATE_PLACEHOLDER
#define TEMPLATE_PLACEHOLDER '%' #define TEMPLATE_PLACEHOLDER '%'
#endif #endif
#define TEMPLATE_PARAM_NAME_LENGTH 32 #define TEMPLATE_PARAM_NAME_LENGTH 32
class AsyncFileResponse: public AsyncAbstractResponse { class AsyncFileResponse : public AsyncAbstractResponse {
using File = fs::File; using File = fs::File;
using FS = fs::FS; using FS = fs::FS;
private: private:
File _content; File _content;
String _path; String _path;
void _setContentType(const String& path); void _setContentType(const String& path);
public: public:
AsyncFileResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); AsyncFileResponse(FS& fs, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncFileResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); AsyncFileResponse(File content, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr);
~AsyncFileResponse(); ~AsyncFileResponse();
bool _sourceValid() const { return !!(_content); } bool _sourceValid() const { return !!(_content); }
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
}; };
class AsyncStreamResponse: public AsyncAbstractResponse { class AsyncStreamResponse : public AsyncAbstractResponse {
private: private:
Stream *_content; Stream* _content;
public: public:
AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); AsyncStreamResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr);
bool _sourceValid() const { return !!(_content); } bool _sourceValid() const { return !!(_content); }
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
}; };
class AsyncCallbackResponse: public AsyncAbstractResponse { class AsyncCallbackResponse : public AsyncAbstractResponse {
private: private:
AwsResponseFiller _content; AwsResponseFiller _content;
size_t _filledLength; size_t _filledLength;
public: public:
AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
bool _sourceValid() const { return !!(_content); } bool _sourceValid() const { return !!(_content); }
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
}; };
class AsyncChunkedResponse: public AsyncAbstractResponse { class AsyncChunkedResponse : public AsyncAbstractResponse {
private: private:
AwsResponseFiller _content; AwsResponseFiller _content;
size_t _filledLength; size_t _filledLength;
public: public:
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
bool _sourceValid() const { return !!(_content); } bool _sourceValid() const { return !!(_content); }
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
}; };
class AsyncProgmemResponse: public AsyncAbstractResponse { class AsyncProgmemResponse : public AsyncAbstractResponse {
private: private:
const uint8_t * _content; const uint8_t* _content;
size_t _readLength; size_t _readLength;
public: public:
AsyncProgmemResponse(int code, const String& 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);
bool _sourceValid() const { return true; } bool _sourceValid() const { return true; }
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
}; };
class cbuf; class cbuf;
class AsyncResponseStream: public AsyncAbstractResponse, public Print { class AsyncResponseStream : public AsyncAbstractResponse, public Print {
private: private:
std::unique_ptr<cbuf> _content; std::unique_ptr<cbuf> _content;
public: public:
AsyncResponseStream(const String& contentType, size_t bufferSize); AsyncResponseStream(const String& contentType, size_t bufferSize);
~AsyncResponseStream(); ~AsyncResponseStream();
bool _sourceValid() const { return (_state < RESPONSE_END); } bool _sourceValid() const { return (_state < RESPONSE_END); }
virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override;
size_t write(const uint8_t *data, size_t len); size_t write(const uint8_t* data, size_t len);
size_t write(uint8_t data); size_t write(uint8_t data);
using Print::write; using Print::write;
}; };

View File

@ -23,111 +23,140 @@
#include "cbuf.h" #include "cbuf.h"
// Since ESP8266 does not link memchr by default, here's its implementation. // Since ESP8266 does not link memchr by default, here's its implementation.
void* memchr(void* ptr, int ch, size_t count) void* memchr(void* ptr, int ch, size_t count) {
{
unsigned char* p = static_cast<unsigned char*>(ptr); unsigned char* p = static_cast<unsigned char*>(ptr);
while(count--) while (count--)
if(*p++ == static_cast<unsigned char>(ch)) if (*p++ == static_cast<unsigned char>(ch))
return --p; return --p;
return nullptr; return nullptr;
} }
/* /*
* Abstract Response * Abstract Response
* */ * */
const char* AsyncWebServerResponse::_responseCodeToString(int code) { const char* AsyncWebServerResponse::_responseCodeToString(int code) {
return reinterpret_cast<const char *>(responseCodeToString(code)); return reinterpret_cast<const char*>(responseCodeToString(code));
} }
const __FlashStringHelper *AsyncWebServerResponse::responseCodeToString(int code) { const __FlashStringHelper* AsyncWebServerResponse::responseCodeToString(int code) {
switch (code) { switch (code) {
case 100: return F("Continue"); case 100:
case 101: return F("Switching Protocols"); return F("Continue");
case 200: return F("OK"); case 101:
case 201: return F("Created"); return F("Switching Protocols");
case 202: return F("Accepted"); case 200:
case 203: return F("Non-Authoritative Information"); return F("OK");
case 204: return F("No Content"); case 201:
case 205: return F("Reset Content"); return F("Created");
case 206: return F("Partial Content"); case 202:
case 300: return F("Multiple Choices"); return F("Accepted");
case 301: return F("Moved Permanently"); case 203:
case 302: return F("Found"); return F("Non-Authoritative Information");
case 303: return F("See Other"); case 204:
case 304: return F("Not Modified"); return F("No Content");
case 305: return F("Use Proxy"); case 205:
case 307: return F("Temporary Redirect"); return F("Reset Content");
case 400: return F("Bad Request"); case 206:
case 401: return F("Unauthorized"); return F("Partial Content");
case 402: return F("Payment Required"); case 300:
case 403: return F("Forbidden"); return F("Multiple Choices");
case 404: return F("Not Found"); case 301:
case 405: return F("Method Not Allowed"); return F("Moved Permanently");
case 406: return F("Not Acceptable"); case 302:
case 407: return F("Proxy Authentication Required"); return F("Found");
case 408: return F("Request Time-out"); case 303:
case 409: return F("Conflict"); return F("See Other");
case 410: return F("Gone"); case 304:
case 411: return F("Length Required"); return F("Not Modified");
case 412: return F("Precondition Failed"); case 305:
case 413: return F("Request Entity Too Large"); return F("Use Proxy");
case 414: return F("Request-URI Too Large"); case 307:
case 415: return F("Unsupported Media Type"); return F("Temporary Redirect");
case 416: return F("Requested range not satisfiable"); case 400:
case 417: return F("Expectation Failed"); return F("Bad Request");
case 500: return F("Internal Server Error"); case 401:
case 501: return F("Not Implemented"); return F("Unauthorized");
case 502: return F("Bad Gateway"); case 402:
case 503: return F("Service Unavailable"); return F("Payment Required");
case 504: return F("Gateway Time-out"); case 403:
case 505: return F("HTTP Version not supported"); return F("Forbidden");
default: return F(""); case 404:
return F("Not Found");
case 405:
return F("Method Not Allowed");
case 406:
return F("Not Acceptable");
case 407:
return F("Proxy Authentication Required");
case 408:
return F("Request Time-out");
case 409:
return F("Conflict");
case 410:
return F("Gone");
case 411:
return F("Length Required");
case 412:
return F("Precondition Failed");
case 413:
return F("Request Entity Too Large");
case 414:
return F("Request-URI Too Large");
case 415:
return F("Unsupported Media Type");
case 416:
return F("Requested range not satisfiable");
case 417:
return F("Expectation Failed");
case 500:
return F("Internal Server Error");
case 501:
return F("Not Implemented");
case 502:
return F("Bad Gateway");
case 503:
return F("Service Unavailable");
case 504:
return F("Gateway Time-out");
case 505:
return F("HTTP Version not supported");
default:
return F("");
} }
} }
AsyncWebServerResponse::AsyncWebServerResponse() AsyncWebServerResponse::AsyncWebServerResponse()
: _code(0) : _code(0), _contentType(), _contentLength(0), _sendContentLength(true), _chunked(false), _headLength(0), _sentLength(0), _ackedLength(0), _writtenLength(0), _state(RESPONSE_SETUP) {
, _contentType() for (const auto& header : DefaultHeaders::Instance()) {
, _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); _headers.emplace_back(header);
} }
} }
AsyncWebServerResponse::~AsyncWebServerResponse() = default; AsyncWebServerResponse::~AsyncWebServerResponse() = default;
void AsyncWebServerResponse::setCode(int code){ void AsyncWebServerResponse::setCode(int code) {
if(_state == RESPONSE_SETUP) if (_state == RESPONSE_SETUP)
_code = code; _code = code;
} }
void AsyncWebServerResponse::setContentLength(size_t len){ void AsyncWebServerResponse::setContentLength(size_t len) {
if(_state == RESPONSE_SETUP) if (_state == RESPONSE_SETUP)
_contentLength = len; _contentLength = len;
} }
void AsyncWebServerResponse::setContentType(const String& type){ void AsyncWebServerResponse::setContentType(const String& type) {
if(_state == RESPONSE_SETUP) if (_state == RESPONSE_SETUP)
_contentType = type; _contentType = type;
} }
void AsyncWebServerResponse::addHeader(const String& name, const String& value){ void AsyncWebServerResponse::addHeader(const String& name, const String& value) {
_headers.emplace_back(name, value); _headers.emplace_back(name, value);
} }
String AsyncWebServerResponse::_assembleHead(uint8_t version){ String AsyncWebServerResponse::_assembleHead(uint8_t version) {
if(version){ if (version) {
addHeader(F("Accept-Ranges"), F("none")); addHeader(F("Accept-Ranges"), F("none"));
if(_chunked) if (_chunked)
addHeader(F("Transfer-Encoding"), F("chunked")); addHeader(F("Transfer-Encoding"), F("chunked"));
} }
String out = String(); String out = String();
@ -137,16 +166,16 @@ String AsyncWebServerResponse::_assembleHead(uint8_t version){
snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code)); snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code));
out.concat(buf); out.concat(buf);
if(_sendContentLength) { if (_sendContentLength) {
snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength); snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength);
out.concat(buf); out.concat(buf);
} }
if(_contentType.length()) { if (_contentType.length()) {
snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str()); snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str());
out.concat(buf); out.concat(buf);
} }
for(const auto& header: _headers){ for (const auto& header : _headers) {
snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header.name().c_str(), header.value().c_str()); snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header.name().c_str(), header.value().c_str());
out.concat(buf); out.concat(buf);
} }
@ -161,44 +190,52 @@ bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP;
bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; } bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; }
bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; } bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; }
bool AsyncWebServerResponse::_sourceValid() const { return false; } bool AsyncWebServerResponse::_sourceValid() const { return false; }
void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request){ _state = RESPONSE_END; request->client()->close(); } void AsyncWebServerResponse::_respond(AsyncWebServerRequest* request) {
size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ (void)request; (void)len; (void)time; return 0; } _state = RESPONSE_END;
request->client()->close();
}
size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
(void)request;
(void)len;
(void)time;
return 0;
}
/* /*
* String/Code Response * String/Code Response
* */ * */
AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content){ AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content) {
_code = code; _code = code;
_content = content; _content = content;
_contentType = contentType; _contentType = contentType;
if(_content.length()){ if (_content.length()) {
_contentLength = _content.length(); _contentLength = _content.length();
if(!_contentType.length()) if (!_contentType.length())
_contentType = F("text/plain"); _contentType = F("text/plain");
} }
addHeader(F("Connection"), F("close")); addHeader(F("Connection"), F("close"));
} }
void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){ void AsyncBasicResponse::_respond(AsyncWebServerRequest* request) {
_state = RESPONSE_HEADERS; _state = RESPONSE_HEADERS;
String out = _assembleHead(request->version()); String out = _assembleHead(request->version());
size_t outLen = out.length(); size_t outLen = out.length();
size_t space = request->client()->space(); size_t space = request->client()->space();
if(!_contentLength && space >= outLen){ if (!_contentLength && space >= outLen) {
_writtenLength += request->client()->write(out.c_str(), outLen); _writtenLength += request->client()->write(out.c_str(), outLen);
_state = RESPONSE_WAIT_ACK; _state = RESPONSE_WAIT_ACK;
} else if(_contentLength && space >= outLen + _contentLength){ } else if (_contentLength && space >= outLen + _contentLength) {
out += _content; out += _content;
outLen += _contentLength; outLen += _contentLength;
_writtenLength += request->client()->write(out.c_str(), outLen); _writtenLength += request->client()->write(out.c_str(), outLen);
_state = RESPONSE_WAIT_ACK; _state = RESPONSE_WAIT_ACK;
} else if(space && space < outLen){ } else if (space && space < outLen) {
String partial = out.substring(0, space); String partial = out.substring(0, space);
_content = out.substring(space) + _content; _content = out.substring(space) + _content;
_contentLength += outLen - space; _contentLength += outLen - space;
_writtenLength += request->client()->write(partial.c_str(), partial.length()); _writtenLength += request->client()->write(partial.c_str(), partial.length());
_state = RESPONSE_CONTENT; _state = RESPONSE_CONTENT;
} else if(space > outLen && space < (outLen + _contentLength)){ } else if (space > outLen && space < (outLen + _contentLength)) {
size_t shift = space - outLen; size_t shift = space - outLen;
outLen += shift; outLen += shift;
_sentLength += shift; _sentLength += shift;
@ -213,58 +250,56 @@ void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){
} }
} }
size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ size_t AsyncBasicResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
(void)time; (void)time;
_ackedLength += len; _ackedLength += len;
if(_state == RESPONSE_CONTENT){ if (_state == RESPONSE_CONTENT) {
size_t available = _contentLength - _sentLength; size_t available = _contentLength - _sentLength;
size_t space = request->client()->space(); size_t space = request->client()->space();
//we can fit in this packet // we can fit in this packet
if(space > available){ if (space > available) {
_writtenLength += request->client()->write(_content.c_str(), available); _writtenLength += request->client()->write(_content.c_str(), available);
_content = String(); _content = String();
_state = RESPONSE_WAIT_ACK; _state = RESPONSE_WAIT_ACK;
return available; return available;
} }
//send some data, the rest on ack // send some data, the rest on ack
String out = _content.substring(0, space); String out = _content.substring(0, space);
_content = _content.substring(space); _content = _content.substring(space);
_sentLength += space; _sentLength += space;
_writtenLength += request->client()->write(out.c_str(), space); _writtenLength += request->client()->write(out.c_str(), space);
return space; return space;
} else if(_state == RESPONSE_WAIT_ACK){ } else if (_state == RESPONSE_WAIT_ACK) {
if(_ackedLength >= _writtenLength){ if (_ackedLength >= _writtenLength) {
_state = RESPONSE_END; _state = RESPONSE_END;
} }
} }
return 0; return 0;
} }
/* /*
* Abstract Response * Abstract Response
* */ * */
AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback) AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) : _callback(callback) {
{
// In case of template processing, we're unable to determine real response size // In case of template processing, we're unable to determine real response size
if(callback) { if (callback) {
_contentLength = 0; _contentLength = 0;
_sendContentLength = false; _sendContentLength = false;
_chunked = true; _chunked = true;
} }
} }
void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){ void AsyncAbstractResponse::_respond(AsyncWebServerRequest* request) {
addHeader(F("Connection"), F("close")); addHeader(F("Connection"), F("close"));
_head = _assembleHead(request->version()); _head = _assembleHead(request->version());
_state = RESPONSE_HEADERS; _state = RESPONSE_HEADERS;
_ack(request, 0, 0); _ack(request, 0, 0);
} }
size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) {
(void)time; (void)time;
if(!_sourceValid()){ if (!_sourceValid()) {
_state = RESPONSE_FAILED; _state = RESPONSE_FAILED;
request->client()->close(); request->client()->close();
return 0; return 0;
@ -273,8 +308,8 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
size_t space = request->client()->space(); size_t space = request->client()->space();
size_t headLen = _head.length(); size_t headLen = _head.length();
if(_state == RESPONSE_HEADERS){ if (_state == RESPONSE_HEADERS) {
if(space >= headLen){ if (space >= headLen) {
_state = RESPONSE_CONTENT; _state = RESPONSE_CONTENT;
space -= headLen; space -= headLen;
} else { } else {
@ -285,103 +320,102 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u
} }
} }
if(_state == RESPONSE_CONTENT){ if (_state == RESPONSE_CONTENT) {
size_t outLen; size_t outLen;
if(_chunked){ if (_chunked) {
if(space <= 8){ if (space <= 8) {
return 0; return 0;
} }
outLen = space; outLen = space;
} else if(!_sendContentLength){ } else if (!_sendContentLength) {
outLen = space; outLen = space;
} else { } else {
outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength); outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength);
} }
uint8_t *buf = (uint8_t *)malloc(outLen+headLen); uint8_t* buf = (uint8_t*)malloc(outLen + headLen);
if (!buf) { if (!buf) {
// os_printf("_ack malloc %d failed\n", outLen+headLen); // os_printf("_ack malloc %d failed\n", outLen+headLen);
return 0; return 0;
} }
if(headLen){ if (headLen) {
memcpy(buf, _head.c_str(), _head.length()); memcpy(buf, _head.c_str(), _head.length());
} }
size_t readLen = 0; size_t readLen = 0;
if(_chunked){ if (_chunked) {
// HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added. // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added.
// See RFC2616 sections 2, 3.6.1. // See RFC2616 sections 2, 3.6.1.
readLen = _fillBufferAndProcessTemplates(buf+headLen+6, outLen - 8); readLen = _fillBufferAndProcessTemplates(buf + headLen + 6, outLen - 8);
if(readLen == RESPONSE_TRY_AGAIN){ if (readLen == RESPONSE_TRY_AGAIN) {
free(buf); free(buf);
return 0; return 0;
} }
outLen = sprintf_P((char*)buf+headLen, PSTR("%x"), readLen) + headLen; outLen = sprintf_P((char*)buf + headLen, PSTR("%x"), readLen) + headLen;
while(outLen < headLen + 4) buf[outLen++] = ' '; while (outLen < headLen + 4)
buf[outLen++] = ' ';
buf[outLen++] = '\r'; buf[outLen++] = '\r';
buf[outLen++] = '\n'; buf[outLen++] = '\n';
outLen += readLen; outLen += readLen;
buf[outLen++] = '\r'; buf[outLen++] = '\r';
buf[outLen++] = '\n'; buf[outLen++] = '\n';
} else { } else {
readLen = _fillBufferAndProcessTemplates(buf+headLen, outLen); readLen = _fillBufferAndProcessTemplates(buf + headLen, outLen);
if(readLen == RESPONSE_TRY_AGAIN){ if (readLen == RESPONSE_TRY_AGAIN) {
free(buf); free(buf);
return 0; return 0;
} }
outLen = readLen + headLen; outLen = readLen + headLen;
} }
if(headLen){ if (headLen) {
_head = String(); _head = String();
} }
if(outLen){ if (outLen) {
_writtenLength += request->client()->write((const char*)buf, outLen); _writtenLength += request->client()->write((const char*)buf, outLen);
} }
if(_chunked){ if (_chunked) {
_sentLength += readLen; _sentLength += readLen;
} else { } else {
_sentLength += outLen - headLen; _sentLength += outLen - headLen;
} }
free(buf); free(buf);
if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){ if ((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)) {
_state = RESPONSE_WAIT_ACK; _state = RESPONSE_WAIT_ACK;
} }
return outLen; return outLen;
} else if(_state == RESPONSE_WAIT_ACK){ } else if (_state == RESPONSE_WAIT_ACK) {
if(!_sendContentLength || _ackedLength >= _writtenLength){ if (!_sendContentLength || _ackedLength >= _writtenLength) {
_state = RESPONSE_END; _state = RESPONSE_END;
if(!_chunked && !_sendContentLength) if (!_chunked && !_sendContentLength)
request->client()->close(true); request->client()->close(true);
} }
} }
return 0; return 0;
} }
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) {
{ // If we have something in cache, copy it to buffer
// If we have something in cache, copy it to buffer const size_t readFromCache = std::min(len, _cache.size());
const size_t readFromCache = std::min(len, _cache.size()); if (readFromCache) {
if(readFromCache) { memcpy(data, _cache.data(), readFromCache);
memcpy(data, _cache.data(), readFromCache); _cache.erase(_cache.begin(), _cache.begin() + readFromCache);
_cache.erase(_cache.begin(), _cache.begin() + readFromCache); }
} // If we need to read more...
// If we need to read more... const size_t needFromFile = len - readFromCache;
const size_t needFromFile = len - readFromCache; const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile);
const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile); return readFromCache + readFromContent;
return readFromCache + readFromContent;
} }
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) {
{ if (!_callback)
if(!_callback)
return _fillBuffer(data, len); return _fillBuffer(data, len);
const size_t originalLen = len; const size_t originalLen = len;
@ -389,16 +423,16 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
// Now we've read 'len' bytes, either from cache or from file // Now we've read 'len' bytes, either from cache or from file
// Search for template placeholders // Search for template placeholders
uint8_t* pTemplateStart = data; 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] 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; 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 // temporary buffer to hold parameter name
uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
String paramName; String paramName;
// If closing placeholder is found: // If closing placeholder is found:
if(pTemplateEnd) { if (pTemplateEnd) {
// prepare argument to callback // prepare argument to callback
const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1)); const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1));
if(paramNameLength) { if (paramNameLength) {
memcpy(buf, pTemplateStart + 1, paramNameLength); memcpy(buf, pTemplateStart + 1, paramNameLength);
buf[paramNameLength] = 0; buf[paramNameLength] = 0;
paramName = String(reinterpret_cast<char*>(buf)); paramName = String(reinterpret_cast<char*>(buf));
@ -408,32 +442,29 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1;
++pTemplateStart; ++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); 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) { if (readFromCacheOrContent) {
pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
if(pTemplateEnd) { if (pTemplateEnd) {
// prepare argument to callback // prepare argument to callback
*pTemplateEnd = 0; *pTemplateEnd = 0;
paramName = String(reinterpret_cast<char*>(buf)); paramName = String(reinterpret_cast<char*>(buf));
// Copy remaining read-ahead data into cache // Copy remaining read-ahead data into cache
_cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
pTemplateEnd = &data[len - 1]; pTemplateEnd = &data[len - 1];
} } else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
{ {
// but first, store read file data in cache // but first, store read file data in cache
_cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
++pTemplateStart; ++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; ++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; ++pTemplateStart;
if(paramName.length()) { if (paramName.length()) {
// call callback and replace with result. // call callback and replace with result.
// Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value.
// Data after pTemplateEnd may need to be moved. // Data after pTemplateEnd may need to be moved.
@ -445,21 +476,21 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1)); const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1));
// make room for param value // make room for param value
// 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store
if((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) { if ((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) {
_cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]); _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]);
//2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end // 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); memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied);
len = originalLen; // fix issue with truncated data, not sure if it has any side effects 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. // 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 // Move the entire data after the placeholder
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
// 3. replace placeholder with actual value // 3. replace placeholder with actual value
memcpy(pTemplateStart, pvstr, numBytesCopied); 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) // 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)
if(numBytesCopied < pvlen) { if (numBytesCopied < pvlen) {
_cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen); _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen);
} else if(pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text... } else if (pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text...
// there is some free room, fill it from cache // there is some free room, fill it from cache
const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied;
const size_t totalFreeRoom = originalLen - len + roomFreed; const size_t totalFreeRoom = originalLen - len + roomFreed;
@ -473,48 +504,66 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
return len; return len;
} }
/* /*
* File Response * File Response
* */ * */
AsyncFileResponse::~AsyncFileResponse(){ AsyncFileResponse::~AsyncFileResponse() {
if(_content) if (_content)
_content.close(); _content.close();
} }
void AsyncFileResponse::_setContentType(const String& path){ void AsyncFileResponse::_setContentType(const String& path) {
#if HAVE_EXTERN_GET_CONTENT_TYPE_FUNCTION #if HAVE_EXTERN_GET_CONTENT_TYPE_FUNCTION
extern const __FlashStringHelper *getContentType(const String &path); extern const __FlashStringHelper* getContentType(const String& path);
_contentType = getContentType(path); _contentType = getContentType(path);
#else #else
if (path.endsWith(F(".html"))) _contentType = F("text/html"); if (path.endsWith(F(".html")))
else if (path.endsWith(F(".htm"))) _contentType = F("text/html"); _contentType = F("text/html");
else if (path.endsWith(F(".css"))) _contentType = F("text/css"); else if (path.endsWith(F(".htm")))
else if (path.endsWith(F(".json"))) _contentType = F("application/json"); _contentType = F("text/html");
else if (path.endsWith(F(".js"))) _contentType = F("application/javascript"); else if (path.endsWith(F(".css")))
else if (path.endsWith(F(".png"))) _contentType = F("image/png"); _contentType = F("text/css");
else if (path.endsWith(F(".gif"))) _contentType = F("image/gif"); else if (path.endsWith(F(".json")))
else if (path.endsWith(F(".jpg"))) _contentType = F("image/jpeg"); _contentType = F("application/json");
else if (path.endsWith(F(".ico"))) _contentType = F("image/x-icon"); else if (path.endsWith(F(".js")))
else if (path.endsWith(F(".svg"))) _contentType = F("image/svg+xml"); _contentType = F("application/javascript");
else if (path.endsWith(F(".eot"))) _contentType = F("font/eot"); else if (path.endsWith(F(".png")))
else if (path.endsWith(F(".woff"))) _contentType = F("font/woff"); _contentType = F("image/png");
else if (path.endsWith(F(".woff2"))) _contentType = F("font/woff2"); else if (path.endsWith(F(".gif")))
else if (path.endsWith(F(".ttf"))) _contentType = F("font/ttf"); _contentType = F("image/gif");
else if (path.endsWith(F(".xml"))) _contentType = F("text/xml"); else if (path.endsWith(F(".jpg")))
else if (path.endsWith(F(".pdf"))) _contentType = F("application/pdf"); _contentType = F("image/jpeg");
else if (path.endsWith(F(".zip"))) _contentType = F("application/zip"); else if (path.endsWith(F(".ico")))
else if(path.endsWith(F(".gz"))) _contentType = F("application/x-gzip"); _contentType = F("image/x-icon");
else _contentType = F("text/plain"); else if (path.endsWith(F(".svg")))
_contentType = F("image/svg+xml");
else if (path.endsWith(F(".eot")))
_contentType = F("font/eot");
else if (path.endsWith(F(".woff")))
_contentType = F("font/woff");
else if (path.endsWith(F(".woff2")))
_contentType = F("font/woff2");
else if (path.endsWith(F(".ttf")))
_contentType = F("font/ttf");
else if (path.endsWith(F(".xml")))
_contentType = F("text/xml");
else if (path.endsWith(F(".pdf")))
_contentType = F("application/pdf");
else if (path.endsWith(F(".zip")))
_contentType = F("application/zip");
else if (path.endsWith(F(".gz")))
_contentType = F("application/x-gzip");
else
_contentType = F("text/plain");
#endif #endif
} }
AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
_code = 200; _code = 200;
_path = path; _path = path;
if(!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))){ if (!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))) {
_path = _path + F(".gz"); _path = _path + F(".gz");
addHeader(F("Content-Encoding"), F("gzip")); addHeader(F("Content-Encoding"), F("gzip"));
_callback = nullptr; // Unable to process zipped templates _callback = nullptr; // Unable to process zipped templates
@ -525,30 +574,30 @@ AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& c
_content = fs.open(_path, fs::FileOpenMode::read); _content = fs.open(_path, fs::FileOpenMode::read);
_contentLength = _content.size(); _contentLength = _content.size();
if(contentType.length() == 0) if (contentType.length() == 0)
_setContentType(path); _setContentType(path);
else else
_contentType = contentType; _contentType = contentType;
int filenameStart = path.lastIndexOf('/') + 1; int filenameStart = path.lastIndexOf('/') + 1;
char buf[26+path.length()-filenameStart]; char buf[26 + path.length() - filenameStart];
char* filename = (char*)path.c_str() + filenameStart; char* filename = (char*)path.c_str() + filenameStart;
if(download) { if (download) {
// set filename and force download // set filename and force download
snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename); snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
} else { } else {
// set filename and force rendering // set filename and force rendering
snprintf_P(buf, sizeof (buf), PSTR("inline")); snprintf_P(buf, sizeof(buf), PSTR("inline"));
} }
addHeader(F("Content-Disposition"), buf); addHeader(F("Content-Disposition"), buf);
} }
AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
_code = 200; _code = 200;
_path = path; _path = path;
if(!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))){ if (!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))) {
addHeader(F("Content-Encoding"), F("gzip")); addHeader(F("Content-Encoding"), F("gzip"));
_callback = nullptr; // Unable to process gzipped templates _callback = nullptr; // Unable to process gzipped templates
_sendContentLength = true; _sendContentLength = true;
@ -558,24 +607,24 @@ AsyncFileResponse::AsyncFileResponse(File content, const String& path, const Str
_content = content; _content = content;
_contentLength = _content.size(); _contentLength = _content.size();
if(contentType.length() == 0) if (contentType.length() == 0)
_setContentType(path); _setContentType(path);
else else
_contentType = contentType; _contentType = contentType;
int filenameStart = path.lastIndexOf('/') + 1; int filenameStart = path.lastIndexOf('/') + 1;
char buf[26+path.length()-filenameStart]; char buf[26 + path.length() - filenameStart];
char* filename = (char*)path.c_str() + filenameStart; char* filename = (char*)path.c_str() + filenameStart;
if(download) { if (download) {
snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename); snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
} else { } else {
snprintf_P(buf, sizeof (buf), PSTR("inline")); snprintf_P(buf, sizeof(buf), PSTR("inline"));
} }
addHeader(F("Content-Disposition"), buf); addHeader(F("Content-Disposition"), buf);
} }
size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){ size_t AsyncFileResponse::_fillBuffer(uint8_t* data, size_t len) {
return _content.read(data, len); return _content.read(data, len);
} }
@ -583,18 +632,18 @@ size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){
* Stream Response * Stream Response
* */ * */
AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { AsyncStreamResponse::AsyncStreamResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
_code = 200; _code = 200;
_content = &stream; _content = &stream;
_contentLength = len; _contentLength = len;
_contentType = contentType; _contentType = contentType;
} }
size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){ size_t AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) {
size_t available = _content->available(); size_t available = _content->available();
size_t outLen = (available > len)?len:available; size_t outLen = (available > len) ? len : available;
size_t i; size_t i;
for(i=0;i<outLen;i++) for (i = 0; i < outLen; i++)
data[i] = _content->read(); data[i] = _content->read();
return outLen; return outLen;
} }
@ -603,20 +652,20 @@ size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){
* Callback Response * Callback Response
* */ * */
AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) { AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) : AsyncAbstractResponse(templateCallback) {
_code = 200; _code = 200;
_content = callback; _content = callback;
_contentLength = len; _contentLength = len;
if(!len) if (!len)
_sendContentLength = false; _sendContentLength = false;
_contentType = contentType; _contentType = contentType;
_filledLength = 0; _filledLength = 0;
} }
size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){ size_t AsyncCallbackResponse::_fillBuffer(uint8_t* data, size_t len) {
size_t ret = _content(data, len, _filledLength); size_t ret = _content(data, len, _filledLength);
if(ret != RESPONSE_TRY_AGAIN){ if (ret != RESPONSE_TRY_AGAIN) {
_filledLength += ret; _filledLength += ret;
} }
return ret; return ret;
} }
@ -625,7 +674,7 @@ size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){
* Chunked Response * Chunked Response
* */ * */
AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) { AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback) : AsyncAbstractResponse(processorCallback) {
_code = 200; _code = 200;
_content = callback; _content = callback;
_contentLength = 0; _contentLength = 0;
@ -635,10 +684,10 @@ AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsRespons
_filledLength = 0; _filledLength = 0;
} }
size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){ size_t AsyncChunkedResponse::_fillBuffer(uint8_t* data, size_t len) {
size_t ret = _content(data, len, _filledLength); size_t ret = _content(data, len, _filledLength);
if(ret != RESPONSE_TRY_AGAIN){ if (ret != RESPONSE_TRY_AGAIN) {
_filledLength += ret; _filledLength += ret;
} }
return ret; return ret;
} }
@ -647,7 +696,7 @@ size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){
* Progmem Response * Progmem Response
* */ * */
AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
_code = code; _code = code;
_content = content; _content = content;
_contentType = contentType; _contentType = contentType;
@ -655,7 +704,7 @@ AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType,
_readLength = 0; _readLength = 0;
} }
size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){ size_t AsyncProgmemResponse::_fillBuffer(uint8_t* data, size_t len) {
size_t left = _contentLength - _readLength; size_t left = _contentLength - _readLength;
if (left > len) { if (left > len) {
memcpy_P(data, _content + _readLength, len); memcpy_P(data, _content + _readLength, len);
@ -667,30 +716,28 @@ size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){
return left; return left;
} }
/* /*
* Response Stream (You can print/write/printf to it, up to the contentLen bytes) * Response Stream (You can print/write/printf to it, up to the contentLen bytes)
* */ * */
AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize) AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize) {
{
_code = 200; _code = 200;
_contentLength = 0; _contentLength = 0;
_contentType = contentType; _contentType = contentType;
_content = std::unique_ptr<cbuf>(new cbuf(bufferSize)); //std::make_unique<cbuf>(bufferSize); _content = std::unique_ptr<cbuf>(new cbuf(bufferSize)); // std::make_unique<cbuf>(bufferSize);
} }
AsyncResponseStream::~AsyncResponseStream() = default; AsyncResponseStream::~AsyncResponseStream() = default;
size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen){ size_t AsyncResponseStream::_fillBuffer(uint8_t* buf, size_t maxLen) {
return _content->read((char*)buf, maxLen); return _content->read((char*)buf, maxLen);
} }
size_t AsyncResponseStream::write(const uint8_t *data, size_t len){ size_t AsyncResponseStream::write(const uint8_t* data, size_t len) {
if(_started()) if (_started())
return 0; return 0;
if(len > _content->room()){ if (len > _content->room()) {
size_t needed = len - _content->room(); size_t needed = len - _content->room();
_content->resizeAdd(needed); _content->resizeAdd(needed);
} }
@ -699,6 +746,6 @@ size_t AsyncResponseStream::write(const uint8_t *data, size_t len){
return written; return written;
} }
size_t AsyncResponseStream::write(uint8_t data){ size_t AsyncResponseStream::write(uint8_t data) {
return write(&data, 1); return write(&data, 1);
} }

View File

@ -21,105 +21,126 @@
#include "ESPAsyncWebServer.h" #include "ESPAsyncWebServer.h"
#include "WebHandlerImpl.h" #include "WebHandlerImpl.h"
bool ON_STA_FILTER(AsyncWebServerRequest *request) { bool ON_STA_FILTER(AsyncWebServerRequest* request) {
return WiFi.localIP() == request->client()->localIP(); return WiFi.localIP() == request->client()->localIP();
} }
bool ON_AP_FILTER(AsyncWebServerRequest *request) { bool ON_AP_FILTER(AsyncWebServerRequest* request) {
return WiFi.localIP() != request->client()->localIP(); return WiFi.localIP() != request->client()->localIP();
} }
#ifndef HAVE_FS_FILE_OPEN_MODE #ifndef HAVE_FS_FILE_OPEN_MODE
const char *fs::FileOpenMode::read = "r"; const char* fs::FileOpenMode::read = "r";
const char *fs::FileOpenMode::write = "w"; const char* fs::FileOpenMode::write = "w";
const char *fs::FileOpenMode::append = "a"; const char* fs::FileOpenMode::append = "a";
#endif #endif
AsyncWebServer::AsyncWebServer(uint16_t port) AsyncWebServer::AsyncWebServer(uint16_t port)
: _server(port) : _server(port) {
, _rewrites(LinkedList<AsyncWebRewrite*>([](AsyncWebRewrite* r){ delete r; }))
, _handlers(LinkedList<AsyncWebHandler*>([](AsyncWebHandler* h){ delete h; }))
{
_catchAllHandler = new AsyncCallbackWebHandler(); _catchAllHandler = new AsyncCallbackWebHandler();
if(_catchAllHandler == NULL) if (_catchAllHandler == NULL)
return; return;
_server.onClient([](void *s, AsyncClient* c){ _server.onClient([](void* s, AsyncClient* c) {
if(c == NULL) if (c == NULL)
return; return;
c->setRxTimeout(3); c->setRxTimeout(3);
AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer*)s, c); AsyncWebServerRequest* r = new AsyncWebServerRequest((AsyncWebServer*)s, c);
if(r == NULL){ if (r == NULL) {
c->close(true); c->close(true);
c->free(); c->free();
delete c; delete c;
} }
}, this); },
this);
} }
AsyncWebServer::~AsyncWebServer(){ AsyncWebServer::~AsyncWebServer() {
reset(); reset();
end(); end();
if(_catchAllHandler) delete _catchAllHandler; if (_catchAllHandler)
delete _catchAllHandler;
} }
AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){ AsyncWebRewrite& AsyncWebServer::addRewrite(std::shared_ptr<AsyncWebRewrite> rewrite) {
_rewrites.add(rewrite); _rewrites.emplace_back(rewrite);
return *rewrite; return *_rewrites.back().get();
} }
bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite){ AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite) {
return _rewrites.remove(rewrite); _rewrites.emplace_back(rewrite);
return *_rewrites.back().get();
} }
AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){ bool AsyncWebServer::removeRewrite(AsyncWebRewrite* rewrite) {
return addRewrite(new AsyncWebRewrite(from, to)); return removeRewrite(rewrite->from().c_str(), rewrite->toUrl().c_str());
} }
AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){ bool AsyncWebServer::removeRewrite(const char* from, const char* to) {
_handlers.add(handler); for (auto r = _rewrites.begin(); r != _rewrites.end(); ++r) {
return *handler; if (r->get()->from() == from && r->get()->toUrl() == to) {
_rewrites.erase(r);
return true;
}
}
return false;
} }
bool AsyncWebServer::removeHandler(AsyncWebHandler *handler){ AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to) {
return _handlers.remove(handler); _rewrites.emplace_back(std::make_shared<AsyncWebRewrite>(from, to));
return *_rewrites.back().get();
} }
void AsyncWebServer::begin(){ AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler) {
_handlers.emplace_back(handler);
return *(_handlers.back().get());
}
bool AsyncWebServer::removeHandler(AsyncWebHandler* handler) {
for (auto i = _handlers.begin(); i != _handlers.end(); ++i) {
if (i->get() == handler) {
_handlers.erase(i);
return true;
}
}
return false;
}
void AsyncWebServer::begin() {
_server.setNoDelay(true); _server.setNoDelay(true);
_server.begin(); _server.begin();
} }
void AsyncWebServer::end(){ void AsyncWebServer::end() {
_server.end(); _server.end();
} }
#if ASYNC_TCP_SSL_ENABLED #if ASYNC_TCP_SSL_ENABLED
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg){ void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg) {
_server.onSslFileRequest(cb, arg); _server.onSslFileRequest(cb, arg);
} }
void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password){ void AsyncWebServer::beginSecure(const char* cert, const char* key, const char* password) {
_server.beginSecure(cert, key, password); _server.beginSecure(cert, key, password);
} }
#endif #endif
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){ void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest* request) {
delete request; delete request;
} }
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request){ void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest* request) {
for(const auto& r: _rewrites){ for (const auto& r : _rewrites) {
if (r->match(request)){ if (r->match(request)) {
request->_url = r->toUrl(); request->_url = r->toUrl();
request->_addGetParams(r->params()); request->_addGetParams(r->params());
} }
} }
} }
void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){ void AsyncWebServer::_attachHandler(AsyncWebServerRequest* request) {
for(const auto& h: _handlers){ for (auto& h : _handlers) {
if (h->filter(request) && h->canHandle(request)){ if (h->filter(request) && h->canHandle(request)) {
request->setHandler(h); request->setHandler(h.get());
return; return;
} }
} }
@ -128,8 +149,7 @@ void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){
request->setHandler(_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(); AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
handler->setUri(uri); handler->setUri(uri);
handler->setMethod(method); handler->setMethod(method);
@ -140,7 +160,7 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom
return *handler; return *handler;
} }
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload) {
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
handler->setUri(uri); handler->setUri(uri);
handler->setMethod(method); handler->setMethod(method);
@ -150,7 +170,7 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom
return *handler; return *handler;
} }
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest) {
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
handler->setUri(uri); handler->setUri(uri);
handler->setMethod(method); handler->setMethod(method);
@ -159,7 +179,7 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom
return *handler; return *handler;
} }
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest) {
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler();
handler->setUri(uri); handler->setUri(uri);
handler->onRequest(onRequest); handler->onRequest(onRequest);
@ -167,32 +187,31 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFun
return *handler; return *handler;
} }
AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control){ AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control) {
AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control); AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control);
addHandler(handler); addHandler(handler);
return *handler; return *handler;
} }
void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn){ void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn) {
_catchAllHandler->onRequest(fn); _catchAllHandler->onRequest(fn);
} }
void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn){ void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn) {
_catchAllHandler->onUpload(fn); _catchAllHandler->onUpload(fn);
} }
void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn){ void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn) {
_catchAllHandler->onBody(fn); _catchAllHandler->onBody(fn);
} }
void AsyncWebServer::reset(){ void AsyncWebServer::reset() {
_rewrites.free(); _rewrites.clear();
_handlers.free(); _handlers.clear();
if (_catchAllHandler != NULL){ if (_catchAllHandler != NULL) {
_catchAllHandler->onRequest(NULL); _catchAllHandler->onRequest(NULL);
_catchAllHandler->onUpload(NULL); _catchAllHandler->onUpload(NULL);
_catchAllHandler->onBody(NULL); _catchAllHandler->onBody(NULL);
} }
} }