mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-04 10:19:29 +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)
|
||||
|
||||
- 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)
|
||||
|
||||
|
@ -72,6 +72,19 @@ lib_deps =
|
|||
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
|
||||
|
||||
**AsyncTCPSock**
|
||||
|
@ -100,7 +113,7 @@ platform = https://github.com/maxgerhardt/platform-raspberrypi.git
|
|||
board = rpipicow
|
||||
board_build.core = earlephilhower
|
||||
lib_deps =
|
||||
ayushsharma82/RPAsyncTCP@^1.3.1
|
||||
ayushsharma82/RPAsyncTCP@^1.3.2
|
||||
ESP32Async/ESPAsyncWebServer
|
||||
lib_ignore =
|
||||
lwIP_ESPHost
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"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.",
|
||||
"keywords": "http,async,websocket,webserver",
|
||||
"homepage": "https://github.com/ESP32Async/ESPAsyncWebServer",
|
||||
|
@ -18,14 +18,18 @@
|
|||
"platforms": [
|
||||
"espressif32",
|
||||
"espressif8266",
|
||||
"raspberrypi"
|
||||
"raspberrypi",
|
||||
"libretiny"
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"owner": "ESP32Async",
|
||||
"name": "AsyncTCP",
|
||||
"version": "^3.3.6",
|
||||
"platforms": "espressif32"
|
||||
"version": "^3.4.5",
|
||||
"platforms": [
|
||||
"espressif32",
|
||||
"libretiny"
|
||||
]
|
||||
},
|
||||
{
|
||||
"owner": "ESP32Async",
|
||||
|
@ -40,7 +44,7 @@
|
|||
{
|
||||
"owner": "ayushsharma82",
|
||||
"name": "RPAsyncTCP",
|
||||
"version": "^1.3.1",
|
||||
"version": "^1.3.2",
|
||||
"platforms": "raspberrypi"
|
||||
}
|
||||
],
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
name=ESP Async WebServer
|
||||
includes=ESPAsyncWebServer.h
|
||||
version=3.7.2
|
||||
version=3.7.10
|
||||
author=ESP32Async
|
||||
maintainer=ESP32Async
|
||||
sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040
|
||||
|
|
|
@ -193,7 +193,7 @@ AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, A
|
|||
|
||||
AsyncEventSourceClient::~AsyncEventSourceClient() {
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lockmq);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lockmq);
|
||||
#endif
|
||||
_messageQueue.clear();
|
||||
close();
|
||||
|
@ -211,7 +211,7 @@ bool AsyncEventSourceClient::_queueMessage(const char *message, size_t len) {
|
|||
|
||||
#ifdef ESP32
|
||||
// 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
|
||||
|
||||
_messageQueue.emplace_back(message, len);
|
||||
|
@ -241,7 +241,7 @@ bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) {
|
|||
|
||||
#ifdef ESP32
|
||||
// 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
|
||||
|
||||
_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))) {
|
||||
#ifdef ESP32
|
||||
// Same here, acquiring the lock early
|
||||
std::lock_guard<std::mutex> lock(_lockmq);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lockmq);
|
||||
#endif
|
||||
|
||||
// adjust in-flight len
|
||||
|
@ -290,7 +290,7 @@ void AsyncEventSourceClient::_onPoll() {
|
|||
if (_messageQueue.size()) {
|
||||
#ifdef ESP32
|
||||
// Same here, acquiring the lock early
|
||||
std::lock_guard<std::mutex> lock(_lockmq);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lockmq);
|
||||
#endif
|
||||
_runQueue();
|
||||
}
|
||||
|
@ -367,7 +367,7 @@ void AsyncEventSource::_addClient(AsyncEventSourceClient *client) {
|
|||
return;
|
||||
}
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
_clients.emplace_back(client);
|
||||
if (_connectcb) {
|
||||
|
@ -382,7 +382,7 @@ void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient *client) {
|
|||
_disconnectcb(client);
|
||||
}
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
for (auto i = _clients.begin(); i != _clients.end(); ++i) {
|
||||
if (i->get() == client) {
|
||||
|
@ -398,10 +398,15 @@ void AsyncEventSource::close() {
|
|||
// iterator should remain valid even when AsyncEventSource::_handleDisconnect()
|
||||
// is called very early
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
for (const auto &c : _clients) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -412,7 +417,7 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
|
|||
size_t aql = 0;
|
||||
uint32_t nConnectedClients = 0;
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
if (!_clients.size()) {
|
||||
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) {
|
||||
AsyncEvent_SharedData_t shared_msg = std::make_shared<String>(generateEventMessage(message, event, id, reconnect));
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
size_t hits = 0;
|
||||
size_t miss = 0;
|
||||
|
@ -446,7 +451,7 @@ AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const c
|
|||
|
||||
size_t AsyncEventSource::count() const {
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_client_queue_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
|
||||
#endif
|
||||
size_t n_clients{0};
|
||||
for (const auto &i : _clients) {
|
||||
|
|
|
@ -6,8 +6,13 @@
|
|||
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifdef ESP32
|
||||
#if defined(ESP32) || defined(LIBRETINY)
|
||||
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
||||
#ifdef LIBRETINY
|
||||
#ifdef round
|
||||
#undef round
|
||||
#endif
|
||||
#endif
|
||||
#include <mutex>
|
||||
#ifndef SSE_MAX_QUEUED_MESSAGES
|
||||
#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
|
||||
std::list<AsyncEventSourceMessage> _messageQueue;
|
||||
#ifdef ESP32
|
||||
mutable std::mutex _lockmq;
|
||||
mutable std::recursive_mutex _lockmq;
|
||||
#endif
|
||||
bool _queueMessage(const char *message, size_t len);
|
||||
bool _queueMessage(AsyncEvent_SharedData_t &&msg);
|
||||
|
@ -230,7 +235,7 @@ private:
|
|||
#ifdef ESP32
|
||||
// Same as for individual messages, protect mutations of _clients list
|
||||
// since simultaneous access from different tasks is possible
|
||||
mutable std::mutex _client_queue_lock;
|
||||
mutable std::recursive_mutex _client_queue_lock;
|
||||
#endif
|
||||
ArEventHandlerFunction _connectcb = nullptr;
|
||||
ArEventHandlerFunction _disconnectcb = nullptr;
|
||||
|
|
|
@ -113,43 +113,64 @@ bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) cons
|
|||
|
||||
void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) {
|
||||
if (_onRequest) {
|
||||
// GET request:
|
||||
if (request->method() == HTTP_GET) {
|
||||
JsonVariant json;
|
||||
_onRequest(request, json);
|
||||
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
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonVariant json = jsonBuffer.parse((uint8_t *)(request->_tempObject));
|
||||
JsonVariant json = jsonBuffer.parse((const char *)request->_tempObject);
|
||||
if (json.success()) {
|
||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
|
||||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
|
||||
DeserializationError error = deserializeJson(jsonBuffer, (const char *)request->_tempObject);
|
||||
if (!error) {
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
#else
|
||||
JsonDocument jsonBuffer;
|
||||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
|
||||
DeserializationError error = deserializeJson(jsonBuffer, (const char *)request->_tempObject);
|
||||
if (!error) {
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
#endif
|
||||
|
||||
_onRequest(request, json);
|
||||
return;
|
||||
}
|
||||
}
|
||||
request->send(_contentLength > _maxContentLength ? 413 : 400);
|
||||
} 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) {
|
||||
if (_onRequest) {
|
||||
_contentLength = total;
|
||||
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
|
||||
request->_tempObject = malloc(total);
|
||||
// ignore callback if size is larger than maxContentLength
|
||||
if (total > _maxContentLength) {
|
||||
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) {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
|
@ -158,8 +179,11 @@ void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uin
|
|||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
WebRequestMethodComposite _method;
|
||||
ArJsonRequestHandlerFunction _onRequest;
|
||||
size_t _contentLength;
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
size_t maxJsonBufferSize;
|
||||
#endif
|
||||
|
|
|
@ -3,30 +3,32 @@
|
|||
|
||||
#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) {
|
||||
return;
|
||||
return AsyncWebHeader(); // nullptr
|
||||
}
|
||||
int index = data.indexOf(':');
|
||||
if (index < 0) {
|
||||
return;
|
||||
if (data[0] == '\0') {
|
||||
return AsyncWebHeader(); // empty string
|
||||
}
|
||||
_name = data.substring(0, index);
|
||||
_value = data.substring(index + 2);
|
||||
}
|
||||
|
||||
String AsyncWebHeader::toString() const {
|
||||
String str;
|
||||
if (str.reserve(_name.length() + _value.length() + 2)) {
|
||||
str.concat(_name);
|
||||
str.concat((char)0x3a);
|
||||
str.concat((char)0x20);
|
||||
str.concat(_value);
|
||||
str.concat(asyncsrv::T_rn);
|
||||
} else {
|
||||
#ifdef ESP32
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
}
|
||||
return str;
|
||||
if (strchr(data, '\n') || strchr(data, '\r')) {
|
||||
return AsyncWebHeader(); // Invalid header format
|
||||
}
|
||||
char *colon = strchr(data, ':');
|
||||
if (!colon) {
|
||||
return AsyncWebHeader(); // separator not found
|
||||
}
|
||||
if (colon == data) {
|
||||
return AsyncWebHeader(); // Header name cannot be empty
|
||||
}
|
||||
char *startOfValue = colon + 1; // Skip the colon
|
||||
// skip one optional whitespace after the colon
|
||||
if (*startOfValue == ' ') {
|
||||
startOfValue++;
|
||||
}
|
||||
String name;
|
||||
name.reserve(colon - data);
|
||||
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) */
|
||||
#define ASYNCWEBSERVER_VERSION_MINOR 7
|
||||
/** Patch version number (x.x.X) */
|
||||
#define ASYNCWEBSERVER_VERSION_PATCH 2
|
||||
#define ASYNCWEBSERVER_VERSION_PATCH 10
|
||||
|
||||
/**
|
||||
* Macro to convert version number into an integer
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include <rom/ets_sys.h>
|
||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(ESP8266)
|
||||
#include <Hash.h>
|
||||
#elif defined(LIBRETINY)
|
||||
#include <mbedtls/sha1.h>
|
||||
#endif
|
||||
|
||||
using namespace asyncsrv;
|
||||
|
@ -333,7 +335,7 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, Async
|
|||
AsyncWebSocketClient::~AsyncWebSocketClient() {
|
||||
{
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lock);
|
||||
#endif
|
||||
_messageQueue.clear();
|
||||
_controlQueue.clear();
|
||||
|
@ -351,7 +353,7 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
|
|||
_lastMessageTime = millis();
|
||||
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::unique_lock<std::recursive_mutex> lock(_lock);
|
||||
#endif
|
||||
|
||||
if (!_controlQueue.empty()) {
|
||||
|
@ -362,6 +364,14 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
|
|||
_controlQueue.pop_front();
|
||||
_status = WS_DISCONNECTED;
|
||||
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);
|
||||
}
|
||||
return;
|
||||
|
@ -385,7 +395,7 @@ void AsyncWebSocketClient::_onPoll() {
|
|||
}
|
||||
|
||||
#ifdef ESP32
|
||||
std::unique_lock<std::mutex> lock(_lock);
|
||||
std::unique_lock<std::recursive_mutex> lock(_lock);
|
||||
#endif
|
||||
if (_client && _client->canSend() && (!_controlQueue.empty() || !_messageQueue.empty())) {
|
||||
_runQueue();
|
||||
|
@ -415,21 +425,21 @@ void AsyncWebSocketClient::_runQueue() {
|
|||
|
||||
bool AsyncWebSocketClient::queueIsFull() const {
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lock);
|
||||
#endif
|
||||
return (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED);
|
||||
}
|
||||
|
||||
size_t AsyncWebSocketClient::queueLen() const {
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lock);
|
||||
#endif
|
||||
return _messageQueue.size();
|
||||
}
|
||||
|
||||
bool AsyncWebSocketClient::canSend() const {
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lock);
|
||||
#endif
|
||||
return _messageQueue.size() < WS_MAX_QUEUED_MESSAGES;
|
||||
}
|
||||
|
@ -440,7 +450,7 @@ bool AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t *data, si
|
|||
}
|
||||
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(_lock);
|
||||
#endif
|
||||
|
||||
_controlQueue.emplace_back(opcode, data, len, mask);
|
||||
|
@ -458,7 +468,7 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint
|
|||
}
|
||||
|
||||
#ifdef ESP32
|
||||
std::lock_guard<std::mutex> lock(_lock);
|
||||
std::unique_lock<std::recursive_mutex> lock(_lock);
|
||||
#endif
|
||||
|
||||
if (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) {
|
||||
|
@ -466,6 +476,14 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint
|
|||
_status = WS_DISCONNECTED;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -551,6 +569,7 @@ void AsyncWebSocketClient::_onTimeout(uint32_t time) {
|
|||
void AsyncWebSocketClient::_onDisconnect() {
|
||||
// Serial.println("onDis");
|
||||
_client = nullptr;
|
||||
_server->_handleDisconnect(this);
|
||||
}
|
||||
|
||||
void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
|
||||
|
@ -857,6 +876,16 @@ AsyncWebSocketClient *AsyncWebSocket::_newClient(AsyncWebServerRequest *request)
|
|||
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() {
|
||||
return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) {
|
||||
return c.queueIsFull();
|
||||
|
@ -1300,11 +1329,20 @@ AsyncWebSocketResponse::AsyncWebSocketResponse(const String &key, AsyncWebSocket
|
|||
}
|
||||
k.concat(key);
|
||||
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;
|
||||
sha1.begin();
|
||||
sha1.add((const uint8_t *)k.c_str(), k.length());
|
||||
sha1.calculate();
|
||||
sha1.getBytes(hash);
|
||||
#endif
|
||||
#endif
|
||||
base64_encodestate _state;
|
||||
base64_init_encodestate(&_state);
|
||||
|
|
|
@ -5,8 +5,14 @@
|
|||
#define ASYNCWEBSOCKET_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
#ifdef ESP32
|
||||
|
||||
#if defined(ESP32) || defined(LIBRETINY)
|
||||
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
||||
#ifdef LIBRETINY
|
||||
#ifdef round
|
||||
#undef round
|
||||
#endif
|
||||
#endif
|
||||
#include <mutex>
|
||||
#ifndef WS_MAX_QUEUED_MESSAGES
|
||||
#define WS_MAX_QUEUED_MESSAGES 32
|
||||
|
@ -152,7 +158,7 @@ private:
|
|||
uint32_t _clientId;
|
||||
AwsClientStatus _status;
|
||||
#ifdef ESP32
|
||||
mutable std::mutex _lock;
|
||||
mutable std::recursive_mutex _lock;
|
||||
#endif
|
||||
std::deque<AsyncWebSocketControl> _controlQueue;
|
||||
std::deque<AsyncWebSocketMessage> _messageQueue;
|
||||
|
@ -291,7 +297,7 @@ private:
|
|||
String _url;
|
||||
std::list<AsyncWebSocketClient> _clients;
|
||||
uint32_t _cNextId;
|
||||
AwsEventHandler _eventHandler{nullptr};
|
||||
AwsEventHandler _eventHandler;
|
||||
AwsHandshakeHandler _handshakeHandler;
|
||||
bool _enabled;
|
||||
#ifdef ESP32
|
||||
|
@ -305,8 +311,8 @@ public:
|
|||
PARTIALLY_ENQUEUED = 2,
|
||||
} SendStatus;
|
||||
|
||||
explicit AsyncWebSocket(const char *url) : _url(url), _cNextId(1), _enabled(true) {}
|
||||
AsyncWebSocket(const String &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, AwsEventHandler handler = nullptr) : _url(url), _cNextId(1), _eventHandler(handler), _enabled(true) {}
|
||||
~AsyncWebSocket(){};
|
||||
const char *url() const {
|
||||
return _url.c_str();
|
||||
|
@ -385,6 +391,7 @@ public:
|
|||
return _cNextId++;
|
||||
}
|
||||
AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request);
|
||||
void _handleDisconnect(AsyncWebSocketClient *client);
|
||||
void _handleEvent(AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);
|
||||
bool canHandle(AsyncWebServerRequest *request) const 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_ */
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
#ifndef _ESPAsyncWebServer_H_
|
||||
#define _ESPAsyncWebServer_H_
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <Arduino.h>
|
||||
#include <FS.h>
|
||||
#include <lwip/tcpbase.h>
|
||||
|
||||
#include "FS.h"
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
|
@ -14,16 +15,13 @@
|
|||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#ifdef ESP32
|
||||
#if defined(ESP32) || defined(LIBRETINY)
|
||||
#include "../../mathieucarbou-AsyncTCPSock/src/AsyncTCP.h"
|
||||
#include <WiFi.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncTCP.h>
|
||||
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
||||
#include <RPAsyncTCP.h>
|
||||
#include <HTTP_Method.h>
|
||||
#include <WiFi.h>
|
||||
#include <http_parser.h>
|
||||
#else
|
||||
#error Platform not supported
|
||||
|
@ -131,12 +129,20 @@ private:
|
|||
String _value;
|
||||
|
||||
public:
|
||||
AsyncWebHeader() {}
|
||||
AsyncWebHeader(const AsyncWebHeader &) = default;
|
||||
AsyncWebHeader(AsyncWebHeader &&) = default;
|
||||
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 &data);
|
||||
|
||||
#ifndef ESP8266
|
||||
[[deprecated("Use AsyncWebHeader::parse(data) instead")]]
|
||||
#endif
|
||||
AsyncWebHeader(const String &data)
|
||||
: AsyncWebHeader(parse(data)){};
|
||||
|
||||
AsyncWebHeader &operator=(const AsyncWebHeader &) = default;
|
||||
AsyncWebHeader &operator=(AsyncWebHeader &&other) = default;
|
||||
|
||||
const String &name() const {
|
||||
return _name;
|
||||
|
@ -144,7 +150,18 @@ public:
|
|||
const String &value() const {
|
||||
return _value;
|
||||
}
|
||||
|
||||
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;
|
||||
friend class AsyncWebServer;
|
||||
friend class AsyncCallbackWebHandler;
|
||||
friend class AsyncFileResponse;
|
||||
|
||||
private:
|
||||
AsyncClient *_client;
|
||||
|
@ -251,6 +269,8 @@ private:
|
|||
void _send();
|
||||
void _runMiddlewareChain();
|
||||
|
||||
static void _getEtag(uint8_t trailer[4], char *serverETag);
|
||||
|
||||
public:
|
||||
File _tempFile;
|
||||
void *_tempObject;
|
||||
|
@ -363,13 +383,7 @@ public:
|
|||
send(beginResponse(code, contentType, content, len, callback));
|
||||
}
|
||||
|
||||
void send(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) {
|
||||
if (fs.exists(path) || (!download && fs.exists(path + asyncsrv::T__gz))) {
|
||||
send(beginResponse(fs, path, contentType, download, callback));
|
||||
} else {
|
||||
send(404);
|
||||
}
|
||||
}
|
||||
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 String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr) {
|
||||
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 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 String &contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE) {
|
||||
|
@ -531,6 +547,9 @@ public:
|
|||
* @return const AsyncWebParameter*
|
||||
*/
|
||||
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 {
|
||||
return params();
|
||||
|
@ -546,7 +565,13 @@ public:
|
|||
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 &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(int i) const {
|
||||
return i < 0 ? emptyString : argName((size_t)i);
|
||||
};
|
||||
bool hasArg(const char *name) const; // check if argument exists
|
||||
bool hasArg(const String &name) const {
|
||||
return hasArg(name.c_str());
|
||||
|
@ -556,6 +581,9 @@ public:
|
|||
#endif
|
||||
|
||||
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
|
||||
const String &header(const char *name) const;
|
||||
|
@ -568,7 +596,13 @@ public:
|
|||
#endif
|
||||
|
||||
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(int i) const {
|
||||
return i < 0 ? emptyString : headerName((size_t)i);
|
||||
};
|
||||
|
||||
size_t headers() const; // get header count
|
||||
|
||||
|
@ -590,6 +624,9 @@ public:
|
|||
#endif
|
||||
|
||||
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 {
|
||||
return _headers;
|
||||
|
@ -1011,6 +1048,10 @@ public:
|
|||
setContentType(type.c_str());
|
||||
}
|
||||
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 String &name, const String &value, bool replaceExisting = true) {
|
||||
return addHeader(name.c_str(), value.c_str(), replaceExisting);
|
||||
|
@ -1069,6 +1110,15 @@ public:
|
|||
void begin();
|
||||
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
|
||||
void onSslFileRequest(AcSSlFileHandler cb, void *arg);
|
||||
void beginSecure(const char *cert, const char *private_key_file, const char *password);
|
||||
|
|
|
@ -172,7 +172,11 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNex
|
|||
return;
|
||||
}
|
||||
_out->print(F("* Connection from "));
|
||||
#ifndef LIBRETINY
|
||||
_out->print(request->client()->remoteIP().toString());
|
||||
#else
|
||||
_out->print(request->client()->remoteIP());
|
||||
#endif
|
||||
_out->print(':');
|
||||
_out->println(request->client()->remotePort());
|
||||
_out->print('>');
|
||||
|
|
|
@ -19,7 +19,6 @@ class AsyncStaticWebHandler : public AsyncWebHandler {
|
|||
private:
|
||||
bool _getFile(AsyncWebServerRequest *request) const;
|
||||
bool _searchFile(AsyncWebServerRequest *request, const String &path);
|
||||
uint8_t _countBits(const uint8_t value) const;
|
||||
|
||||
protected:
|
||||
FS _fs;
|
||||
|
|
|
@ -187,15 +187,6 @@ bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest *request, const St
|
|||
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) {
|
||||
// Get the filename from request->_tempObject and free it
|
||||
String filename((char *)request->_tempObject);
|
||||
|
@ -218,11 +209,14 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) {
|
|||
char buf[len];
|
||||
char *ret = lltoa(lw ^ request->_tempFile.size(), buf, len, 10);
|
||||
etag = ret ? String(ret) : String(request->_tempFile.size());
|
||||
#elif defined(LIBRETINY)
|
||||
long val = lw ^ request->_tempFile.size();
|
||||
etag = String(val);
|
||||
#else
|
||||
etag = lw ^ request->_tempFile.size(); // etag combines file size and lastmod timestamp
|
||||
#endif
|
||||
} else {
|
||||
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
|
||||
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(LIBRETINY)
|
||||
etag = String(request->_tempFile.size());
|
||||
#else
|
||||
etag = request->_tempFile.size();
|
||||
|
|
|
@ -22,10 +22,10 @@ enum {
|
|||
};
|
||||
|
||||
AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
|
||||
: _client(c), _server(s), _handler(NULL), _response(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY), _url(), _host(),
|
||||
_contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false), _isPlainPost(false),
|
||||
_expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), _itemSize(0), _itemName(),
|
||||
_itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) {
|
||||
: _client(c), _server(s), _handler(NULL), _response(NULL), _onDisconnectfn(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY),
|
||||
_url(), _host(), _contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false),
|
||||
_isPlainPost(false), _expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0),
|
||||
_itemSize(0), _itemName(), _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) {
|
||||
c->onError(
|
||||
[](void *r, AsyncClient *c, int8_t error) {
|
||||
(void)c;
|
||||
|
@ -341,10 +341,10 @@ bool AsyncWebServerRequest::_parseReqHead() {
|
|||
}
|
||||
|
||||
bool AsyncWebServerRequest::_parseReqHeader() {
|
||||
int index = _temp.indexOf(':');
|
||||
if (index) {
|
||||
String name(_temp.substring(0, index));
|
||||
String value(_temp.substring(index + 2));
|
||||
AsyncWebHeader header = AsyncWebHeader::parse(_temp);
|
||||
if (header) {
|
||||
const String &name = header.name();
|
||||
const String &value = header.value();
|
||||
if (name.equalsIgnoreCase(T_Host)) {
|
||||
_host = value;
|
||||
} else if (name.equalsIgnoreCase(T_Content_Type)) {
|
||||
|
@ -392,9 +392,9 @@ bool AsyncWebServerRequest::_parseReqHeader() {
|
|||
_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-()
|
||||
_temp = emptyString;
|
||||
#else
|
||||
|
@ -419,7 +419,7 @@ void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) {
|
|||
_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-()
|
||||
_temp = emptyString;
|
||||
#else
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#undef max
|
||||
#endif
|
||||
#include "literals.h"
|
||||
#include <StreamString.h>
|
||||
#include <cbuf.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
|
@ -157,7 +157,7 @@ public:
|
|||
|
||||
class AsyncResponseStream : public AsyncAbstractResponse, public Print {
|
||||
private:
|
||||
StreamString _content;
|
||||
std::unique_ptr<cbuf> _content;
|
||||
|
||||
public:
|
||||
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 write(const uint8_t *data, size_t len);
|
||||
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;
|
||||
};
|
||||
|
||||
|
|
|
@ -134,6 +134,30 @@ bool AsyncWebServerResponse::headerMustBePresentOnce(const String &name) {
|
|||
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) {
|
||||
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
|
||||
if (i->name().equalsIgnoreCase(name)) {
|
||||
|
@ -595,6 +619,16 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t *data, size
|
|||
* 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) {
|
||||
#if HAVE_EXTERN_GET_Content_Type_FUNCTION
|
||||
#ifndef ESP8266
|
||||
|
@ -604,41 +638,47 @@ void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
|
|||
#endif
|
||||
_contentType = getContentType(path);
|
||||
#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;
|
||||
} else if (path.endsWith(T__htm)) {
|
||||
_contentType = T_text_html;
|
||||
} else if (path.endsWith(T__css)) {
|
||||
} else if (strcmp(dot, T__css) == 0) {
|
||||
_contentType = T_text_css;
|
||||
} else if (path.endsWith(T__json)) {
|
||||
_contentType = T_application_json;
|
||||
} else if (path.endsWith(T__js)) {
|
||||
} else if (strcmp(dot, T__js) == 0) {
|
||||
_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;
|
||||
} else if (path.endsWith(T__gif)) {
|
||||
_contentType = T_image_gif;
|
||||
} else if (path.endsWith(T__jpg)) {
|
||||
_contentType = T_image_jpeg;
|
||||
} else if (path.endsWith(T__ico)) {
|
||||
} else if (strcmp(dot, T__ico) == 0) {
|
||||
_contentType = T_image_x_icon;
|
||||
} else if (path.endsWith(T__svg)) {
|
||||
} else if (strcmp(dot, T__svg) == 0) {
|
||||
_contentType = T_image_svg_xml;
|
||||
} else if (path.endsWith(T__eot)) {
|
||||
_contentType = T_font_eot;
|
||||
} else if (path.endsWith(T__woff)) {
|
||||
_contentType = T_font_woff;
|
||||
} else if (path.endsWith(T__woff2)) {
|
||||
} else if (strcmp(dot, T__jpg) == 0) {
|
||||
_contentType = T_image_jpeg;
|
||||
} else if (strcmp(dot, T__gif) == 0) {
|
||||
_contentType = T_image_gif;
|
||||
} else if (strcmp(dot, T__woff2) == 0) {
|
||||
_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;
|
||||
} 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;
|
||||
} else if (path.endsWith(T__pdf)) {
|
||||
} else if (strcmp(dot, T__pdf) == 0) {
|
||||
_contentType = T_application_pdf;
|
||||
} else if (path.endsWith(T__zip)) {
|
||||
} else if (strcmp(dot, T__zip) == 0) {
|
||||
_contentType = T_application_zip;
|
||||
} else if (path.endsWith(T__gz)) {
|
||||
} else if (strcmp(dot, T__gz) == 0) {
|
||||
_contentType = T_application_x_gzip;
|
||||
} else {
|
||||
_contentType = T_text_plain;
|
||||
|
@ -646,40 +686,73 @@ void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
|
|||
#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)
|
||||
: AsyncAbstractResponse(callback) {
|
||||
_code = 200;
|
||||
// Try to open the uncompressed version first
|
||||
_content = fs.open(path, fs::FileOpenMode::read);
|
||||
if (_content.available()) {
|
||||
_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)) {
|
||||
_path = _path + T__gz;
|
||||
if (_content.seek(_contentLength - 8)) {
|
||||
addHeader(T_Content_Encoding, T_gzip, false);
|
||||
_callback = nullptr; // Unable to process zipped templates
|
||||
_sendContentLength = true;
|
||||
_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);
|
||||
_contentLength = _content.size();
|
||||
|
||||
if (strlen(contentType) == 0) {
|
||||
if (*contentType != '\0') {
|
||||
_setContentTypeFromPath(path);
|
||||
} else {
|
||||
_contentType = contentType;
|
||||
}
|
||||
|
||||
if (download) {
|
||||
// Extract filename from path and set as download attachment
|
||||
int filenameStart = path.lastIndexOf('/') + 1;
|
||||
char buf[26 + path.length() - 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);
|
||||
} else {
|
||||
// set filename and force rendering
|
||||
snprintf_P(buf, sizeof(buf), PSTR("inline"));
|
||||
}
|
||||
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)
|
||||
|
@ -820,7 +893,9 @@ AsyncResponseStream::AsyncResponseStream(const char *contentType, size_t bufferS
|
|||
_code = 200;
|
||||
_contentLength = 0;
|
||||
_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
|
||||
log_e("Failed to allocate");
|
||||
#endif
|
||||
|
@ -828,14 +903,26 @@ AsyncResponseStream::AsyncResponseStream(const char *contentType, size_t bufferS
|
|||
}
|
||||
|
||||
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) {
|
||||
if (_started()) {
|
||||
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;
|
||||
return written;
|
||||
}
|
||||
|
|
|
@ -4,10 +4,18 @@
|
|||
#include "ESPAsyncWebServer.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;
|
||||
|
||||
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();
|
||||
#else
|
||||
return false;
|
||||
|
@ -15,7 +23,7 @@ bool ON_STA_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();
|
||||
#else
|
||||
return false;
|
||||
|
|
|
@ -300,7 +300,7 @@ class AsyncServer : public AsyncSocketBase
|
|||
|
||||
void setNoDelay(bool nodelay) { _noDelay = nodelay; }
|
||||
bool getNoDelay() { return _noDelay; }
|
||||
uint8_t status();
|
||||
uint8_t status() const;
|
||||
|
||||
protected:
|
||||
uint16_t _port;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue