Update ESPAsyncWebServer to v3.7.10

This commit is contained in:
Daniel Öster 2025-07-28 09:23:49 +03:00
parent 302282abeb
commit 537c01a8b7
21 changed files with 589 additions and 177 deletions

View file

@ -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

View file

@ -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"
}
],

View file

@ -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

View file

@ -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) {

View file

@ -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;

View file

@ -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);
}
}
}

View file

@ -79,7 +79,6 @@ protected:
String _uri;
WebRequestMethodComposite _method;
ArJsonRequestHandlerFunction _onRequest;
size_t _contentLength;
#if ARDUINOJSON_VERSION_MAJOR == 6
size_t maxJsonBufferSize;
#endif

View file

@ -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));
}

View file

@ -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';
}

View file

@ -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

View file

@ -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);

View file

@ -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_ */

View file

@ -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);

View file

@ -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('>');

View file

@ -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;

View file

@ -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();

View file

@ -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

View file

@ -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;
};

View file

@ -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;
}

View file

@ -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;

View file

@ -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;