mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-04 18:29:48 +02:00
Update ESPAsyncWebServer to v3.7.10
This commit is contained in:
parent
302282abeb
commit
537c01a8b7
21 changed files with 589 additions and 177 deletions
|
@ -38,7 +38,7 @@ It is also deployed in these registries:
|
||||||
|
|
||||||
- Arduino Library Registry: [https://github.com/arduino/library-registry](https://github.com/arduino/library-registry)
|
- Arduino Library Registry: [https://github.com/arduino/library-registry](https://github.com/arduino/library-registry)
|
||||||
|
|
||||||
- ESP Component Registry [https://components.espressif.com/components/esp32async/espasyncbebserver/](https://components.espressif.com/components/esp32async/espasyncbebserver/)
|
- ESP Component Registry [https://components.espressif.com/components/esp32async/espasyncwebserver](https://components.espressif.com/components/esp32async/espasyncwebserver)
|
||||||
|
|
||||||
- PlatformIO Registry: [https://registry.platformio.org/libraries/esp32async/ESPAsyncWebServer](https://registry.platformio.org/libraries/esp32async/ESPAsyncWebServer)
|
- PlatformIO Registry: [https://registry.platformio.org/libraries/esp32async/ESPAsyncWebServer](https://registry.platformio.org/libraries/esp32async/ESPAsyncWebServer)
|
||||||
|
|
||||||
|
@ -72,6 +72,19 @@ lib_deps =
|
||||||
ESP32Async/ESPAsyncWebServer
|
ESP32Async/ESPAsyncWebServer
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### LibreTiny (BK7231N/T, RTL8710B, etc.)
|
||||||
|
|
||||||
|
Version 1.9.1 or newer is required.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[env:stable]
|
||||||
|
platform = libretiny @ ^1.9.1
|
||||||
|
lib_ldf_mode = chain
|
||||||
|
lib_deps =
|
||||||
|
ESP32Async/AsyncTCP
|
||||||
|
ESP32Async/ESPAsyncWebServer
|
||||||
|
```
|
||||||
|
|
||||||
### Unofficial dependencies
|
### Unofficial dependencies
|
||||||
|
|
||||||
**AsyncTCPSock**
|
**AsyncTCPSock**
|
||||||
|
@ -100,7 +113,7 @@ platform = https://github.com/maxgerhardt/platform-raspberrypi.git
|
||||||
board = rpipicow
|
board = rpipicow
|
||||||
board_build.core = earlephilhower
|
board_build.core = earlephilhower
|
||||||
lib_deps =
|
lib_deps =
|
||||||
ayushsharma82/RPAsyncTCP@^1.3.1
|
ayushsharma82/RPAsyncTCP@^1.3.2
|
||||||
ESP32Async/ESPAsyncWebServer
|
ESP32Async/ESPAsyncWebServer
|
||||||
lib_ignore =
|
lib_ignore =
|
||||||
lwIP_ESPHost
|
lwIP_ESPHost
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "ESPAsyncWebServer",
|
"name": "ESPAsyncWebServer",
|
||||||
"version": "3.7.2",
|
"version": "3.7.10",
|
||||||
"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.",
|
"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/ESP32Async/ESPAsyncWebServer",
|
"homepage": "https://github.com/ESP32Async/ESPAsyncWebServer",
|
||||||
|
@ -18,14 +18,18 @@
|
||||||
"platforms": [
|
"platforms": [
|
||||||
"espressif32",
|
"espressif32",
|
||||||
"espressif8266",
|
"espressif8266",
|
||||||
"raspberrypi"
|
"raspberrypi",
|
||||||
|
"libretiny"
|
||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
{
|
{
|
||||||
"owner": "ESP32Async",
|
"owner": "ESP32Async",
|
||||||
"name": "AsyncTCP",
|
"name": "AsyncTCP",
|
||||||
"version": "^3.3.6",
|
"version": "^3.4.5",
|
||||||
"platforms": "espressif32"
|
"platforms": [
|
||||||
|
"espressif32",
|
||||||
|
"libretiny"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"owner": "ESP32Async",
|
"owner": "ESP32Async",
|
||||||
|
@ -40,7 +44,7 @@
|
||||||
{
|
{
|
||||||
"owner": "ayushsharma82",
|
"owner": "ayushsharma82",
|
||||||
"name": "RPAsyncTCP",
|
"name": "RPAsyncTCP",
|
||||||
"version": "^1.3.1",
|
"version": "^1.3.2",
|
||||||
"platforms": "raspberrypi"
|
"platforms": "raspberrypi"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
name=ESP Async WebServer
|
name=ESP Async WebServer
|
||||||
includes=ESPAsyncWebServer.h
|
includes=ESPAsyncWebServer.h
|
||||||
version=3.7.2
|
version=3.7.10
|
||||||
author=ESP32Async
|
author=ESP32Async
|
||||||
maintainer=ESP32Async
|
maintainer=ESP32Async
|
||||||
sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040
|
sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040
|
||||||
|
|
|
@ -193,7 +193,7 @@ AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, A
|
||||||
|
|
||||||
AsyncEventSourceClient::~AsyncEventSourceClient() {
|
AsyncEventSourceClient::~AsyncEventSourceClient() {
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
std::lock_guard<std::mutex> lock(_lockmq);
|
std::lock_guard<std::recursive_mutex> lock(_lockmq);
|
||||||
#endif
|
#endif
|
||||||
_messageQueue.clear();
|
_messageQueue.clear();
|
||||||
close();
|
close();
|
||||||
|
@ -211,7 +211,7 @@ bool AsyncEventSourceClient::_queueMessage(const char *message, size_t len) {
|
||||||
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
// length() is not thread-safe, thus acquiring the lock before this call..
|
// length() is not thread-safe, thus acquiring the lock before this call..
|
||||||
std::lock_guard<std::mutex> lock(_lockmq);
|
std::lock_guard<std::recursive_mutex> lock(_lockmq);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_messageQueue.emplace_back(message, len);
|
_messageQueue.emplace_back(message, len);
|
||||||
|
@ -241,7 +241,7 @@ bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) {
|
||||||
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
// length() is not thread-safe, thus acquiring the lock before this call..
|
// length() is not thread-safe, thus acquiring the lock before this call..
|
||||||
std::lock_guard<std::mutex> lock(_lockmq);
|
std::lock_guard<std::recursive_mutex> lock(_lockmq);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_messageQueue.emplace_back(std::move(msg));
|
_messageQueue.emplace_back(std::move(msg));
|
||||||
|
@ -261,7 +261,7 @@ bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) {
|
||||||
void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))) {
|
void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))) {
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
// Same here, acquiring the lock early
|
// Same here, acquiring the lock early
|
||||||
std::lock_guard<std::mutex> lock(_lockmq);
|
std::lock_guard<std::recursive_mutex> lock(_lockmq);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// adjust in-flight len
|
// adjust in-flight len
|
||||||
|
@ -290,7 +290,7 @@ void AsyncEventSourceClient::_onPoll() {
|
||||||
if (_messageQueue.size()) {
|
if (_messageQueue.size()) {
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
// Same here, acquiring the lock early
|
// Same here, acquiring the lock early
|
||||||
std::lock_guard<std::mutex> lock(_lockmq);
|
std::lock_guard<std::recursive_mutex> lock(_lockmq);
|
||||||
#endif
|
#endif
|
||||||
_runQueue();
|
_runQueue();
|
||||||
}
|
}
|
||||||
|
@ -367,7 +367,7 @@ void AsyncEventSource::_addClient(AsyncEventSourceClient *client) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||||
#endif
|
#endif
|
||||||
_clients.emplace_back(client);
|
_clients.emplace_back(client);
|
||||||
if (_connectcb) {
|
if (_connectcb) {
|
||||||
|
@ -382,7 +382,7 @@ void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient *client) {
|
||||||
_disconnectcb(client);
|
_disconnectcb(client);
|
||||||
}
|
}
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||||
#endif
|
#endif
|
||||||
for (auto i = _clients.begin(); i != _clients.end(); ++i) {
|
for (auto i = _clients.begin(); i != _clients.end(); ++i) {
|
||||||
if (i->get() == client) {
|
if (i->get() == client) {
|
||||||
|
@ -398,10 +398,15 @@ void AsyncEventSource::close() {
|
||||||
// iterator should remain valid even when AsyncEventSource::_handleDisconnect()
|
// iterator should remain valid even when AsyncEventSource::_handleDisconnect()
|
||||||
// is called very early
|
// is called very early
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||||
#endif
|
#endif
|
||||||
for (const auto &c : _clients) {
|
for (const auto &c : _clients) {
|
||||||
if (c->connected()) {
|
if (c->connected()) {
|
||||||
|
/**
|
||||||
|
* @brief: Fix self-deadlock by using recursive_mutex instead.
|
||||||
|
* Due to c->close() shall call the callback function _onDisconnect()
|
||||||
|
* The calling flow _onDisconnect() --> _handleDisconnect() --> deadlock
|
||||||
|
*/
|
||||||
c->close();
|
c->close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -412,7 +417,7 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
|
||||||
size_t aql = 0;
|
size_t aql = 0;
|
||||||
uint32_t nConnectedClients = 0;
|
uint32_t nConnectedClients = 0;
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||||
#endif
|
#endif
|
||||||
if (!_clients.size()) {
|
if (!_clients.size()) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -430,7 +435,7 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
|
||||||
AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
|
AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
|
||||||
AsyncEvent_SharedData_t shared_msg = std::make_shared<String>(generateEventMessage(message, event, id, reconnect));
|
AsyncEvent_SharedData_t shared_msg = std::make_shared<String>(generateEventMessage(message, event, id, reconnect));
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||||
#endif
|
#endif
|
||||||
size_t hits = 0;
|
size_t hits = 0;
|
||||||
size_t miss = 0;
|
size_t miss = 0;
|
||||||
|
@ -446,7 +451,7 @@ AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const c
|
||||||
|
|
||||||
size_t AsyncEventSource::count() const {
|
size_t AsyncEventSource::count() const {
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||||
#endif
|
#endif
|
||||||
size_t n_clients{0};
|
size_t n_clients{0};
|
||||||
for (const auto &i : _clients) {
|
for (const auto &i : _clients) {
|
||||||
|
|
|
@ -6,8 +6,13 @@
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
#ifdef ESP32
|
#if defined(ESP32) || defined(LIBRETINY)
|
||||||
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
||||||
|
#ifdef LIBRETINY
|
||||||
|
#ifdef round
|
||||||
|
#undef round
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||||
#define SSE_MAX_QUEUED_MESSAGES 32
|
#define SSE_MAX_QUEUED_MESSAGES 32
|
||||||
|
@ -129,7 +134,7 @@ private:
|
||||||
size_t _max_inflight{SSE_MAX_INFLIGH}; // max num of unacknowledged bytes that could be written to socket buffer
|
size_t _max_inflight{SSE_MAX_INFLIGH}; // max num of unacknowledged bytes that could be written to socket buffer
|
||||||
std::list<AsyncEventSourceMessage> _messageQueue;
|
std::list<AsyncEventSourceMessage> _messageQueue;
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
mutable std::mutex _lockmq;
|
mutable std::recursive_mutex _lockmq;
|
||||||
#endif
|
#endif
|
||||||
bool _queueMessage(const char *message, size_t len);
|
bool _queueMessage(const char *message, size_t len);
|
||||||
bool _queueMessage(AsyncEvent_SharedData_t &&msg);
|
bool _queueMessage(AsyncEvent_SharedData_t &&msg);
|
||||||
|
@ -230,7 +235,7 @@ private:
|
||||||
#ifdef ESP32
|
#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
|
||||||
mutable std::mutex _client_queue_lock;
|
mutable std::recursive_mutex _client_queue_lock;
|
||||||
#endif
|
#endif
|
||||||
ArEventHandlerFunction _connectcb = nullptr;
|
ArEventHandlerFunction _connectcb = nullptr;
|
||||||
ArEventHandlerFunction _disconnectcb = nullptr;
|
ArEventHandlerFunction _disconnectcb = nullptr;
|
||||||
|
|
|
@ -113,43 +113,64 @@ bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) cons
|
||||||
|
|
||||||
void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) {
|
void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) {
|
||||||
if (_onRequest) {
|
if (_onRequest) {
|
||||||
|
// GET request:
|
||||||
if (request->method() == HTTP_GET) {
|
if (request->method() == HTTP_GET) {
|
||||||
JsonVariant json;
|
JsonVariant json;
|
||||||
_onRequest(request, json);
|
_onRequest(request, json);
|
||||||
return;
|
return;
|
||||||
} else if (request->_tempObject != NULL) {
|
}
|
||||||
|
|
||||||
|
// POST / PUT / ... requests:
|
||||||
|
// check if JSON body is too large, if it is, don't deserialize
|
||||||
|
if (request->contentLength() > _maxContentLength) {
|
||||||
|
#ifdef ESP32
|
||||||
|
log_e("Content length exceeds maximum allowed");
|
||||||
|
#endif
|
||||||
|
request->send(413);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request->_tempObject == NULL) {
|
||||||
|
// there is no body
|
||||||
|
request->send(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#if ARDUINOJSON_VERSION_MAJOR == 5
|
#if ARDUINOJSON_VERSION_MAJOR == 5
|
||||||
DynamicJsonBuffer jsonBuffer;
|
DynamicJsonBuffer jsonBuffer;
|
||||||
JsonVariant json = jsonBuffer.parse((uint8_t *)(request->_tempObject));
|
JsonVariant json = jsonBuffer.parse((const char *)request->_tempObject);
|
||||||
if (json.success()) {
|
if (json.success()) {
|
||||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
|
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
|
||||||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
|
DeserializationError error = deserializeJson(jsonBuffer, (const char *)request->_tempObject);
|
||||||
if (!error) {
|
if (!error) {
|
||||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||||
#else
|
#else
|
||||||
JsonDocument jsonBuffer;
|
JsonDocument jsonBuffer;
|
||||||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
|
DeserializationError error = deserializeJson(jsonBuffer, (const char *)request->_tempObject);
|
||||||
if (!error) {
|
if (!error) {
|
||||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_onRequest(request, json);
|
_onRequest(request, json);
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
request->send(_contentLength > _maxContentLength ? 413 : 400);
|
|
||||||
} else {
|
} else {
|
||||||
request->send(500);
|
// error parsing the body
|
||||||
|
request->send(400);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
|
void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
|
||||||
if (_onRequest) {
|
if (_onRequest) {
|
||||||
_contentLength = total;
|
// ignore callback if size is larger than maxContentLength
|
||||||
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
|
if (total > _maxContentLength) {
|
||||||
request->_tempObject = malloc(total);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index == 0) {
|
||||||
|
// this check allows request->_tempObject to be initialized from a middleware
|
||||||
|
if (request->_tempObject == NULL) {
|
||||||
|
request->_tempObject = calloc(total + 1, sizeof(uint8_t)); // null-terminated string
|
||||||
if (request->_tempObject == NULL) {
|
if (request->_tempObject == NULL) {
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
log_e("Failed to allocate");
|
log_e("Failed to allocate");
|
||||||
|
@ -158,8 +179,11 @@ void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uin
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (request->_tempObject != NULL) {
|
if (request->_tempObject != NULL) {
|
||||||
memcpy((uint8_t *)(request->_tempObject) + index, data, len);
|
uint8_t *buffer = (uint8_t *)request->_tempObject;
|
||||||
|
memcpy(buffer + index, data, len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,6 @@ protected:
|
||||||
String _uri;
|
String _uri;
|
||||||
WebRequestMethodComposite _method;
|
WebRequestMethodComposite _method;
|
||||||
ArJsonRequestHandlerFunction _onRequest;
|
ArJsonRequestHandlerFunction _onRequest;
|
||||||
size_t _contentLength;
|
|
||||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
size_t maxJsonBufferSize;
|
size_t maxJsonBufferSize;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -3,30 +3,32 @@
|
||||||
|
|
||||||
#include "ESPAsyncWebServer.h"
|
#include "ESPAsyncWebServer.h"
|
||||||
|
|
||||||
AsyncWebHeader::AsyncWebHeader(const String &data) {
|
const AsyncWebHeader AsyncWebHeader::parse(const char *data) {
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers
|
||||||
|
// In HTTP/1.X, a header is a case-insensitive name followed by a colon, then optional whitespace which will be ignored, and finally by its value
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return;
|
return AsyncWebHeader(); // nullptr
|
||||||
}
|
}
|
||||||
int index = data.indexOf(':');
|
if (data[0] == '\0') {
|
||||||
if (index < 0) {
|
return AsyncWebHeader(); // empty string
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
_name = data.substring(0, index);
|
if (strchr(data, '\n') || strchr(data, '\r')) {
|
||||||
_value = data.substring(index + 2);
|
return AsyncWebHeader(); // Invalid header format
|
||||||
}
|
}
|
||||||
|
char *colon = strchr(data, ':');
|
||||||
String AsyncWebHeader::toString() const {
|
if (!colon) {
|
||||||
String str;
|
return AsyncWebHeader(); // separator not found
|
||||||
if (str.reserve(_name.length() + _value.length() + 2)) {
|
}
|
||||||
str.concat(_name);
|
if (colon == data) {
|
||||||
str.concat((char)0x3a);
|
return AsyncWebHeader(); // Header name cannot be empty
|
||||||
str.concat((char)0x20);
|
}
|
||||||
str.concat(_value);
|
char *startOfValue = colon + 1; // Skip the colon
|
||||||
str.concat(asyncsrv::T_rn);
|
// skip one optional whitespace after the colon
|
||||||
} else {
|
if (*startOfValue == ' ') {
|
||||||
#ifdef ESP32
|
startOfValue++;
|
||||||
log_e("Failed to allocate");
|
}
|
||||||
#endif
|
String name;
|
||||||
}
|
name.reserve(colon - data);
|
||||||
return str;
|
name.concat(data, colon - data);
|
||||||
|
return AsyncWebHeader(name, String(startOfValue));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
#include "ESPAsyncWebServer.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sends a file from the filesystem to the client, with optional gzip compression and ETag-based caching.
|
||||||
|
*
|
||||||
|
* This method serves files over HTTP from the provided filesystem. If a compressed version of the file
|
||||||
|
* (with a `.gz` extension) exists and uncompressed version does not exist, it serves the compressed file.
|
||||||
|
* It also handles ETag caching using the CRC32 value from the gzip trailer, responding with `304 Not Modified`
|
||||||
|
* if the client's `If-None-Match` header matches the generated ETag.
|
||||||
|
*
|
||||||
|
* @param fs Reference to the filesystem (SPIFFS, LittleFS, etc.).
|
||||||
|
* @param path Path to the file to be served.
|
||||||
|
* @param contentType Optional MIME type of the file to be sent.
|
||||||
|
* If contentType is "" it will be obtained from the file extension
|
||||||
|
* @param download If true, forces the file to be sent as a download.
|
||||||
|
* @param callback Optional template processor for dynamic content generation.
|
||||||
|
* Templates will not be processed in compressed files.
|
||||||
|
*
|
||||||
|
* @note If neither the file nor its compressed version exists, responds with `404 Not Found`.
|
||||||
|
*/
|
||||||
|
void AsyncWebServerRequest::send(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback) {
|
||||||
|
// Check uncompressed file first
|
||||||
|
if (fs.exists(path)) {
|
||||||
|
send(beginResponse(fs, path, contentType, download, callback));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle compressed version
|
||||||
|
const String gzPath = path + asyncsrv::T__gz;
|
||||||
|
File gzFile = fs.open(gzPath, "r");
|
||||||
|
|
||||||
|
// Compressed file not found or invalid
|
||||||
|
if (!gzFile.seek(gzFile.size() - 8)) {
|
||||||
|
send(404);
|
||||||
|
gzFile.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ETag validation
|
||||||
|
if (this->hasHeader(asyncsrv::T_INM)) {
|
||||||
|
// Generate server ETag from CRC in gzip trailer
|
||||||
|
uint8_t crcInTrailer[4];
|
||||||
|
gzFile.read(crcInTrailer, 4);
|
||||||
|
char serverETag[9];
|
||||||
|
_getEtag(crcInTrailer, serverETag);
|
||||||
|
|
||||||
|
// Compare with client's ETag
|
||||||
|
const AsyncWebHeader *inmHeader = this->getHeader(asyncsrv::T_INM);
|
||||||
|
if (inmHeader && inmHeader->value() == serverETag) {
|
||||||
|
gzFile.close();
|
||||||
|
this->send(304); // Not Modified
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send compressed file response
|
||||||
|
gzFile.close();
|
||||||
|
send(beginResponse(fs, path, contentType, download, callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generates an ETag string from a 4-byte trailer
|
||||||
|
*
|
||||||
|
* This function converts a 4-byte array into a hexadecimal ETag string enclosed in quotes.
|
||||||
|
*
|
||||||
|
* @param trailer[4] Input array of 4 bytes to convert to hexadecimal
|
||||||
|
* @param serverETag Output buffer to store the ETag
|
||||||
|
* Must be pre-allocated with minimum 9 bytes (8 hex + 1 null terminator)
|
||||||
|
*/
|
||||||
|
void AsyncWebServerRequest::_getEtag(uint8_t trailer[4], char *serverETag) {
|
||||||
|
static constexpr char hexChars[] = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
uint32_t data;
|
||||||
|
memcpy(&data, trailer, 4);
|
||||||
|
|
||||||
|
serverETag[0] = hexChars[(data >> 4) & 0x0F];
|
||||||
|
serverETag[1] = hexChars[data & 0x0F];
|
||||||
|
serverETag[2] = hexChars[(data >> 12) & 0x0F];
|
||||||
|
serverETag[3] = hexChars[(data >> 8) & 0x0F];
|
||||||
|
serverETag[4] = hexChars[(data >> 20) & 0x0F];
|
||||||
|
serverETag[5] = hexChars[(data >> 16) & 0x0F];
|
||||||
|
serverETag[6] = hexChars[(data >> 28)];
|
||||||
|
serverETag[7] = hexChars[(data >> 24) & 0x0F];
|
||||||
|
serverETag[8] = '\0';
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ extern "C" {
|
||||||
/** Minor version number (x.X.x) */
|
/** Minor version number (x.X.x) */
|
||||||
#define ASYNCWEBSERVER_VERSION_MINOR 7
|
#define ASYNCWEBSERVER_VERSION_MINOR 7
|
||||||
/** Patch version number (x.x.X) */
|
/** Patch version number (x.x.X) */
|
||||||
#define ASYNCWEBSERVER_VERSION_PATCH 2
|
#define ASYNCWEBSERVER_VERSION_PATCH 10
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Macro to convert version number into an integer
|
* Macro to convert version number into an integer
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
#include <rom/ets_sys.h>
|
#include <rom/ets_sys.h>
|
||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(ESP8266)
|
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(ESP8266)
|
||||||
#include <Hash.h>
|
#include <Hash.h>
|
||||||
|
#elif defined(LIBRETINY)
|
||||||
|
#include <mbedtls/sha1.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
using namespace asyncsrv;
|
using namespace asyncsrv;
|
||||||
|
@ -333,7 +335,7 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, Async
|
||||||
AsyncWebSocketClient::~AsyncWebSocketClient() {
|
AsyncWebSocketClient::~AsyncWebSocketClient() {
|
||||||
{
|
{
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
std::lock_guard<std::mutex> lock(_lock);
|
std::lock_guard<std::recursive_mutex> lock(_lock);
|
||||||
#endif
|
#endif
|
||||||
_messageQueue.clear();
|
_messageQueue.clear();
|
||||||
_controlQueue.clear();
|
_controlQueue.clear();
|
||||||
|
@ -351,7 +353,7 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
|
||||||
_lastMessageTime = millis();
|
_lastMessageTime = millis();
|
||||||
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
std::lock_guard<std::mutex> lock(_lock);
|
std::unique_lock<std::recursive_mutex> lock(_lock);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!_controlQueue.empty()) {
|
if (!_controlQueue.empty()) {
|
||||||
|
@ -362,6 +364,14 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
|
||||||
_controlQueue.pop_front();
|
_controlQueue.pop_front();
|
||||||
_status = WS_DISCONNECTED;
|
_status = WS_DISCONNECTED;
|
||||||
if (_client) {
|
if (_client) {
|
||||||
|
#ifdef ESP32
|
||||||
|
/*
|
||||||
|
Unlocking has to be called before return execution otherwise std::unique_lock ::~unique_lock() will get an exception pthread_mutex_unlock.
|
||||||
|
Due to _client->close(true) shall call the callback function _onDisconnect()
|
||||||
|
The calling flow _onDisconnect() --> _handleDisconnect() --> ~AsyncWebSocketClient()
|
||||||
|
*/
|
||||||
|
lock.unlock();
|
||||||
|
#endif
|
||||||
_client->close(true);
|
_client->close(true);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -385,7 +395,7 @@ void AsyncWebSocketClient::_onPoll() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
std::unique_lock<std::mutex> lock(_lock);
|
std::unique_lock<std::recursive_mutex> lock(_lock);
|
||||||
#endif
|
#endif
|
||||||
if (_client && _client->canSend() && (!_controlQueue.empty() || !_messageQueue.empty())) {
|
if (_client && _client->canSend() && (!_controlQueue.empty() || !_messageQueue.empty())) {
|
||||||
_runQueue();
|
_runQueue();
|
||||||
|
@ -415,21 +425,21 @@ void AsyncWebSocketClient::_runQueue() {
|
||||||
|
|
||||||
bool AsyncWebSocketClient::queueIsFull() const {
|
bool AsyncWebSocketClient::queueIsFull() const {
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
std::lock_guard<std::mutex> lock(_lock);
|
std::lock_guard<std::recursive_mutex> lock(_lock);
|
||||||
#endif
|
#endif
|
||||||
return (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED);
|
return (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AsyncWebSocketClient::queueLen() const {
|
size_t AsyncWebSocketClient::queueLen() const {
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
std::lock_guard<std::mutex> lock(_lock);
|
std::lock_guard<std::recursive_mutex> lock(_lock);
|
||||||
#endif
|
#endif
|
||||||
return _messageQueue.size();
|
return _messageQueue.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AsyncWebSocketClient::canSend() const {
|
bool AsyncWebSocketClient::canSend() const {
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
std::lock_guard<std::mutex> lock(_lock);
|
std::lock_guard<std::recursive_mutex> lock(_lock);
|
||||||
#endif
|
#endif
|
||||||
return _messageQueue.size() < WS_MAX_QUEUED_MESSAGES;
|
return _messageQueue.size() < WS_MAX_QUEUED_MESSAGES;
|
||||||
}
|
}
|
||||||
|
@ -440,7 +450,7 @@ bool AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t *data, si
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
std::lock_guard<std::mutex> lock(_lock);
|
std::lock_guard<std::recursive_mutex> lock(_lock);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_controlQueue.emplace_back(opcode, data, len, mask);
|
_controlQueue.emplace_back(opcode, data, len, mask);
|
||||||
|
@ -458,7 +468,7 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
std::lock_guard<std::mutex> lock(_lock);
|
std::unique_lock<std::recursive_mutex> lock(_lock);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) {
|
if (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) {
|
||||||
|
@ -466,6 +476,14 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint
|
||||||
_status = WS_DISCONNECTED;
|
_status = WS_DISCONNECTED;
|
||||||
|
|
||||||
if (_client) {
|
if (_client) {
|
||||||
|
#ifdef ESP32
|
||||||
|
/*
|
||||||
|
Unlocking has to be called before return execution otherwise std::unique_lock ::~unique_lock() will get an exception pthread_mutex_unlock.
|
||||||
|
Due to _client->close(true) shall call the callback function _onDisconnect()
|
||||||
|
The calling flow _onDisconnect() --> _handleDisconnect() --> ~AsyncWebSocketClient()
|
||||||
|
*/
|
||||||
|
lock.unlock();
|
||||||
|
#endif
|
||||||
_client->close(true);
|
_client->close(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,6 +569,7 @@ void AsyncWebSocketClient::_onTimeout(uint32_t time) {
|
||||||
void AsyncWebSocketClient::_onDisconnect() {
|
void AsyncWebSocketClient::_onDisconnect() {
|
||||||
// Serial.println("onDis");
|
// Serial.println("onDis");
|
||||||
_client = nullptr;
|
_client = nullptr;
|
||||||
|
_server->_handleDisconnect(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
|
void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
|
||||||
|
@ -857,6 +876,16 @@ AsyncWebSocketClient *AsyncWebSocket::_newClient(AsyncWebServerRequest *request)
|
||||||
return &_clients.back();
|
return &_clients.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient *client) {
|
||||||
|
const auto client_id = client->id();
|
||||||
|
const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [client_id](const AsyncWebSocketClient &c) {
|
||||||
|
return c.id() == client_id;
|
||||||
|
});
|
||||||
|
if (iter != std::end(_clients)) {
|
||||||
|
_clients.erase(iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool AsyncWebSocket::availableForWriteAll() {
|
bool AsyncWebSocket::availableForWriteAll() {
|
||||||
return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) {
|
return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) {
|
||||||
return c.queueIsFull();
|
return c.queueIsFull();
|
||||||
|
@ -1300,11 +1329,20 @@ AsyncWebSocketResponse::AsyncWebSocketResponse(const String &key, AsyncWebSocket
|
||||||
}
|
}
|
||||||
k.concat(key);
|
k.concat(key);
|
||||||
k.concat(WS_STR_UUID);
|
k.concat(WS_STR_UUID);
|
||||||
|
#ifdef LIBRETINY
|
||||||
|
mbedtls_sha1_context ctx;
|
||||||
|
mbedtls_sha1_init(&ctx);
|
||||||
|
mbedtls_sha1_starts(&ctx);
|
||||||
|
mbedtls_sha1_update(&ctx, (const uint8_t *)k.c_str(), k.length());
|
||||||
|
mbedtls_sha1_finish(&ctx, hash);
|
||||||
|
mbedtls_sha1_free(&ctx);
|
||||||
|
#else
|
||||||
SHA1Builder sha1;
|
SHA1Builder sha1;
|
||||||
sha1.begin();
|
sha1.begin();
|
||||||
sha1.add((const uint8_t *)k.c_str(), k.length());
|
sha1.add((const uint8_t *)k.c_str(), k.length());
|
||||||
sha1.calculate();
|
sha1.calculate();
|
||||||
sha1.getBytes(hash);
|
sha1.getBytes(hash);
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
base64_encodestate _state;
|
base64_encodestate _state;
|
||||||
base64_init_encodestate(&_state);
|
base64_init_encodestate(&_state);
|
||||||
|
|
|
@ -5,8 +5,14 @@
|
||||||
#define ASYNCWEBSOCKET_H_
|
#define ASYNCWEBSOCKET_H_
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#ifdef ESP32
|
|
||||||
|
#if defined(ESP32) || defined(LIBRETINY)
|
||||||
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
||||||
|
#ifdef LIBRETINY
|
||||||
|
#ifdef round
|
||||||
|
#undef round
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||||
#define WS_MAX_QUEUED_MESSAGES 32
|
#define WS_MAX_QUEUED_MESSAGES 32
|
||||||
|
@ -152,7 +158,7 @@ private:
|
||||||
uint32_t _clientId;
|
uint32_t _clientId;
|
||||||
AwsClientStatus _status;
|
AwsClientStatus _status;
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
mutable std::mutex _lock;
|
mutable std::recursive_mutex _lock;
|
||||||
#endif
|
#endif
|
||||||
std::deque<AsyncWebSocketControl> _controlQueue;
|
std::deque<AsyncWebSocketControl> _controlQueue;
|
||||||
std::deque<AsyncWebSocketMessage> _messageQueue;
|
std::deque<AsyncWebSocketMessage> _messageQueue;
|
||||||
|
@ -291,7 +297,7 @@ private:
|
||||||
String _url;
|
String _url;
|
||||||
std::list<AsyncWebSocketClient> _clients;
|
std::list<AsyncWebSocketClient> _clients;
|
||||||
uint32_t _cNextId;
|
uint32_t _cNextId;
|
||||||
AwsEventHandler _eventHandler{nullptr};
|
AwsEventHandler _eventHandler;
|
||||||
AwsHandshakeHandler _handshakeHandler;
|
AwsHandshakeHandler _handshakeHandler;
|
||||||
bool _enabled;
|
bool _enabled;
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
|
@ -305,8 +311,8 @@ public:
|
||||||
PARTIALLY_ENQUEUED = 2,
|
PARTIALLY_ENQUEUED = 2,
|
||||||
} SendStatus;
|
} SendStatus;
|
||||||
|
|
||||||
explicit AsyncWebSocket(const char *url) : _url(url), _cNextId(1), _enabled(true) {}
|
explicit AsyncWebSocket(const char *url, AwsEventHandler handler = nullptr) : _url(url), _cNextId(1), _eventHandler(handler), _enabled(true) {}
|
||||||
AsyncWebSocket(const String &url) : _url(url), _cNextId(1), _enabled(true) {}
|
AsyncWebSocket(const String &url, AwsEventHandler handler = nullptr) : _url(url), _cNextId(1), _eventHandler(handler), _enabled(true) {}
|
||||||
~AsyncWebSocket(){};
|
~AsyncWebSocket(){};
|
||||||
const char *url() const {
|
const char *url() const {
|
||||||
return _url.c_str();
|
return _url.c_str();
|
||||||
|
@ -385,6 +391,7 @@ public:
|
||||||
return _cNextId++;
|
return _cNextId++;
|
||||||
}
|
}
|
||||||
AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request);
|
AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request);
|
||||||
|
void _handleDisconnect(AsyncWebSocketClient *client);
|
||||||
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);
|
||||||
bool canHandle(AsyncWebServerRequest *request) const override final;
|
bool canHandle(AsyncWebServerRequest *request) const override final;
|
||||||
void handleRequest(AsyncWebServerRequest *request) override final;
|
void handleRequest(AsyncWebServerRequest *request) override final;
|
||||||
|
@ -413,4 +420,86 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AsyncWebSocketMessageHandler {
|
||||||
|
public:
|
||||||
|
AwsEventHandler eventHandler() const {
|
||||||
|
return _handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onConnect(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client)> onConnect) {
|
||||||
|
_onConnect = onConnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onDisconnect(std::function<void(AsyncWebSocket *server, uint32_t clientId)> onDisconnect) {
|
||||||
|
_onDisconnect = onDisconnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error callback
|
||||||
|
* @param reason null-terminated string
|
||||||
|
* @param len length of the string
|
||||||
|
*/
|
||||||
|
void onError(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, uint16_t errorCode, const char *reason, size_t len)> onError) {
|
||||||
|
_onError = onError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete message callback
|
||||||
|
* @param data pointer to the data (binary or null-terminated string). This handler expects the user to know which data type he uses.
|
||||||
|
*/
|
||||||
|
void onMessage(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const uint8_t *data, size_t len)> onMessage) {
|
||||||
|
_onMessage = onMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragmented message callback
|
||||||
|
* @param data pointer to the data (binary or null-terminated string), will be null-terminated. This handler expects the user to know which data type he uses.
|
||||||
|
*/
|
||||||
|
// clang-format off
|
||||||
|
void onFragment(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const AwsFrameInfo *frameInfo, const uint8_t *data, size_t len)> onFragment) {
|
||||||
|
_onFragment = onFragment;
|
||||||
|
}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
private:
|
||||||
|
// clang-format off
|
||||||
|
std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client)> _onConnect;
|
||||||
|
std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, uint16_t errorCode, const char *reason, size_t len)> _onError;
|
||||||
|
std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const uint8_t *data, size_t len)> _onMessage;
|
||||||
|
std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const AwsFrameInfo *frameInfo, const uint8_t *data, size_t len)> _onFragment;
|
||||||
|
std::function<void(AsyncWebSocket *server, uint32_t clientId)> _onDisconnect;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
// this handler is meant to only support 1-frame messages (== unfragmented messages)
|
||||||
|
AwsEventHandler _handler = [this](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
|
||||||
|
if (type == WS_EVT_CONNECT) {
|
||||||
|
if (_onConnect) {
|
||||||
|
_onConnect(server, client);
|
||||||
|
}
|
||||||
|
} else if (type == WS_EVT_DISCONNECT) {
|
||||||
|
if (_onDisconnect) {
|
||||||
|
_onDisconnect(server, client->id());
|
||||||
|
}
|
||||||
|
} else if (type == WS_EVT_ERROR) {
|
||||||
|
if (_onError) {
|
||||||
|
_onError(server, client, *((uint16_t *)arg), (const char *)data, len);
|
||||||
|
}
|
||||||
|
} else if (type == WS_EVT_DATA) {
|
||||||
|
AwsFrameInfo *info = (AwsFrameInfo *)arg;
|
||||||
|
if (info->opcode == WS_TEXT) {
|
||||||
|
data[len] = 0;
|
||||||
|
}
|
||||||
|
if (info->final && info->index == 0 && info->len == len) {
|
||||||
|
if (_onMessage) {
|
||||||
|
_onMessage(server, client, data, len);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_onFragment) {
|
||||||
|
_onFragment(server, client, info, data, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
#endif /* ASYNCWEBSOCKET_H_ */
|
#endif /* ASYNCWEBSOCKET_H_ */
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
#ifndef _ESPAsyncWebServer_H_
|
#ifndef _ESPAsyncWebServer_H_
|
||||||
#define _ESPAsyncWebServer_H_
|
#define _ESPAsyncWebServer_H_
|
||||||
|
|
||||||
#include "Arduino.h"
|
#include <Arduino.h>
|
||||||
|
#include <FS.h>
|
||||||
|
#include <lwip/tcpbase.h>
|
||||||
|
|
||||||
#include "FS.h"
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
@ -14,16 +15,13 @@
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef ESP32
|
#if defined(ESP32) || defined(LIBRETINY)
|
||||||
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
||||||
#include <WiFi.h>
|
|
||||||
#elif defined(ESP8266)
|
#elif defined(ESP8266)
|
||||||
#include <ESP8266WiFi.h>
|
|
||||||
#include <ESPAsyncTCP.h>
|
#include <ESPAsyncTCP.h>
|
||||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
||||||
#include <RPAsyncTCP.h>
|
#include <RPAsyncTCP.h>
|
||||||
#include <HTTP_Method.h>
|
#include <HTTP_Method.h>
|
||||||
#include <WiFi.h>
|
|
||||||
#include <http_parser.h>
|
#include <http_parser.h>
|
||||||
#else
|
#else
|
||||||
#error Platform not supported
|
#error Platform not supported
|
||||||
|
@ -131,12 +129,20 @@ private:
|
||||||
String _value;
|
String _value;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
AsyncWebHeader() {}
|
||||||
AsyncWebHeader(const AsyncWebHeader &) = default;
|
AsyncWebHeader(const AsyncWebHeader &) = default;
|
||||||
|
AsyncWebHeader(AsyncWebHeader &&) = default;
|
||||||
AsyncWebHeader(const char *name, const char *value) : _name(name), _value(value) {}
|
AsyncWebHeader(const char *name, const char *value) : _name(name), _value(value) {}
|
||||||
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);
|
|
||||||
|
#ifndef ESP8266
|
||||||
|
[[deprecated("Use AsyncWebHeader::parse(data) instead")]]
|
||||||
|
#endif
|
||||||
|
AsyncWebHeader(const String &data)
|
||||||
|
: AsyncWebHeader(parse(data)){};
|
||||||
|
|
||||||
AsyncWebHeader &operator=(const AsyncWebHeader &) = default;
|
AsyncWebHeader &operator=(const AsyncWebHeader &) = default;
|
||||||
|
AsyncWebHeader &operator=(AsyncWebHeader &&other) = default;
|
||||||
|
|
||||||
const String &name() const {
|
const String &name() const {
|
||||||
return _name;
|
return _name;
|
||||||
|
@ -144,7 +150,18 @@ public:
|
||||||
const String &value() const {
|
const String &value() const {
|
||||||
return _value;
|
return _value;
|
||||||
}
|
}
|
||||||
|
|
||||||
String toString() const;
|
String toString() const;
|
||||||
|
|
||||||
|
// returns true if the header is valid
|
||||||
|
operator bool() const {
|
||||||
|
return _name.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
static const AsyncWebHeader parse(const String &data) {
|
||||||
|
return parse(data.c_str());
|
||||||
|
}
|
||||||
|
static const AsyncWebHeader parse(const char *data);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -180,6 +197,7 @@ class AsyncWebServerRequest {
|
||||||
using FS = fs::FS;
|
using FS = fs::FS;
|
||||||
friend class AsyncWebServer;
|
friend class AsyncWebServer;
|
||||||
friend class AsyncCallbackWebHandler;
|
friend class AsyncCallbackWebHandler;
|
||||||
|
friend class AsyncFileResponse;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AsyncClient *_client;
|
AsyncClient *_client;
|
||||||
|
@ -251,6 +269,8 @@ private:
|
||||||
void _send();
|
void _send();
|
||||||
void _runMiddlewareChain();
|
void _runMiddlewareChain();
|
||||||
|
|
||||||
|
static void _getEtag(uint8_t trailer[4], char *serverETag);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
File _tempFile;
|
File _tempFile;
|
||||||
void *_tempObject;
|
void *_tempObject;
|
||||||
|
@ -363,13 +383,7 @@ public:
|
||||||
send(beginResponse(code, contentType, content, len, callback));
|
send(beginResponse(code, contentType, content, len, callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
void send(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) {
|
void send(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
|
||||||
if (fs.exists(path) || (!download && fs.exists(path + asyncsrv::T__gz))) {
|
|
||||||
send(beginResponse(fs, path, contentType, download, callback));
|
|
||||||
} else {
|
|
||||||
send(404);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void send(FS &fs, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr) {
|
void send(FS &fs, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr) {
|
||||||
send(fs, path, contentType.c_str(), download, callback);
|
send(fs, path, contentType.c_str(), download, callback);
|
||||||
}
|
}
|
||||||
|
@ -462,7 +476,9 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *beginChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
AsyncWebServerResponse *beginChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
||||||
AsyncWebServerResponse *beginChunkedResponse(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
|
AsyncWebServerResponse *beginChunkedResponse(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
|
||||||
|
return beginChunkedResponse(contentType.c_str(), callback, templateCallback);
|
||||||
|
}
|
||||||
|
|
||||||
AsyncResponseStream *beginResponseStream(const char *contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE);
|
AsyncResponseStream *beginResponseStream(const char *contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE);
|
||||||
AsyncResponseStream *beginResponseStream(const String &contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE) {
|
AsyncResponseStream *beginResponseStream(const String &contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE) {
|
||||||
|
@ -531,6 +547,9 @@ public:
|
||||||
* @return const AsyncWebParameter*
|
* @return const AsyncWebParameter*
|
||||||
*/
|
*/
|
||||||
const AsyncWebParameter *getParam(size_t num) const;
|
const AsyncWebParameter *getParam(size_t num) const;
|
||||||
|
const AsyncWebParameter *getParam(int num) const {
|
||||||
|
return num < 0 ? nullptr : getParam((size_t)num);
|
||||||
|
}
|
||||||
|
|
||||||
size_t args() const {
|
size_t args() const {
|
||||||
return params();
|
return params();
|
||||||
|
@ -546,7 +565,13 @@ public:
|
||||||
const String &arg(const __FlashStringHelper *data) const; // get request argument value by F(name)
|
const String &arg(const __FlashStringHelper *data) const; // get request argument value by F(name)
|
||||||
#endif
|
#endif
|
||||||
const String &arg(size_t i) const; // get request argument value by number
|
const String &arg(size_t i) const; // get request argument value by number
|
||||||
|
const String &arg(int i) const {
|
||||||
|
return i < 0 ? emptyString : arg((size_t)i);
|
||||||
|
};
|
||||||
const String &argName(size_t i) const; // get request argument name by number
|
const String &argName(size_t i) const; // get request argument name by number
|
||||||
|
const String &argName(int i) const {
|
||||||
|
return i < 0 ? emptyString : argName((size_t)i);
|
||||||
|
};
|
||||||
bool hasArg(const char *name) const; // check if argument exists
|
bool hasArg(const char *name) const; // check if argument exists
|
||||||
bool hasArg(const String &name) const {
|
bool hasArg(const String &name) const {
|
||||||
return hasArg(name.c_str());
|
return hasArg(name.c_str());
|
||||||
|
@ -556,6 +581,9 @@ public:
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const String &ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const;
|
const String &ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const;
|
||||||
|
const String &ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(int i) const {
|
||||||
|
return i < 0 ? emptyString : pathArg((size_t)i);
|
||||||
|
}
|
||||||
|
|
||||||
// get request header value by name
|
// get request header value by name
|
||||||
const String &header(const char *name) const;
|
const String &header(const char *name) const;
|
||||||
|
@ -568,7 +596,13 @@ public:
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const String &header(size_t i) const; // get request header value by number
|
const String &header(size_t i) const; // get request header value by number
|
||||||
|
const String &header(int i) const {
|
||||||
|
return i < 0 ? emptyString : header((size_t)i);
|
||||||
|
};
|
||||||
const String &headerName(size_t i) const; // get request header name by number
|
const String &headerName(size_t i) const; // get request header name by number
|
||||||
|
const String &headerName(int i) const {
|
||||||
|
return i < 0 ? emptyString : headerName((size_t)i);
|
||||||
|
};
|
||||||
|
|
||||||
size_t headers() const; // get header count
|
size_t headers() const; // get header count
|
||||||
|
|
||||||
|
@ -590,6 +624,9 @@ public:
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const AsyncWebHeader *getHeader(size_t num) const;
|
const AsyncWebHeader *getHeader(size_t num) const;
|
||||||
|
const AsyncWebHeader *getHeader(int num) const {
|
||||||
|
return num < 0 ? nullptr : getHeader((size_t)num);
|
||||||
|
};
|
||||||
|
|
||||||
const std::list<AsyncWebHeader> &getHeaders() const {
|
const std::list<AsyncWebHeader> &getHeaders() const {
|
||||||
return _headers;
|
return _headers;
|
||||||
|
@ -1011,6 +1048,10 @@ public:
|
||||||
setContentType(type.c_str());
|
setContentType(type.c_str());
|
||||||
}
|
}
|
||||||
void setContentType(const char *type);
|
void setContentType(const char *type);
|
||||||
|
bool addHeader(AsyncWebHeader &&header, bool replaceExisting = true);
|
||||||
|
bool addHeader(const AsyncWebHeader &header, bool replaceExisting = true) {
|
||||||
|
return header && addHeader(header.name(), header.value(), replaceExisting);
|
||||||
|
}
|
||||||
bool addHeader(const char *name, const char *value, bool replaceExisting = true);
|
bool addHeader(const char *name, const char *value, bool replaceExisting = true);
|
||||||
bool addHeader(const String &name, const String &value, bool replaceExisting = true) {
|
bool addHeader(const String &name, const String &value, bool replaceExisting = true) {
|
||||||
return addHeader(name.c_str(), value.c_str(), replaceExisting);
|
return addHeader(name.c_str(), value.c_str(), replaceExisting);
|
||||||
|
@ -1069,6 +1110,15 @@ public:
|
||||||
void begin();
|
void begin();
|
||||||
void end();
|
void end();
|
||||||
|
|
||||||
|
tcp_state state() const {
|
||||||
|
#ifdef ESP8266
|
||||||
|
// ESPAsyncTCP and RPAsyncTCP methods are not corrected declared with const for immutable ones.
|
||||||
|
return static_cast<tcp_state>(const_cast<AsyncWebServer *>(this)->_server.status());
|
||||||
|
#else
|
||||||
|
return static_cast<tcp_state>(_server.status());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#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);
|
||||||
|
|
|
@ -172,7 +172,11 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNex
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_out->print(F("* Connection from "));
|
_out->print(F("* Connection from "));
|
||||||
|
#ifndef LIBRETINY
|
||||||
_out->print(request->client()->remoteIP().toString());
|
_out->print(request->client()->remoteIP().toString());
|
||||||
|
#else
|
||||||
|
_out->print(request->client()->remoteIP());
|
||||||
|
#endif
|
||||||
_out->print(':');
|
_out->print(':');
|
||||||
_out->println(request->client()->remotePort());
|
_out->println(request->client()->remotePort());
|
||||||
_out->print('>');
|
_out->print('>');
|
||||||
|
|
|
@ -19,7 +19,6 @@ class AsyncStaticWebHandler : public AsyncWebHandler {
|
||||||
private:
|
private:
|
||||||
bool _getFile(AsyncWebServerRequest *request) const;
|
bool _getFile(AsyncWebServerRequest *request) const;
|
||||||
bool _searchFile(AsyncWebServerRequest *request, const String &path);
|
bool _searchFile(AsyncWebServerRequest *request, const String &path);
|
||||||
uint8_t _countBits(const uint8_t value) const;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
FS _fs;
|
FS _fs;
|
||||||
|
|
|
@ -187,15 +187,6 @@ bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest *request, const St
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const {
|
|
||||||
uint8_t w = value;
|
|
||||||
uint8_t n;
|
|
||||||
for (n = 0; w != 0; n++) {
|
|
||||||
w &= w - 1;
|
|
||||||
}
|
|
||||||
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((char *)request->_tempObject);
|
String filename((char *)request->_tempObject);
|
||||||
|
@ -218,11 +209,14 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) {
|
||||||
char buf[len];
|
char buf[len];
|
||||||
char *ret = lltoa(lw ^ request->_tempFile.size(), buf, len, 10);
|
char *ret = lltoa(lw ^ request->_tempFile.size(), buf, len, 10);
|
||||||
etag = ret ? String(ret) : String(request->_tempFile.size());
|
etag = ret ? String(ret) : String(request->_tempFile.size());
|
||||||
|
#elif defined(LIBRETINY)
|
||||||
|
long val = lw ^ request->_tempFile.size();
|
||||||
|
etag = String(val);
|
||||||
#else
|
#else
|
||||||
etag = lw ^ request->_tempFile.size(); // etag combines file size and lastmod timestamp
|
etag = lw ^ request->_tempFile.size(); // etag combines file size and lastmod timestamp
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
|
||||||
etag = String(request->_tempFile.size());
|
etag = String(request->_tempFile.size());
|
||||||
#else
|
#else
|
||||||
etag = request->_tempFile.size();
|
etag = request->_tempFile.size();
|
||||||
|
|
|
@ -22,10 +22,10 @@ enum {
|
||||||
};
|
};
|
||||||
|
|
||||||
AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
|
AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
|
||||||
: _client(c), _server(s), _handler(NULL), _response(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY), _url(), _host(),
|
: _client(c), _server(s), _handler(NULL), _response(NULL), _onDisconnectfn(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY),
|
||||||
_contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false), _isPlainPost(false),
|
_url(), _host(), _contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false),
|
||||||
_expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), _itemSize(0), _itemName(),
|
_isPlainPost(false), _expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0),
|
||||||
_itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) {
|
_itemSize(0), _itemName(), _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) {
|
||||||
c->onError(
|
c->onError(
|
||||||
[](void *r, AsyncClient *c, int8_t error) {
|
[](void *r, AsyncClient *c, int8_t error) {
|
||||||
(void)c;
|
(void)c;
|
||||||
|
@ -341,10 +341,10 @@ bool AsyncWebServerRequest::_parseReqHead() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AsyncWebServerRequest::_parseReqHeader() {
|
bool AsyncWebServerRequest::_parseReqHeader() {
|
||||||
int index = _temp.indexOf(':');
|
AsyncWebHeader header = AsyncWebHeader::parse(_temp);
|
||||||
if (index) {
|
if (header) {
|
||||||
String name(_temp.substring(0, index));
|
const String &name = header.name();
|
||||||
String value(_temp.substring(index + 2));
|
const String &value = header.value();
|
||||||
if (name.equalsIgnoreCase(T_Host)) {
|
if (name.equalsIgnoreCase(T_Host)) {
|
||||||
_host = value;
|
_host = value;
|
||||||
} else if (name.equalsIgnoreCase(T_Content_Type)) {
|
} else if (name.equalsIgnoreCase(T_Content_Type)) {
|
||||||
|
@ -392,9 +392,9 @@ bool AsyncWebServerRequest::_parseReqHeader() {
|
||||||
_reqconntype = RCT_EVENT;
|
_reqconntype = RCT_EVENT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_headers.emplace_back(name, value);
|
_headers.emplace_back(std::move(header));
|
||||||
}
|
}
|
||||||
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
|
||||||
// Ancient PRI core does not have String::clear() method 8-()
|
// Ancient PRI core does not have String::clear() method 8-()
|
||||||
_temp = emptyString;
|
_temp = emptyString;
|
||||||
#else
|
#else
|
||||||
|
@ -419,7 +419,7 @@ void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) {
|
||||||
_params.emplace_back(name, urlDecode(value), true);
|
_params.emplace_back(name, urlDecode(value), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
|
||||||
// Ancient PRI core does not have String::clear() method 8-()
|
// Ancient PRI core does not have String::clear() method 8-()
|
||||||
_temp = emptyString;
|
_temp = emptyString;
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
#undef max
|
#undef max
|
||||||
#endif
|
#endif
|
||||||
#include "literals.h"
|
#include "literals.h"
|
||||||
#include <StreamString.h>
|
#include <cbuf.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ public:
|
||||||
|
|
||||||
class AsyncResponseStream : public AsyncAbstractResponse, public Print {
|
class AsyncResponseStream : public AsyncAbstractResponse, public Print {
|
||||||
private:
|
private:
|
||||||
StreamString _content;
|
std::unique_ptr<cbuf> _content;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AsyncResponseStream(const char *contentType, size_t bufferSize);
|
AsyncResponseStream(const char *contentType, size_t bufferSize);
|
||||||
|
@ -168,6 +168,12 @@ public:
|
||||||
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
|
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
|
||||||
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);
|
||||||
|
/**
|
||||||
|
* @brief Returns the number of bytes available in the stream.
|
||||||
|
*/
|
||||||
|
size_t available() const {
|
||||||
|
return _content->available();
|
||||||
|
}
|
||||||
using Print::write;
|
using Print::write;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -134,6 +134,30 @@ bool AsyncWebServerResponse::headerMustBePresentOnce(const String &name) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AsyncWebServerResponse::addHeader(AsyncWebHeader &&header, bool replaceExisting) {
|
||||||
|
if (!header) {
|
||||||
|
return false; // invalid header
|
||||||
|
}
|
||||||
|
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
|
||||||
|
if (i->name().equalsIgnoreCase(header.name())) {
|
||||||
|
// header already set
|
||||||
|
if (replaceExisting) {
|
||||||
|
// remove, break and add the new one
|
||||||
|
_headers.erase(i);
|
||||||
|
break;
|
||||||
|
} else if (headerMustBePresentOnce(i->name())) { // we can have only one header with that name
|
||||||
|
// do not update
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
break; // accept multiple headers with the same name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// header was not found found, or existing one was removed
|
||||||
|
_headers.emplace_back(std::move(header));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool AsyncWebServerResponse::addHeader(const char *name, const char *value, bool replaceExisting) {
|
bool AsyncWebServerResponse::addHeader(const char *name, const char *value, bool replaceExisting) {
|
||||||
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
|
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
|
||||||
if (i->name().equalsIgnoreCase(name)) {
|
if (i->name().equalsIgnoreCase(name)) {
|
||||||
|
@ -595,6 +619,16 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t *data, size
|
||||||
* File Response
|
* File Response
|
||||||
* */
|
* */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the content type based on the file path extension
|
||||||
|
*
|
||||||
|
* This method determines the appropriate MIME content type for a file based on its
|
||||||
|
* file extension. It supports both external content type functions (if available)
|
||||||
|
* and an internal mapping of common file extensions to their corresponding MIME types.
|
||||||
|
*
|
||||||
|
* @param path The file path string from which to extract the extension
|
||||||
|
* @note The method modifies the internal _contentType member variable
|
||||||
|
*/
|
||||||
void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
|
void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
|
||||||
#if HAVE_EXTERN_GET_Content_Type_FUNCTION
|
#if HAVE_EXTERN_GET_Content_Type_FUNCTION
|
||||||
#ifndef ESP8266
|
#ifndef ESP8266
|
||||||
|
@ -604,41 +638,47 @@ void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
|
||||||
#endif
|
#endif
|
||||||
_contentType = getContentType(path);
|
_contentType = getContentType(path);
|
||||||
#else
|
#else
|
||||||
if (path.endsWith(T__html)) {
|
const char *cpath = path.c_str();
|
||||||
|
const char *dot = strrchr(cpath, '.');
|
||||||
|
|
||||||
|
if (!dot) {
|
||||||
|
_contentType = T_text_plain;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(dot, T__html) == 0 || strcmp(dot, T__htm) == 0) {
|
||||||
_contentType = T_text_html;
|
_contentType = T_text_html;
|
||||||
} else if (path.endsWith(T__htm)) {
|
} else if (strcmp(dot, T__css) == 0) {
|
||||||
_contentType = T_text_html;
|
|
||||||
} else if (path.endsWith(T__css)) {
|
|
||||||
_contentType = T_text_css;
|
_contentType = T_text_css;
|
||||||
} else if (path.endsWith(T__json)) {
|
} else if (strcmp(dot, T__js) == 0) {
|
||||||
_contentType = T_application_json;
|
|
||||||
} else if (path.endsWith(T__js)) {
|
|
||||||
_contentType = T_application_javascript;
|
_contentType = T_application_javascript;
|
||||||
} else if (path.endsWith(T__png)) {
|
} else if (strcmp(dot, T__json) == 0) {
|
||||||
|
_contentType = T_application_json;
|
||||||
|
} else if (strcmp(dot, T__png) == 0) {
|
||||||
_contentType = T_image_png;
|
_contentType = T_image_png;
|
||||||
} else if (path.endsWith(T__gif)) {
|
} else if (strcmp(dot, T__ico) == 0) {
|
||||||
_contentType = T_image_gif;
|
|
||||||
} else if (path.endsWith(T__jpg)) {
|
|
||||||
_contentType = T_image_jpeg;
|
|
||||||
} else if (path.endsWith(T__ico)) {
|
|
||||||
_contentType = T_image_x_icon;
|
_contentType = T_image_x_icon;
|
||||||
} else if (path.endsWith(T__svg)) {
|
} else if (strcmp(dot, T__svg) == 0) {
|
||||||
_contentType = T_image_svg_xml;
|
_contentType = T_image_svg_xml;
|
||||||
} else if (path.endsWith(T__eot)) {
|
} else if (strcmp(dot, T__jpg) == 0) {
|
||||||
_contentType = T_font_eot;
|
_contentType = T_image_jpeg;
|
||||||
} else if (path.endsWith(T__woff)) {
|
} else if (strcmp(dot, T__gif) == 0) {
|
||||||
_contentType = T_font_woff;
|
_contentType = T_image_gif;
|
||||||
} else if (path.endsWith(T__woff2)) {
|
} else if (strcmp(dot, T__woff2) == 0) {
|
||||||
_contentType = T_font_woff2;
|
_contentType = T_font_woff2;
|
||||||
} else if (path.endsWith(T__ttf)) {
|
} else if (strcmp(dot, T__woff) == 0) {
|
||||||
|
_contentType = T_font_woff;
|
||||||
|
} else if (strcmp(dot, T__ttf) == 0) {
|
||||||
_contentType = T_font_ttf;
|
_contentType = T_font_ttf;
|
||||||
} else if (path.endsWith(T__xml)) {
|
} else if (strcmp(dot, T__eot) == 0) {
|
||||||
|
_contentType = T_font_eot;
|
||||||
|
} else if (strcmp(dot, T__xml) == 0) {
|
||||||
_contentType = T_text_xml;
|
_contentType = T_text_xml;
|
||||||
} else if (path.endsWith(T__pdf)) {
|
} else if (strcmp(dot, T__pdf) == 0) {
|
||||||
_contentType = T_application_pdf;
|
_contentType = T_application_pdf;
|
||||||
} else if (path.endsWith(T__zip)) {
|
} else if (strcmp(dot, T__zip) == 0) {
|
||||||
_contentType = T_application_zip;
|
_contentType = T_application_zip;
|
||||||
} else if (path.endsWith(T__gz)) {
|
} else if (strcmp(dot, T__gz) == 0) {
|
||||||
_contentType = T_application_x_gzip;
|
_contentType = T_application_x_gzip;
|
||||||
} else {
|
} else {
|
||||||
_contentType = T_text_plain;
|
_contentType = T_text_plain;
|
||||||
|
@ -646,40 +686,73 @@ void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructor for AsyncFileResponse that handles file serving with compression support
|
||||||
|
*
|
||||||
|
* This constructor creates an AsyncFileResponse object that can serve files from a filesystem,
|
||||||
|
* with automatic fallback to gzip-compressed versions if the original file is not found.
|
||||||
|
* It also handles ETag generation for caching and supports both inline and download modes.
|
||||||
|
*
|
||||||
|
* @param fs Reference to the filesystem object used to open files
|
||||||
|
* @param path Path to the file to be served (without compression extension)
|
||||||
|
* @param contentType MIME type of the file content (empty string for auto-detection)
|
||||||
|
* @param download If true, file will be served as download attachment; if false, as inline content
|
||||||
|
* @param callback Template processor callback for dynamic content processing
|
||||||
|
*/
|
||||||
AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
|
AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
|
||||||
: AsyncAbstractResponse(callback) {
|
: AsyncAbstractResponse(callback) {
|
||||||
_code = 200;
|
// Try to open the uncompressed version first
|
||||||
|
_content = fs.open(path, fs::FileOpenMode::read);
|
||||||
|
if (_content.available()) {
|
||||||
_path = path;
|
_path = path;
|
||||||
|
_contentLength = _content.size();
|
||||||
|
} else {
|
||||||
|
// Try to open the compressed version (.gz)
|
||||||
|
_path = path + asyncsrv::T__gz;
|
||||||
|
_content = fs.open(_path, fs::FileOpenMode::read);
|
||||||
|
_contentLength = _content.size();
|
||||||
|
|
||||||
if (!download && !fs.exists(_path) && fs.exists(_path + T__gz)) {
|
if (_content.seek(_contentLength - 8)) {
|
||||||
_path = _path + T__gz;
|
|
||||||
addHeader(T_Content_Encoding, T_gzip, false);
|
addHeader(T_Content_Encoding, T_gzip, false);
|
||||||
_callback = nullptr; // Unable to process zipped templates
|
_callback = nullptr; // Unable to process zipped templates
|
||||||
_sendContentLength = true;
|
_sendContentLength = true;
|
||||||
_chunked = false;
|
_chunked = false;
|
||||||
|
|
||||||
|
// Add ETag and cache headers
|
||||||
|
uint8_t crcInTrailer[4];
|
||||||
|
_content.read(crcInTrailer, sizeof(crcInTrailer));
|
||||||
|
char serverETag[9];
|
||||||
|
AsyncWebServerRequest::_getEtag(crcInTrailer, serverETag);
|
||||||
|
addHeader(T_ETag, serverETag, true);
|
||||||
|
addHeader(T_Cache_Control, T_no_cache, true);
|
||||||
|
|
||||||
|
_content.seek(0);
|
||||||
|
} else {
|
||||||
|
// File is corrupted or invalid
|
||||||
|
_code = 404;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_content = fs.open(_path, fs::FileOpenMode::read);
|
if (*contentType != '\0') {
|
||||||
_contentLength = _content.size();
|
|
||||||
|
|
||||||
if (strlen(contentType) == 0) {
|
|
||||||
_setContentTypeFromPath(path);
|
_setContentTypeFromPath(path);
|
||||||
} else {
|
} else {
|
||||||
_contentType = contentType;
|
_contentType = contentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (download) {
|
||||||
|
// Extract filename from path and set as download attachment
|
||||||
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) {
|
|
||||||
// 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 {
|
|
||||||
// set filename and force rendering
|
|
||||||
snprintf_P(buf, sizeof(buf), PSTR("inline"));
|
|
||||||
}
|
|
||||||
addHeader(T_Content_Disposition, buf, false);
|
addHeader(T_Content_Disposition, buf, false);
|
||||||
|
} else {
|
||||||
|
// Serve file inline (display in browser)
|
||||||
|
addHeader(T_Content_Disposition, PSTR("inline"), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_code = 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncFileResponse::AsyncFileResponse(File content, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
|
AsyncFileResponse::AsyncFileResponse(File content, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
|
||||||
|
@ -820,7 +893,9 @@ AsyncResponseStream::AsyncResponseStream(const char *contentType, size_t bufferS
|
||||||
_code = 200;
|
_code = 200;
|
||||||
_contentLength = 0;
|
_contentLength = 0;
|
||||||
_contentType = contentType;
|
_contentType = contentType;
|
||||||
if (!_content.reserve(bufferSize)) {
|
// internal buffer will be null on allocation failure
|
||||||
|
_content = std::unique_ptr<cbuf>(new cbuf(bufferSize));
|
||||||
|
if (bufferSize && _content->size() < bufferSize) {
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
log_e("Failed to allocate");
|
log_e("Failed to allocate");
|
||||||
#endif
|
#endif
|
||||||
|
@ -828,14 +903,26 @@ AsyncResponseStream::AsyncResponseStream(const char *contentType, size_t bufferS
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen) {
|
size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen) {
|
||||||
return _content.readBytes((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;
|
||||||
}
|
}
|
||||||
size_t written = _content.write(data, len);
|
if (len > _content->room()) {
|
||||||
|
size_t needed = len - _content->room();
|
||||||
|
_content->resizeAdd(needed);
|
||||||
|
// log a warning if allocation failed, but do not return: keep writing the bytes we can
|
||||||
|
// with _content->write: if len is more than the available size in the buffer, only
|
||||||
|
// the available size will be written
|
||||||
|
if (len > _content->room()) {
|
||||||
|
#ifdef ESP32
|
||||||
|
log_e("Failed to allocate");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size_t written = _content->write((const char *)data, len);
|
||||||
_contentLength += written;
|
_contentLength += written;
|
||||||
return written;
|
return written;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,18 @@
|
||||||
#include "ESPAsyncWebServer.h"
|
#include "ESPAsyncWebServer.h"
|
||||||
#include "WebHandlerImpl.h"
|
#include "WebHandlerImpl.h"
|
||||||
|
|
||||||
|
#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
|
||||||
|
#include <WiFi.h>
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#else
|
||||||
|
#error Platform not supported
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace asyncsrv;
|
using namespace asyncsrv;
|
||||||
|
|
||||||
bool ON_STA_FILTER(AsyncWebServerRequest *request) {
|
bool ON_STA_FILTER(AsyncWebServerRequest *request) {
|
||||||
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
|
||||||
return WiFi.localIP() == request->client()->localIP();
|
return WiFi.localIP() == request->client()->localIP();
|
||||||
#else
|
#else
|
||||||
return false;
|
return false;
|
||||||
|
@ -15,7 +23,7 @@ bool ON_STA_FILTER(AsyncWebServerRequest *request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ON_AP_FILTER(AsyncWebServerRequest *request) {
|
bool ON_AP_FILTER(AsyncWebServerRequest *request) {
|
||||||
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
#if SOC_WIFI_SUPPORTED || CONFIG_ESP_WIFI_REMOTE_ENABLED || LT_ARD_HAS_WIFI
|
||||||
return WiFi.localIP() != request->client()->localIP();
|
return WiFi.localIP() != request->client()->localIP();
|
||||||
#else
|
#else
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -300,7 +300,7 @@ class AsyncServer : public AsyncSocketBase
|
||||||
|
|
||||||
void setNoDelay(bool nodelay) { _noDelay = nodelay; }
|
void setNoDelay(bool nodelay) { _noDelay = nodelay; }
|
||||||
bool getNoDelay() { return _noDelay; }
|
bool getNoDelay() { return _noDelay; }
|
||||||
uint8_t status();
|
uint8_t status() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint16_t _port;
|
uint16_t _port;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue