Update ESPAsyncWebserver to v3.7.2

This commit is contained in:
Daniel Öster 2025-03-05 23:07:58 +02:00
parent 545fceb4cc
commit 27c29867fe
30 changed files with 3596 additions and 4660 deletions

View file

@ -6,12 +6,4 @@ set(COMPONENT_ADD_INCLUDEDIRS
"src" "src"
) )
set(COMPONENT_REQUIRES
"arduino-esp32"
"AsyncTCP"
)
register_component() register_component()
target_compile_definitions(${COMPONENT_TARGET} PUBLIC -DESP32)
target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti)

View file

@ -6,7 +6,7 @@
We as members, contributors, and leaders pledge to make participation in our We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status, identity and expression, level of experience, education, socioeconomic status,
nationality, personal appearance, race, religion, or sexual identity nationality, personal appearance, race, religion, or sexual identity
and orientation. and orientation.

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "ESPAsyncWebServer", "name": "ESPAsyncWebServer",
"version": "3.6.2", "version": "3.7.2",
"description": "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.", "description": "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.",
"keywords": "http,async,websocket,webserver", "keywords": "http,async,websocket,webserver",
"homepage": "https://github.com/ESP32Async/ESPAsyncWebServer", "homepage": "https://github.com/ESP32Async/ESPAsyncWebServer",
@ -24,7 +24,7 @@
{ {
"owner": "ESP32Async", "owner": "ESP32Async",
"name": "AsyncTCP", "name": "AsyncTCP",
"version": "^3.3.2", "version": "^3.3.6",
"platforms": "espressif32" "platforms": "espressif32"
}, },
{ {
@ -38,9 +38,9 @@
"platforms": "espressif8266" "platforms": "espressif8266"
}, },
{ {
"owner": "khoih-prog", "owner": "ayushsharma82",
"name": "AsyncTCP_RP2040W", "name": "RPAsyncTCP",
"version": "^1.2.0", "version": "^1.3.1",
"platforms": "raspberrypi" "platforms": "raspberrypi"
} }
], ],

View file

@ -1,6 +1,6 @@
name=ESP Async WebServer name=ESP Async WebServer
includes=ESPAsyncWebServer.h includes=ESPAsyncWebServer.h
version=3.6.2 version=3.7.2
author=ESP32Async author=ESP32Async
maintainer=ESP32Async maintainer=ESP32Async
sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040 sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040

View file

@ -1,25 +1,9 @@
/* // SPDX-License-Identifier: LGPL-3.0-or-later
Asynchronous WebServer library for Espressif MCUs // Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "Arduino.h" #include "Arduino.h"
#if defined(ESP32) #if defined(ESP32)
#include <rom/ets_sys.h> #include <rom/ets_sys.h>
#endif #endif
#include "AsyncEventSource.h" #include "AsyncEventSource.h"
@ -27,18 +11,25 @@
using namespace asyncsrv; using namespace asyncsrv;
static String generateEventMessage(const char* message, const char* event, uint32_t id, uint32_t reconnect) { static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
String str; String str;
size_t len{0}; size_t len{0};
if (message) if (message) {
len += strlen(message); len += strlen(message);
}
if (event) if (event) {
len += strlen(event); len += strlen(event);
}
len += 42; // give it some overhead len += 42; // give it some overhead
str.reserve(len); if (!str.reserve(len)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
return emptyString;
}
if (reconnect) { if (reconnect) {
str += T_retry_; str += T_retry_;
@ -58,15 +49,16 @@ static String generateEventMessage(const char* message, const char* event, uint3
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n' str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
} }
if (!message) if (!message) {
return str; return str;
}
size_t messageLen = strlen(message); size_t messageLen = strlen(message);
char* lineStart = (char*)message; char *lineStart = (char *)message;
char* lineEnd; char *lineEnd;
do { do {
char* nextN = strchr(lineStart, '\n'); char *nextN = strchr(lineStart, '\n');
char* nextR = strchr(lineStart, '\r'); char *nextR = strchr(lineStart, '\r');
if (nextN == NULL && nextR == NULL) { if (nextN == NULL && nextR == NULL) {
// a message is a single-line string // a message is a single-line string
str += T_data_; str += T_data_;
@ -76,10 +68,10 @@ static String generateEventMessage(const char* message, const char* event, uint3
} }
// a message is a multi-line string // a message is a multi-line string
char* nextLine = NULL; char *nextLine = NULL;
if (nextN != NULL && nextR != NULL) { // windows line-ending \r\n if (nextN != NULL && nextR != NULL) { // windows line-ending \r\n
if (nextR + 1 == nextN) { if (nextR + 1 == nextN) {
// normal \r\n sequense // normal \r\n sequence
lineEnd = nextR; lineEnd = nextR;
nextLine = nextN + 1; nextLine = nextN + 1;
} else { } else {
@ -100,7 +92,7 @@ static String generateEventMessage(const char* message, const char* event, uint3
str += ASYNC_SSE_NEW_LINE_CHAR; // \n str += ASYNC_SSE_NEW_LINE_CHAR; // \n
lineStart = nextLine; lineStart = nextLine;
} while (lineStart < ((char*)message + messageLen)); } while (lineStart < ((char *)message + messageLen));
// append another \n to terminate message // append another \n to terminate message
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n' str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
@ -123,9 +115,10 @@ size_t AsyncEventSourceMessage::ack(size_t len, __attribute__((unused)) uint32_t
return 0; return 0;
} }
size_t AsyncEventSourceMessage::write(AsyncClient* client) { size_t AsyncEventSourceMessage::write(AsyncClient *client) {
if (!client) if (!client) {
return 0; return 0;
}
if (_sent >= _data->length() || !client->canSend()) { if (_sent >= _data->length() || !client->canSend()) {
return 0; return 0;
@ -148,26 +141,49 @@ size_t AsyncEventSourceMessage::write(AsyncClient* client) {
return written; return written;
} }
size_t AsyncEventSourceMessage::send(AsyncClient* client) { size_t AsyncEventSourceMessage::send(AsyncClient *client) {
size_t sent = write(client); size_t sent = write(client);
return sent && client->send() ? sent : 0; return sent && client->send() ? sent : 0;
} }
// Client // Client
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server) AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) : _client(request->client()), _server(server) {
: _client(request->client()), _server(server) {
if (request->hasHeader(T_Last_Event_ID)) if (request->hasHeader(T_Last_Event_ID)) {
_lastId = atoi(request->getHeader(T_Last_Event_ID)->value().c_str()); _lastId = atoi(request->getHeader(T_Last_Event_ID)->value().c_str());
}
_client->setRxTimeout(0); _client->setRxTimeout(0);
_client->onError(NULL, NULL); _client->onError(NULL, NULL);
_client->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; static_cast<AsyncEventSourceClient*>(r)->_onAck(len, time); }, this); _client->onAck(
_client->onPoll([](void* r, AsyncClient* c) { (void)c; static_cast<AsyncEventSourceClient*>(r)->_onPoll(); }, this); [](void *r, AsyncClient *c, size_t len, uint32_t time) {
(void)c;
static_cast<AsyncEventSourceClient *>(r)->_onAck(len, time);
},
this
);
_client->onPoll(
[](void *r, AsyncClient *c) {
(void)c;
static_cast<AsyncEventSourceClient *>(r)->_onPoll();
},
this
);
_client->onData(NULL, NULL); _client->onData(NULL, NULL);
_client->onTimeout([this](void* r, AsyncClient* c __attribute__((unused)), uint32_t time) { static_cast<AsyncEventSourceClient*>(r)->_onTimeout(time); }, this); _client->onTimeout(
_client->onDisconnect([this](void* r, AsyncClient* c) { static_cast<AsyncEventSourceClient*>(r)->_onDisconnect(); delete c; }, this); [this](void *r, AsyncClient *c __attribute__((unused)), uint32_t time) {
static_cast<AsyncEventSourceClient *>(r)->_onTimeout(time);
},
this
);
_client->onDisconnect(
[this](void *r, AsyncClient *c) {
static_cast<AsyncEventSourceClient *>(r)->_onDisconnect();
delete c;
},
this
);
_server->_addClient(this); _server->_addClient(this);
delete request; delete request;
@ -183,7 +199,7 @@ AsyncEventSourceClient::~AsyncEventSourceClient() {
close(); close();
} }
bool AsyncEventSourceClient::_queueMessage(const char* message, size_t len) { bool AsyncEventSourceClient::_queueMessage(const char *message, size_t len) {
if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) { if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
#ifdef ESP8266 #ifdef ESP8266
ets_printf(String(F("ERROR: Too many messages queued\n")).c_str()); ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
@ -213,7 +229,7 @@ bool AsyncEventSourceClient::_queueMessage(const char* message, size_t len) {
return true; return true;
} }
bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t&& msg) { bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) {
if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) { if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
#ifdef ESP8266 #ifdef ESP8266
ets_printf(String(F("ERROR: Too many messages queued\n")).c_str()); ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
@ -249,10 +265,11 @@ void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t
#endif #endif
// adjust in-flight len // adjust in-flight len
if (len < _inflight) if (len < _inflight) {
_inflight -= len; _inflight -= len;
else } else {
_inflight = 0; _inflight = 0;
}
// acknowledge as much messages's data as we got confirmed len from a AsyncTCP // acknowledge as much messages's data as we got confirmed len from a AsyncTCP
while (len && _messageQueue.size()) { while (len && _messageQueue.size()) {
@ -264,8 +281,9 @@ void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t
} }
// try to send another batch of data // try to send another batch of data
if (_messageQueue.size()) if (_messageQueue.size()) {
_runQueue(); _runQueue();
}
} }
void AsyncEventSourceClient::_onPoll() { void AsyncEventSourceClient::_onPoll() {
@ -279,31 +297,36 @@ void AsyncEventSourceClient::_onPoll() {
} }
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))) { void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))) {
if (_client) if (_client) {
_client->close(true); _client->close(true);
}
} }
void AsyncEventSourceClient::_onDisconnect() { void AsyncEventSourceClient::_onDisconnect() {
if (!_client) if (!_client) {
return; return;
}
_client = nullptr; _client = nullptr;
_server->_handleDisconnect(this); _server->_handleDisconnect(this);
} }
void AsyncEventSourceClient::close() { void AsyncEventSourceClient::close() {
if (_client) if (_client) {
_client->close(); _client->close();
}
} }
bool AsyncEventSourceClient::send(const char* message, const char* event, uint32_t id, uint32_t reconnect) { bool AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
if (!connected()) if (!connected()) {
return false; return false;
}
return _queueMessage(std::make_shared<String>(generateEventMessage(message, event, id, reconnect))); return _queueMessage(std::make_shared<String>(generateEventMessage(message, event, id, reconnect)));
} }
void AsyncEventSourceClient::_runQueue() { void AsyncEventSourceClient::_runQueue() {
if (!_client) if (!_client) {
return; return;
}
// there is no need to lock the mutex here, 'cause all the calls to this method must be already lock'ed // there is no need to lock the mutex here, 'cause all the calls to this method must be already lock'ed
size_t total_bytes_written = 0; size_t total_bytes_written = 0;
@ -320,45 +343,52 @@ void AsyncEventSourceClient::_runQueue() {
} }
// flush socket // flush socket
if (total_bytes_written) if (total_bytes_written) {
_client->send(); _client->send();
}
} }
void AsyncEventSourceClient::set_max_inflight_bytes(size_t value) { void AsyncEventSourceClient::set_max_inflight_bytes(size_t value) {
if (value >= SSE_MIN_INFLIGH && value <= SSE_MAX_INFLIGH) if (value >= SSE_MIN_INFLIGH && value <= SSE_MAX_INFLIGH) {
_max_inflight = value; _max_inflight = value;
}
} }
/* AsyncEventSource */ /* AsyncEventSource */
void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) { void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) {
AsyncAuthorizationMiddleware* m = new AsyncAuthorizationMiddleware(401, cb); AsyncAuthorizationMiddleware *m = new AsyncAuthorizationMiddleware(401, cb);
m->_freeOnRemoval = true; m->_freeOnRemoval = true;
addMiddleware(m); addMiddleware(m);
} }
void AsyncEventSource::_addClient(AsyncEventSourceClient* client) { void AsyncEventSource::_addClient(AsyncEventSourceClient *client) {
if (!client) if (!client) {
return; return;
}
#ifdef ESP32 #ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock); std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif #endif
_clients.emplace_back(client); _clients.emplace_back(client);
if (_connectcb) if (_connectcb) {
_connectcb(client); _connectcb(client);
}
_adjust_inflight_window(); _adjust_inflight_window();
} }
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient* client) { void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient *client) {
if (_disconnectcb) if (_disconnectcb) {
_disconnectcb(client); _disconnectcb(client);
}
#ifdef ESP32 #ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock); std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif #endif
for (auto i = _clients.begin(); i != _clients.end(); ++i) { for (auto i = _clients.begin(); i != _clients.end(); ++i) {
if (i->get() == client) if (i->get() == client) {
_clients.erase(i); _clients.erase(i);
break;
}
} }
_adjust_inflight_window(); _adjust_inflight_window();
} }
@ -370,10 +400,11 @@ void AsyncEventSource::close() {
#ifdef ESP32 #ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock); std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif #endif
for (const auto& c : _clients) { for (const auto &c : _clients) {
if (c->connected()) if (c->connected()) {
c->close(); c->close();
} }
}
} }
// pmb fix // pmb fix
@ -383,10 +414,11 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
#ifdef ESP32 #ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock); std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif #endif
if (!_clients.size()) if (!_clients.size()) {
return 0; return 0;
}
for (const auto& c : _clients) { for (const auto &c : _clients) {
if (c->connected()) { if (c->connected()) {
aql += c->packetsWaiting(); aql += c->packetsWaiting();
++nConnectedClients; ++nConnectedClients;
@ -395,20 +427,20 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up
} }
AsyncEventSource::SendStatus AsyncEventSource::send( AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
const char* message, const char* event, uint32_t id, uint32_t reconnect) {
AsyncEvent_SharedData_t shared_msg = std::make_shared<String>(generateEventMessage(message, event, id, reconnect)); AsyncEvent_SharedData_t shared_msg = std::make_shared<String>(generateEventMessage(message, event, id, reconnect));
#ifdef ESP32 #ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock); std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif #endif
size_t hits = 0; size_t hits = 0;
size_t miss = 0; size_t miss = 0;
for (const auto& c : _clients) { for (const auto &c : _clients) {
if (c->write(shared_msg)) if (c->write(shared_msg)) {
++hits; ++hits;
else } else {
++miss; ++miss;
} }
}
return hits == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED); return hits == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
} }
@ -417,33 +449,36 @@ size_t AsyncEventSource::count() const {
std::lock_guard<std::mutex> lock(_client_queue_lock); std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif #endif
size_t n_clients{0}; size_t n_clients{0};
for (const auto& i : _clients) for (const auto &i : _clients) {
if (i->connected()) if (i->connected()) {
++n_clients; ++n_clients;
}
}
return n_clients; return n_clients;
} }
bool AsyncEventSource::canHandle(AsyncWebServerRequest* request) const { bool AsyncEventSource::canHandle(AsyncWebServerRequest *request) const {
return request->isSSE() && request->url().equals(_url); return request->isSSE() && request->url().equals(_url);
} }
void AsyncEventSource::handleRequest(AsyncWebServerRequest* request) { void AsyncEventSource::handleRequest(AsyncWebServerRequest *request) {
request->send(new AsyncEventSourceResponse(this)); request->send(new AsyncEventSourceResponse(this));
} }
void AsyncEventSource::_adjust_inflight_window() { void AsyncEventSource::_adjust_inflight_window() {
if (_clients.size()) { if (_clients.size()) {
size_t inflight = SSE_MAX_INFLIGH / _clients.size(); size_t inflight = SSE_MAX_INFLIGH / _clients.size();
for (const auto& c : _clients) for (const auto &c : _clients) {
c->set_max_inflight_bytes(inflight); c->set_max_inflight_bytes(inflight);
}
// Serial.printf("adjusted inflight to: %u\n", inflight); // Serial.printf("adjusted inflight to: %u\n", inflight);
} }
} }
/* Response */ /* Response */
AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource* server) { AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server) {
_server = server; _server = server;
_code = 200; _code = 200;
_contentType = T_text_event_stream; _contentType = T_text_event_stream;
@ -452,14 +487,14 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource* server) {
addHeader(T_Connection, T_keep_alive); addHeader(T_Connection, T_keep_alive);
} }
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest* request) { void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request) {
String out; String out;
_assembleHead(out, request->version()); _assembleHead(out, request->version());
request->client()->write(out.c_str(), _headLength); request->client()->write(out.c_str(), _headLength);
_state = RESPONSE_WAIT_ACK; _state = RESPONSE_WAIT_ACK;
} }
size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time __attribute__((unused))) { size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))) {
if (len) { if (len) {
new AsyncEventSourceClient(request, _server); new AsyncEventSourceClient(request, _server);
} }

View file

@ -1,64 +1,48 @@
/* // SPDX-License-Identifier: LGPL-3.0-or-later
Asynchronous WebServer library for Espressif MCUs // Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCEVENTSOURCE_H_ #ifndef ASYNCEVENTSOURCE_H_
#define ASYNCEVENTSOURCE_H_ #define ASYNCEVENTSOURCE_H_
#include <Arduino.h> #include <Arduino.h>
#ifdef ESP32 #ifdef ESP32
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h" #include <AsyncTCP.h>
#include <mutex> #include <mutex>
#ifndef SSE_MAX_QUEUED_MESSAGES #ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 32 #define SSE_MAX_QUEUED_MESSAGES 32
#endif #endif
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets #define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
#define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q #define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q
#elif defined(ESP8266) #elif defined(ESP8266)
#include <ESPAsyncTCP.h> #include <ESPAsyncTCP.h>
#ifndef SSE_MAX_QUEUED_MESSAGES #ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 8 #define SSE_MAX_QUEUED_MESSAGES 8
#endif #endif
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets #define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
#define SSE_MAX_INFLIGH 8 * 1024 // but no more than 8k, no need to blow it, since same data is kept in local Q #define SSE_MAX_INFLIGH 8 * 1024 // but no more than 8k, no need to blow it, since same data is kept in local Q
#elif defined(TARGET_RP2040) #elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <AsyncTCP_RP2040W.h> #include <RPAsyncTCP.h>
#ifndef SSE_MAX_QUEUED_MESSAGES #ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 32 #define SSE_MAX_QUEUED_MESSAGES 32
#endif #endif
#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets #define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
#define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q #define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q
#endif #endif
#include "ESPAsyncWebServer.h" #include <ESPAsyncWebServer.h>
#ifdef ESP8266 #ifdef ESP8266
#include <Hash.h> #include <Hash.h>
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
#include <../src/Hash.h> #include <../src/Hash.h>
#endif #endif
#endif #endif
class AsyncEventSource; class AsyncEventSource;
class AsyncEventSourceResponse; class AsyncEventSourceResponse;
class AsyncEventSourceClient; class AsyncEventSourceClient;
using ArEventHandlerFunction = std::function<void(AsyncEventSourceClient* client)>; using ArEventHandlerFunction = std::function<void(AsyncEventSourceClient *client)>;
using ArAuthorizeConnectHandler = ArAuthorizeFunction; using ArAuthorizeConnectHandler = ArAuthorizeFunction;
// shared message object container // shared message object container
using AsyncEvent_SharedData_t = std::shared_ptr<String>; using AsyncEvent_SharedData_t = std::shared_ptr<String>;
@ -69,25 +53,33 @@ using AsyncEvent_SharedData_t = std::shared_ptr<String>;
*/ */
class AsyncEventSourceMessage { class AsyncEventSourceMessage {
private: private:
const AsyncEvent_SharedData_t _data; const AsyncEvent_SharedData_t _data;
size_t _sent{0}; // num of bytes already sent size_t _sent{0}; // num of bytes already sent
size_t _acked{0}; // num of bytes acked size_t _acked{0}; // num of bytes acked
public: public:
AsyncEventSourceMessage(AsyncEvent_SharedData_t data) : _data(data) {}; AsyncEventSourceMessage(AsyncEvent_SharedData_t data) : _data(data){};
#ifdef ESP32 #if defined(ESP32)
AsyncEventSourceMessage(const char* data, size_t len) : _data(std::make_shared<String>(data, len)) {}; AsyncEventSourceMessage(const char *data, size_t len) : _data(std::make_shared<String>(data, len)){};
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
AsyncEventSourceMessage(const char *data, size_t len) : _data(std::make_shared<String>()) {
if (data && len > 0) {
_data->concat(data, len);
}
};
#else #else
// esp8266's String does not have constructor with data/length arguments. Use a concat method here // esp8266's String does not have constructor with data/length arguments. Use a concat method here
AsyncEventSourceMessage(const char* data, size_t len) { _data->concat(data, len); }; AsyncEventSourceMessage(const char *data, size_t len) {
_data->concat(data, len);
};
#endif #endif
/** /**
* @brief acknowledge sending len bytes of data * @brief acknowledge sending len bytes of data
* @note if num of bytes to ack is larger then the unacknowledged message length the number of carried over bytes are returned * @note if num of bytes to ack is larger then the unacknowledged message length the number of carried over bytes are returned
* *
* @param len bytes to acknowlegde * @param len bytes to acknowledge
* @param time * @param time
* @return size_t number of extra bytes carried over * @return size_t number of extra bytes carried over
*/ */
@ -100,7 +92,7 @@ class AsyncEventSourceMessage {
* @param client * @param client
* @return size_t number of bytes written * @return size_t number of bytes written
*/ */
size_t write(AsyncClient* client); size_t write(AsyncClient *client);
/** /**
* @brief writes message data to client's buffer and calls client's send method * @brief writes message data to client's buffer and calls client's send method
@ -108,16 +100,20 @@ class AsyncEventSourceMessage {
* @param client * @param client
* @return size_t returns num of bytes the clien was able to send() * @return size_t returns num of bytes the clien was able to send()
*/ */
size_t send(AsyncClient* client); size_t send(AsyncClient *client);
// returns true if full message's length were acked // returns true if full message's length were acked
bool finished() { return _acked == _data->length(); } bool finished() {
return _acked == _data->length();
}
/** /**
* @brief returns true if all data has been sent already * @brief returns true if all data has been sent already
* *
*/ */
bool sent() { return _sent == _data->length(); } bool sent() {
return _sent == _data->length();
}
}; };
/** /**
@ -125,9 +121,9 @@ class AsyncEventSourceMessage {
* *
*/ */
class AsyncEventSourceClient { class AsyncEventSourceClient {
private: private:
AsyncClient* _client; AsyncClient *_client;
AsyncEventSource* _server; AsyncEventSource *_server;
uint32_t _lastId{0}; uint32_t _lastId{0};
size_t _inflight{0}; // num of unacknowledged bytes that has been written to socket buffer size_t _inflight{0}; // num of unacknowledged bytes that has been written to socket buffer
size_t _max_inflight{SSE_MAX_INFLIGH}; // max num of unacknowledged bytes that could be written to socket buffer size_t _max_inflight{SSE_MAX_INFLIGH}; // max num of unacknowledged bytes that could be written to socket buffer
@ -135,12 +131,12 @@ class AsyncEventSourceClient {
#ifdef ESP32 #ifdef ESP32
mutable std::mutex _lockmq; mutable std::mutex _lockmq;
#endif #endif
bool _queueMessage(const char* message, size_t len); bool _queueMessage(const char *message, size_t len);
bool _queueMessage(AsyncEvent_SharedData_t&& msg); bool _queueMessage(AsyncEvent_SharedData_t &&msg);
void _runQueue(); void _runQueue();
public: public:
AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server); AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
~AsyncEventSourceClient(); ~AsyncEventSourceClient();
/** /**
@ -154,9 +150,13 @@ class AsyncEventSourceClient {
* @return true if message was placed in a queue * @return true if message was placed in a queue
* @return false if queue is full * @return false if queue is full
*/ */
bool send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0); bool send(const char *message, const char *event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
bool send(const String& message, const String& event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event.c_str(), id, reconnect); } bool send(const String &message, const String &event, uint32_t id = 0, uint32_t reconnect = 0) {
bool send(const String& message, const char* event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event, id, reconnect); } return send(message.c_str(), event.c_str(), id, reconnect);
}
bool send(const String &message, const char *event, uint32_t id = 0, uint32_t reconnect = 0) {
return send(message.c_str(), event, id, reconnect);
}
/** /**
* @brief place supplied preformatted SSE message to the message queue * @brief place supplied preformatted SSE message to the message queue
@ -166,20 +166,32 @@ class AsyncEventSourceClient {
* @return true on success * @return true on success
* @return false on queue overflow or no client connected * @return false on queue overflow or no client connected
*/ */
bool write(AsyncEvent_SharedData_t message) { return connected() && _queueMessage(std::move(message)); }; bool write(AsyncEvent_SharedData_t message) {
return connected() && _queueMessage(std::move(message));
};
[[deprecated("Use _write(AsyncEvent_SharedData_t message) instead to share same data with multiple SSE clients")]] [[deprecated("Use _write(AsyncEvent_SharedData_t message) instead to share same data with multiple SSE clients")]]
bool write(const char* message, size_t len) { return connected() && _queueMessage(message, len); }; bool write(const char *message, size_t len) {
return connected() && _queueMessage(message, len);
};
// close client's connection // close client's connection
void close(); void close();
// getters // getters
AsyncClient* client() { return _client; } AsyncClient *client() {
bool connected() const { return _client && _client->connected(); } return _client;
uint32_t lastId() const { return _lastId; } }
size_t packetsWaiting() const { return _messageQueue.size(); }; bool connected() const {
return _client && _client->connected();
}
uint32_t lastId() const {
return _lastId;
}
size_t packetsWaiting() const {
return _messageQueue.size();
};
/** /**
* @brief Sets max amount of bytes that could be written to client's socket while awaiting delivery acknowledge * @brief Sets max amount of bytes that could be written to client's socket while awaiting delivery acknowledge
@ -195,7 +207,9 @@ class AsyncEventSourceClient {
* *
* @return size_t * @return size_t
*/ */
size_t get_max_inflight_bytes() const { return _max_inflight; } size_t get_max_inflight_bytes() const {
return _max_inflight;
}
// system callbacks (do not call if from user code!) // system callbacks (do not call if from user code!)
void _onAck(size_t len, uint32_t time); void _onAck(size_t len, uint32_t time);
@ -210,7 +224,7 @@ class AsyncEventSourceClient {
* *
*/ */
class AsyncEventSource : public AsyncWebHandler { class AsyncEventSource : public AsyncWebHandler {
private: private:
String _url; String _url;
std::list<std::unique_ptr<AsyncEventSourceClient>> _clients; std::list<std::unique_ptr<AsyncEventSourceClient>> _clients;
#ifdef ESP32 #ifdef ESP32
@ -224,18 +238,22 @@ class AsyncEventSource : public AsyncWebHandler {
// this method manipulates in-fligh data size for connected client depending on number of active connections // this method manipulates in-fligh data size for connected client depending on number of active connections
void _adjust_inflight_window(); void _adjust_inflight_window();
public: public:
typedef enum { typedef enum {
DISCARDED = 0, DISCARDED = 0,
ENQUEUED = 1, ENQUEUED = 1,
PARTIALLY_ENQUEUED = 2, PARTIALLY_ENQUEUED = 2,
} SendStatus; } SendStatus;
AsyncEventSource(const char* url) : _url(url) {}; AsyncEventSource(const char *url) : _url(url){};
AsyncEventSource(const String& url) : _url(url) {}; AsyncEventSource(const String &url) : _url(url){};
~AsyncEventSource() { close(); }; ~AsyncEventSource() {
close();
};
const char* url() const { return _url.c_str(); } const char *url() const {
return _url.c_str();
}
// close all connected clients // close all connected clients
void close(); void close();
@ -245,7 +263,9 @@ class AsyncEventSource : public AsyncWebHandler {
* *
* @param cb * @param cb
*/ */
void onConnect(ArEventHandlerFunction cb) { _connectcb = cb; } void onConnect(ArEventHandlerFunction cb) {
_connectcb = cb;
}
/** /**
* @brief Send an SSE message to client * @brief Send an SSE message to client
@ -257,12 +277,18 @@ class AsyncEventSource : public AsyncWebHandler {
* @param reconnect client's reconnect timeout * @param reconnect client's reconnect timeout
* @return SendStatus if message was placed in any/all/part of the client's queues * @return SendStatus if message was placed in any/all/part of the client's queues
*/ */
SendStatus send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0); SendStatus send(const char *message, const char *event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
SendStatus send(const String& message, const String& event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event.c_str(), id, reconnect); } SendStatus send(const String &message, const String &event, uint32_t id = 0, uint32_t reconnect = 0) {
SendStatus send(const String& message, const char* event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event, id, reconnect); } return send(message.c_str(), event.c_str(), id, reconnect);
}
SendStatus send(const String &message, const char *event, uint32_t id = 0, uint32_t reconnect = 0) {
return send(message.c_str(), event, id, reconnect);
}
// The client pointer sent to the callback is only for reference purposes. DO NOT CALL ANY METHOD ON IT ! // The client pointer sent to the callback is only for reference purposes. DO NOT CALL ANY METHOD ON IT !
void onDisconnect(ArEventHandlerFunction cb) { _disconnectcb = cb; } void onDisconnect(ArEventHandlerFunction cb) {
_disconnectcb = cb;
}
void authorizeConnect(ArAuthorizeConnectHandler cb); void authorizeConnect(ArAuthorizeConnectHandler cb);
// returns number of connected clients // returns number of connected clients
@ -272,21 +298,23 @@ class AsyncEventSource : public AsyncWebHandler {
size_t avgPacketsWaiting() const; size_t avgPacketsWaiting() const;
// system callbacks (do not call from user code!) // system callbacks (do not call from user code!)
void _addClient(AsyncEventSourceClient* client); void _addClient(AsyncEventSourceClient *client);
void _handleDisconnect(AsyncEventSourceClient* client); void _handleDisconnect(AsyncEventSourceClient *client);
bool canHandle(AsyncWebServerRequest* request) const override final; bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest* request) override final; void handleRequest(AsyncWebServerRequest *request) override final;
}; };
class AsyncEventSourceResponse : public AsyncWebServerResponse { class AsyncEventSourceResponse : public AsyncWebServerResponse {
private: private:
AsyncEventSource* _server; AsyncEventSource *_server;
public: public:
AsyncEventSourceResponse(AsyncEventSource* server); AsyncEventSourceResponse(AsyncEventSource *server);
void _respond(AsyncWebServerRequest* request); void _respond(AsyncWebServerRequest *request);
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time); size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
bool _sourceValid() const { return true; } bool _sourceValid() const {
return true;
}
}; };
#endif /* ASYNCEVENTSOURCE_H_ */ #endif /* ASYNCEVENTSOURCE_H_ */

View file

@ -1,108 +1,117 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include "AsyncJson.h" #include "AsyncJson.h"
#if ASYNC_JSON_SUPPORT == 1 #if ASYNC_JSON_SUPPORT == 1
#if ARDUINOJSON_VERSION_MAJOR == 5 #if ARDUINOJSON_VERSION_MAJOR == 5
AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} { AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
_code = 200; _code = 200;
_contentType = asyncsrv::T_application_json; _contentType = asyncsrv::T_application_json;
if (isArray) if (isArray) {
_root = _jsonBuffer.createArray(); _root = _jsonBuffer.createArray();
else } else {
_root = _jsonBuffer.createObject(); _root = _jsonBuffer.createObject();
}
} }
#elif ARDUINOJSON_VERSION_MAJOR == 6 #elif ARDUINOJSON_VERSION_MAJOR == 6
AsyncJsonResponse::AsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} { AsyncJsonResponse::AsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
_code = 200; _code = 200;
_contentType = asyncsrv::T_application_json; _contentType = asyncsrv::T_application_json;
if (isArray) if (isArray) {
_root = _jsonBuffer.createNestedArray(); _root = _jsonBuffer.createNestedArray();
else } else {
_root = _jsonBuffer.createNestedObject(); _root = _jsonBuffer.createNestedObject();
}
} }
#else #else
AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} { AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
_code = 200; _code = 200;
_contentType = asyncsrv::T_application_json; _contentType = asyncsrv::T_application_json;
if (isArray) if (isArray) {
_root = _jsonBuffer.add<JsonArray>(); _root = _jsonBuffer.add<JsonArray>();
else } else {
_root = _jsonBuffer.add<JsonObject>(); _root = _jsonBuffer.add<JsonObject>();
}
} }
#endif #endif
size_t AsyncJsonResponse::setLength() { size_t AsyncJsonResponse::setLength() {
#if ARDUINOJSON_VERSION_MAJOR == 5 #if ARDUINOJSON_VERSION_MAJOR == 5
_contentLength = _root.measureLength(); _contentLength = _root.measureLength();
#else #else
_contentLength = measureJson(_root); _contentLength = measureJson(_root);
#endif #endif
if (_contentLength) { if (_contentLength) {
_isValid = true; _isValid = true;
} }
return _contentLength; return _contentLength;
} }
size_t AsyncJsonResponse::_fillBuffer(uint8_t* data, size_t len) { size_t AsyncJsonResponse::_fillBuffer(uint8_t *data, size_t len) {
ChunkPrint dest(data, _sentLength, len); ChunkPrint dest(data, _sentLength, len);
#if ARDUINOJSON_VERSION_MAJOR == 5 #if ARDUINOJSON_VERSION_MAJOR == 5
_root.printTo(dest); _root.printTo(dest);
#else #else
serializeJson(_root, dest); serializeJson(_root, dest);
#endif #endif
return len; return len;
} }
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : AsyncJsonResponse{isArray, maxJsonBufferSize} {} PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : AsyncJsonResponse{isArray, maxJsonBufferSize} {}
#else #else
PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray) : AsyncJsonResponse{isArray} {} PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray) : AsyncJsonResponse{isArray} {}
#endif #endif
size_t PrettyAsyncJsonResponse::setLength() { size_t PrettyAsyncJsonResponse::setLength() {
#if ARDUINOJSON_VERSION_MAJOR == 5 #if ARDUINOJSON_VERSION_MAJOR == 5
_contentLength = _root.measurePrettyLength(); _contentLength = _root.measurePrettyLength();
#else #else
_contentLength = measureJsonPretty(_root); _contentLength = measureJsonPretty(_root);
#endif #endif
if (_contentLength) { if (_contentLength) {
_isValid = true; _isValid = true;
} }
return _contentLength; return _contentLength;
} }
size_t PrettyAsyncJsonResponse::_fillBuffer(uint8_t* data, size_t len) { size_t PrettyAsyncJsonResponse::_fillBuffer(uint8_t *data, size_t len) {
ChunkPrint dest(data, _sentLength, len); ChunkPrint dest(data, _sentLength, len);
#if ARDUINOJSON_VERSION_MAJOR == 5 #if ARDUINOJSON_VERSION_MAJOR == 5
_root.prettyPrintTo(dest); _root.prettyPrintTo(dest);
#else #else
serializeJsonPretty(_root, dest); serializeJsonPretty(_root, dest);
#endif #endif
return len; return len;
} }
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize) AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize)
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {} : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
#else #else
AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest) AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest)
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {} : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
#endif #endif
bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest* request) const { bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
return false; return false;
}
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
return false; return false;
}
if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_json)) if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_json)) {
return false; return false;
}
return true; return true;
} }
void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest* request) { void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) {
if (_onRequest) { if (_onRequest) {
if (request->method() == HTTP_GET) { if (request->method() == HTTP_GET) {
JsonVariant json; JsonVariant json;
@ -110,21 +119,21 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest* request)
return; return;
} else if (request->_tempObject != NULL) { } else if (request->_tempObject != NULL) {
#if ARDUINOJSON_VERSION_MAJOR == 5 #if ARDUINOJSON_VERSION_MAJOR == 5
DynamicJsonBuffer jsonBuffer; DynamicJsonBuffer jsonBuffer;
JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject)); JsonVariant json = jsonBuffer.parse((uint8_t *)(request->_tempObject));
if (json.success()) { if (json.success()) {
#elif ARDUINOJSON_VERSION_MAJOR == 6 #elif ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
if (!error) { if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>(); JsonVariant json = jsonBuffer.as<JsonVariant>();
#else #else
JsonDocument jsonBuffer; JsonDocument jsonBuffer;
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
if (!error) { if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>(); JsonVariant json = jsonBuffer.as<JsonVariant>();
#endif #endif
_onRequest(request, json); _onRequest(request, json);
return; return;
@ -136,14 +145,21 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest* request)
} }
} }
void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) { void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
if (_onRequest) { if (_onRequest) {
_contentLength = total; _contentLength = total;
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
request->_tempObject = malloc(total); request->_tempObject = malloc(total);
if (request->_tempObject == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
} }
if (request->_tempObject != NULL) { if (request->_tempObject != NULL) {
memcpy((uint8_t*)(request->_tempObject) + index, data, len); memcpy((uint8_t *)(request->_tempObject) + index, data, len);
} }
} }
} }

View file

@ -1,129 +1,117 @@
// AsyncJson.h // SPDX-License-Identifier: LGPL-3.0-or-later
/* // Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Async Response to use with ArduinoJson and AsyncWebServer
Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon.
Example of callback in use
server.on("/json", HTTP_ANY, [](AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse();
JsonObject& root = response->getRoot();
root["key1"] = "key number one";
JsonObject& nested = root.createNestedObject("nested");
nested["key1"] = "key number one";
response->setLength();
request->send(response);
});
--------------------
Async Request to use with ArduinoJson and AsyncWebServer
Written by Arsène von Wyss (avonwyss)
Example
AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint");
handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
JsonObject jsonObj = json.as<JsonObject>();
// ...
});
server.addHandler(handler);
*/
#ifndef ASYNC_JSON_H_ #ifndef ASYNC_JSON_H_
#define ASYNC_JSON_H_ #define ASYNC_JSON_H_
#if __has_include("ArduinoJson.h") #if __has_include("ArduinoJson.h")
#include <ArduinoJson.h> #include <ArduinoJson.h>
#if ARDUINOJSON_VERSION_MAJOR >= 5 #if ARDUINOJSON_VERSION_MAJOR >= 5
#define ASYNC_JSON_SUPPORT 1 #define ASYNC_JSON_SUPPORT 1
#else #else
#define ASYNC_JSON_SUPPORT 0 #define ASYNC_JSON_SUPPORT 0
#endif // ARDUINOJSON_VERSION_MAJOR >= 5 #endif // ARDUINOJSON_VERSION_MAJOR >= 5
#endif // __has_include("ArduinoJson.h") #endif // __has_include("ArduinoJson.h")
#if ASYNC_JSON_SUPPORT == 1 #if ASYNC_JSON_SUPPORT == 1
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include "ChunkPrint.h" #include "ChunkPrint.h"
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE #ifndef DYNAMIC_JSON_DOCUMENT_SIZE
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024 #define DYNAMIC_JSON_DOCUMENT_SIZE 1024
#endif #endif
#endif #endif
class AsyncJsonResponse : public AsyncAbstractResponse { class AsyncJsonResponse : public AsyncAbstractResponse {
protected: protected:
#if ARDUINOJSON_VERSION_MAJOR == 5 #if ARDUINOJSON_VERSION_MAJOR == 5
DynamicJsonBuffer _jsonBuffer; DynamicJsonBuffer _jsonBuffer;
#elif ARDUINOJSON_VERSION_MAJOR == 6 #elif ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument _jsonBuffer; DynamicJsonDocument _jsonBuffer;
#else #else
JsonDocument _jsonBuffer; JsonDocument _jsonBuffer;
#endif #endif
JsonVariant _root; JsonVariant _root;
bool _isValid; bool _isValid;
public: public:
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else #else
AsyncJsonResponse(bool isArray = false); AsyncJsonResponse(bool isArray = false);
#endif #endif
JsonVariant& getRoot() { return _root; } JsonVariant &getRoot() {
bool _sourceValid() const { return _isValid; } return _root;
}
bool _sourceValid() const {
return _isValid;
}
size_t setLength(); size_t setLength();
size_t getSize() const { return _jsonBuffer.size(); } size_t getSize() const {
size_t _fillBuffer(uint8_t* data, size_t len); return _jsonBuffer.size();
#if ARDUINOJSON_VERSION_MAJOR >= 6 }
bool overflowed() const { return _jsonBuffer.overflowed(); } size_t _fillBuffer(uint8_t *data, size_t len);
#endif #if ARDUINOJSON_VERSION_MAJOR >= 6
bool overflowed() const {
return _jsonBuffer.overflowed();
}
#endif
}; };
class PrettyAsyncJsonResponse : public AsyncJsonResponse { class PrettyAsyncJsonResponse : public AsyncJsonResponse {
public: public:
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else #else
PrettyAsyncJsonResponse(bool isArray = false); PrettyAsyncJsonResponse(bool isArray = false);
#endif #endif
size_t setLength(); size_t setLength();
size_t _fillBuffer(uint8_t* data, size_t len); size_t _fillBuffer(uint8_t *data, size_t len);
}; };
typedef std::function<void(AsyncWebServerRequest* request, JsonVariant& json)> ArJsonRequestHandlerFunction; typedef std::function<void(AsyncWebServerRequest *request, JsonVariant &json)> ArJsonRequestHandlerFunction;
class AsyncCallbackJsonWebHandler : public AsyncWebHandler { class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
protected: protected:
String _uri; String _uri;
WebRequestMethodComposite _method; WebRequestMethodComposite _method;
ArJsonRequestHandlerFunction _onRequest; ArJsonRequestHandlerFunction _onRequest;
size_t _contentLength; size_t _contentLength;
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
size_t maxJsonBufferSize; size_t maxJsonBufferSize;
#endif #endif
size_t _maxContentLength; size_t _maxContentLength;
public: public:
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else #else
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest = nullptr); AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest = nullptr);
#endif #endif
void setMethod(WebRequestMethodComposite method) { _method = method; } void setMethod(WebRequestMethodComposite method) {
void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; } _method = method;
void onRequest(ArJsonRequestHandlerFunction fn) { _onRequest = fn; } }
void setMaxContentLength(int maxContentLength) {
_maxContentLength = maxContentLength;
}
void onRequest(ArJsonRequestHandlerFunction fn) {
_onRequest = fn;
}
bool canHandle(AsyncWebServerRequest* request) const override final; bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest* request) override final; void handleRequest(AsyncWebServerRequest *request) override final;
void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) override final {} void handleUpload(
void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final; __unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
bool isRequestHandlerTrivial() const override final { return !_onRequest; } __unused bool final
) override final {}
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final;
bool isRequestHandlerTrivial() const override final {
return !_onRequest;
}
}; };
#endif // ASYNC_JSON_SUPPORT == 1 #endif // ASYNC_JSON_SUPPORT == 1

View file

@ -1,26 +1,31 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include "AsyncMessagePack.h" #include "AsyncMessagePack.h"
#if ASYNC_MSG_PACK_SUPPORT == 1 #if ASYNC_MSG_PACK_SUPPORT == 1
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} { AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
_code = 200; _code = 200;
_contentType = asyncsrv::T_application_msgpack; _contentType = asyncsrv::T_application_msgpack;
if (isArray) if (isArray) {
_root = _jsonBuffer.createNestedArray(); _root = _jsonBuffer.createNestedArray();
else } else {
_root = _jsonBuffer.createNestedObject(); _root = _jsonBuffer.createNestedObject();
}
} }
#else #else
AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray) : _isValid{false} { AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray) : _isValid{false} {
_code = 200; _code = 200;
_contentType = asyncsrv::T_application_msgpack; _contentType = asyncsrv::T_application_msgpack;
if (isArray) if (isArray) {
_root = _jsonBuffer.add<JsonArray>(); _root = _jsonBuffer.add<JsonArray>();
else } else {
_root = _jsonBuffer.add<JsonObject>(); _root = _jsonBuffer.add<JsonObject>();
}
} }
#endif #endif
size_t AsyncMessagePackResponse::setLength() { size_t AsyncMessagePackResponse::setLength() {
_contentLength = measureMsgPack(_root); _contentLength = measureMsgPack(_root);
@ -30,34 +35,39 @@ size_t AsyncMessagePackResponse::setLength() {
return _contentLength; return _contentLength;
} }
size_t AsyncMessagePackResponse::_fillBuffer(uint8_t* data, size_t len) { size_t AsyncMessagePackResponse::_fillBuffer(uint8_t *data, size_t len) {
ChunkPrint dest(data, _sentLength, len); ChunkPrint dest(data, _sentLength, len);
serializeMsgPack(_root, dest); serializeMsgPack(_root, dest);
return len; return len;
} }
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest, size_t maxJsonBufferSize) AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(
const String &uri, ArMessagePackRequestHandlerFunction onRequest, size_t maxJsonBufferSize
)
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {} : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
#else #else
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest) AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest)
: _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {} : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
#endif #endif
bool AsyncCallbackMessagePackWebHandler::canHandle(AsyncWebServerRequest* request) const { bool AsyncCallbackMessagePackWebHandler::canHandle(AsyncWebServerRequest *request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
return false; return false;
}
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
return false; return false;
}
if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack)) if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack)) {
return false; return false;
}
return true; return true;
} }
void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest* request) { void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest *request) {
if (_onRequest) { if (_onRequest) {
if (request->method() == HTTP_GET) { if (request->method() == HTTP_GET) {
JsonVariant json; JsonVariant json;
@ -65,17 +75,17 @@ void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest* re
return; return;
} else if (request->_tempObject != NULL) { } else if (request->_tempObject != NULL) {
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t*)(request->_tempObject)); DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t *)(request->_tempObject));
if (!error) { if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>(); JsonVariant json = jsonBuffer.as<JsonVariant>();
#else #else
JsonDocument jsonBuffer; JsonDocument jsonBuffer;
DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t*)(request->_tempObject)); DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t *)(request->_tempObject));
if (!error) { if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>(); JsonVariant json = jsonBuffer.as<JsonVariant>();
#endif #endif
_onRequest(request, json); _onRequest(request, json);
return; return;
@ -87,14 +97,21 @@ void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest* re
} }
} }
void AsyncCallbackMessagePackWebHandler::handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) { void AsyncCallbackMessagePackWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
if (_onRequest) { if (_onRequest) {
_contentLength = total; _contentLength = total;
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
request->_tempObject = malloc(total); request->_tempObject = malloc(total);
if (request->_tempObject == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
} }
if (request->_tempObject != NULL) { if (request->_tempObject != NULL) {
memcpy((uint8_t*)(request->_tempObject) + index, data, len); memcpy((uint8_t *)(request->_tempObject) + index, data, len);
} }
} }
} }

View file

@ -1,3 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#pragma once #pragma once
/* /*
@ -22,81 +25,102 @@
*/ */
#if __has_include("ArduinoJson.h") #if __has_include("ArduinoJson.h")
#include <ArduinoJson.h> #include <ArduinoJson.h>
#if ARDUINOJSON_VERSION_MAJOR >= 6 #if ARDUINOJSON_VERSION_MAJOR >= 6
#define ASYNC_MSG_PACK_SUPPORT 1 #define ASYNC_MSG_PACK_SUPPORT 1
#else #else
#define ASYNC_MSG_PACK_SUPPORT 0 #define ASYNC_MSG_PACK_SUPPORT 0
#endif // ARDUINOJSON_VERSION_MAJOR >= 6 #endif // ARDUINOJSON_VERSION_MAJOR >= 6
#endif // __has_include("ArduinoJson.h") #endif // __has_include("ArduinoJson.h")
#if ASYNC_MSG_PACK_SUPPORT == 1 #if ASYNC_MSG_PACK_SUPPORT == 1
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include "ChunkPrint.h" #include "ChunkPrint.h"
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
#ifndef DYNAMIC_JSON_DOCUMENT_SIZE #ifndef DYNAMIC_JSON_DOCUMENT_SIZE
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024 #define DYNAMIC_JSON_DOCUMENT_SIZE 1024
#endif #endif
#endif #endif
class AsyncMessagePackResponse : public AsyncAbstractResponse { class AsyncMessagePackResponse : public AsyncAbstractResponse {
protected: protected:
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
DynamicJsonDocument _jsonBuffer; DynamicJsonDocument _jsonBuffer;
#else #else
JsonDocument _jsonBuffer; JsonDocument _jsonBuffer;
#endif #endif
JsonVariant _root; JsonVariant _root;
bool _isValid; bool _isValid;
public: public:
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
AsyncMessagePackResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); AsyncMessagePackResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
#else #else
AsyncMessagePackResponse(bool isArray = false); AsyncMessagePackResponse(bool isArray = false);
#endif #endif
JsonVariant& getRoot() { return _root; } JsonVariant &getRoot() {
bool _sourceValid() const { return _isValid; } return _root;
}
bool _sourceValid() const {
return _isValid;
}
size_t setLength(); size_t setLength();
size_t getSize() const { return _jsonBuffer.size(); } size_t getSize() const {
size_t _fillBuffer(uint8_t* data, size_t len); return _jsonBuffer.size();
#if ARDUINOJSON_VERSION_MAJOR >= 6 }
bool overflowed() const { return _jsonBuffer.overflowed(); } size_t _fillBuffer(uint8_t *data, size_t len);
#endif #if ARDUINOJSON_VERSION_MAJOR >= 6
bool overflowed() const {
return _jsonBuffer.overflowed();
}
#endif
}; };
typedef std::function<void(AsyncWebServerRequest* request, JsonVariant& json)> ArMessagePackRequestHandlerFunction; typedef std::function<void(AsyncWebServerRequest *request, JsonVariant &json)> ArMessagePackRequestHandlerFunction;
class AsyncCallbackMessagePackWebHandler : public AsyncWebHandler { class AsyncCallbackMessagePackWebHandler : public AsyncWebHandler {
protected: protected:
String _uri; String _uri;
WebRequestMethodComposite _method; WebRequestMethodComposite _method;
ArMessagePackRequestHandlerFunction _onRequest; ArMessagePackRequestHandlerFunction _onRequest;
size_t _contentLength; size_t _contentLength;
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
size_t maxJsonBufferSize; size_t maxJsonBufferSize;
#endif #endif
size_t _maxContentLength; size_t _maxContentLength;
public: public:
#if ARDUINOJSON_VERSION_MAJOR == 6 #if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); AsyncCallbackMessagePackWebHandler(
#else const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE
AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest = nullptr); );
#endif #else
AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr);
#endif
void setMethod(WebRequestMethodComposite method) { _method = method; } void setMethod(WebRequestMethodComposite method) {
void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; } _method = method;
void onRequest(ArMessagePackRequestHandlerFunction fn) { _onRequest = fn; } }
void setMaxContentLength(int maxContentLength) {
_maxContentLength = maxContentLength;
}
void onRequest(ArMessagePackRequestHandlerFunction fn) {
_onRequest = fn;
}
bool canHandle(AsyncWebServerRequest* request) const override final; bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest* request) override final; void handleRequest(AsyncWebServerRequest *request) override final;
void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) override final {} void handleUpload(
void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final; __unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
bool isRequestHandlerTrivial() const override final { return !_onRequest; } __unused bool final
) override final {}
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final;
bool isRequestHandlerTrivial() const override final {
return !_onRequest;
}
}; };
#endif // ASYNC_MSG_PACK_SUPPORT == 1 #endif // ASYNC_MSG_PACK_SUPPORT == 1

View file

@ -1,22 +1,32 @@
#include "ESPAsyncWebServer.h" // SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
AsyncWebHeader::AsyncWebHeader(const String& data) { #include <ESPAsyncWebServer.h>
if (!data)
AsyncWebHeader::AsyncWebHeader(const String &data) {
if (!data) {
return; return;
}
int index = data.indexOf(':'); int index = data.indexOf(':');
if (index < 0) if (index < 0) {
return; return;
}
_name = data.substring(0, index); _name = data.substring(0, index);
_value = data.substring(index + 2); _value = data.substring(index + 2);
} }
String AsyncWebHeader::toString() const { String AsyncWebHeader::toString() const {
String str; String str;
str.reserve(_name.length() + _value.length() + 2); if (str.reserve(_name.length() + _value.length() + 2)) {
str.concat(_name); str.concat(_name);
str.concat((char)0x3a); str.concat((char)0x3a);
str.concat((char)0x20); str.concat((char)0x20);
str.concat(_value); str.concat(_value);
str.concat(asyncsrv::T_rn); str.concat(asyncsrv::T_rn);
} else {
#ifdef ESP32
log_e("Failed to allocate");
#endif
}
return str; return str;
} }

View file

@ -0,0 +1,40 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/** Major version number (X.x.x) */
#define ASYNCWEBSERVER_VERSION_MAJOR 3
/** Minor version number (x.X.x) */
#define ASYNCWEBSERVER_VERSION_MINOR 7
/** Patch version number (x.x.X) */
#define ASYNCWEBSERVER_VERSION_PATCH 2
/**
* Macro to convert version number into an integer
*
* To be used in comparisons, such as ASYNCWEBSERVER_VERSION >= ASYNCWEBSERVER_VERSION_VAL(2, 0, 0)
*/
#define ASYNCWEBSERVER_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
/**
* Current version, as an integer
*
* To be used in comparisons, such as ASYNCWEBSERVER_VERSION_NUM >= ASYNCWEBSERVER_VERSION_VAL(2, 0, 0)
*/
#define ASYNCWEBSERVER_VERSION_NUM ASYNCWEBSERVER_VERSION_VAL(ASYNCWEBSERVER_VERSION_MAJOR, ASYNCWEBSERVER_VERSION_MINOR, ASYNCWEBSERVER_VERSION_PATCH)
/**
* Current version, as string
*/
#define df2xstr(s) #s
#define df2str(s) df2xstr(s)
#define ASYNCWEBSERVER_VERSION df2str(ASYNCWEBSERVER_VERSION_MAJOR) "." df2str(ASYNCWEBSERVER_VERSION_MINOR) "." df2str(ASYNCWEBSERVER_VERSION_PATCH)
#ifdef __cplusplus
}
#endif

View file

@ -1,62 +1,45 @@
/* // SPDX-License-Identifier: LGPL-3.0-or-later
Asynchronous WebServer library for Espressif MCUs // Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCWEBSOCKET_H_ #ifndef ASYNCWEBSOCKET_H_
#define ASYNCWEBSOCKET_H_ #define ASYNCWEBSOCKET_H_
#include <Arduino.h> #include <Arduino.h>
#ifdef ESP32 #ifdef ESP32
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h" #include <AsyncTCP.h>
#include <mutex> #include <mutex>
#ifndef WS_MAX_QUEUED_MESSAGES #ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 32 #define WS_MAX_QUEUED_MESSAGES 32
#endif #endif
#elif defined(ESP8266) #elif defined(ESP8266)
#include <ESPAsyncTCP.h> #include <ESPAsyncTCP.h>
#ifndef WS_MAX_QUEUED_MESSAGES #ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 8 #define WS_MAX_QUEUED_MESSAGES 8
#endif #endif
#elif defined(TARGET_RP2040) #elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <AsyncTCP_RP2040W.h> #include <RPAsyncTCP.h>
#ifndef WS_MAX_QUEUED_MESSAGES #ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 32 #define WS_MAX_QUEUED_MESSAGES 32
#endif #endif
#endif #endif
#include "ESPAsyncWebServer.h" #include <ESPAsyncWebServer.h>
#include <memory> #include <memory>
#ifdef ESP8266 #ifdef ESP8266
#include <Hash.h> #include <Hash.h>
#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
#include <../src/Hash.h> #include <../src/Hash.h>
#endif #endif
#endif #endif
#ifndef DEFAULT_MAX_WS_CLIENTS #ifndef DEFAULT_MAX_WS_CLIENTS
#ifdef ESP32 #ifdef ESP32
#define DEFAULT_MAX_WS_CLIENTS 8 #define DEFAULT_MAX_WS_CLIENTS 8
#else #else
#define DEFAULT_MAX_WS_CLIENTS 4 #define DEFAULT_MAX_WS_CLIENTS 4
#endif #endif
#endif #endif
using AsyncWebSocketSharedBuffer = std::shared_ptr<std::vector<uint8_t>>; using AsyncWebSocketSharedBuffer = std::shared_ptr<std::vector<uint8_t>>;
@ -90,44 +73,56 @@ typedef struct {
uint64_t index; uint64_t index;
} AwsFrameInfo; } AwsFrameInfo;
typedef enum { WS_DISCONNECTED, typedef enum {
WS_DISCONNECTED,
WS_CONNECTED, WS_CONNECTED,
WS_DISCONNECTING } AwsClientStatus; WS_DISCONNECTING
typedef enum { WS_CONTINUATION, } AwsClientStatus;
typedef enum {
WS_CONTINUATION,
WS_TEXT, WS_TEXT,
WS_BINARY, WS_BINARY,
WS_DISCONNECT = 0x08, WS_DISCONNECT = 0x08,
WS_PING, WS_PING,
WS_PONG } AwsFrameType; WS_PONG
typedef enum { WS_MSG_SENDING, } AwsFrameType;
typedef enum {
WS_MSG_SENDING,
WS_MSG_SENT, WS_MSG_SENT,
WS_MSG_ERROR } AwsMessageStatus; WS_MSG_ERROR
typedef enum { WS_EVT_CONNECT, } AwsMessageStatus;
typedef enum {
WS_EVT_CONNECT,
WS_EVT_DISCONNECT, WS_EVT_DISCONNECT,
WS_EVT_PING, WS_EVT_PING,
WS_EVT_PONG, WS_EVT_PONG,
WS_EVT_ERROR, WS_EVT_ERROR,
WS_EVT_DATA } AwsEventType; WS_EVT_DATA
} AwsEventType;
class AsyncWebSocketMessageBuffer { class AsyncWebSocketMessageBuffer {
friend AsyncWebSocket; friend AsyncWebSocket;
friend AsyncWebSocketClient; friend AsyncWebSocketClient;
private: private:
AsyncWebSocketSharedBuffer _buffer; AsyncWebSocketSharedBuffer _buffer;
public: public:
AsyncWebSocketMessageBuffer() {} AsyncWebSocketMessageBuffer() {}
explicit AsyncWebSocketMessageBuffer(size_t size); explicit AsyncWebSocketMessageBuffer(size_t size);
AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size); AsyncWebSocketMessageBuffer(const uint8_t *data, size_t size);
//~AsyncWebSocketMessageBuffer(); //~AsyncWebSocketMessageBuffer();
bool reserve(size_t size); bool reserve(size_t size);
uint8_t* get() { return _buffer->data(); } uint8_t *get() {
size_t length() const { return _buffer->size(); } return _buffer->data();
}
size_t length() const {
return _buffer->size();
}
}; };
class AsyncWebSocketMessage { class AsyncWebSocketMessage {
private: private:
AsyncWebSocketSharedBuffer _WSbuffer; AsyncWebSocketSharedBuffer _WSbuffer;
uint8_t _opcode{WS_TEXT}; uint8_t _opcode{WS_TEXT};
bool _mask{false}; bool _mask{false};
@ -136,20 +131,24 @@ class AsyncWebSocketMessage {
size_t _ack{}; size_t _ack{};
size_t _acked{}; size_t _acked{};
public: public:
AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false); AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
bool finished() const { return _status != WS_MSG_SENDING; } bool finished() const {
bool betweenFrames() const { return _acked == _ack; } return _status != WS_MSG_SENDING;
}
bool betweenFrames() const {
return _acked == _ack;
}
void ack(size_t len, uint32_t time); void ack(size_t len, uint32_t time);
size_t send(AsyncClient* client); size_t send(AsyncClient *client);
}; };
class AsyncWebSocketClient { class AsyncWebSocketClient {
private: private:
AsyncClient* _client; AsyncClient *_client;
AsyncWebSocket* _server; AsyncWebSocket *_server;
uint32_t _clientId; uint32_t _clientId;
AwsClientStatus _status; AwsClientStatus _status;
#ifdef ESP32 #ifdef ESP32
@ -165,25 +164,39 @@ class AsyncWebSocketClient {
uint32_t _lastMessageTime; uint32_t _lastMessageTime;
uint32_t _keepAlivePeriod; uint32_t _keepAlivePeriod;
bool _queueControl(uint8_t opcode, const uint8_t* data = NULL, size_t len = 0, bool mask = false); bool _queueControl(uint8_t opcode, const uint8_t *data = NULL, size_t len = 0, bool mask = false);
bool _queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false); bool _queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
void _runQueue(); void _runQueue();
void _clearQueue(); void _clearQueue();
public: public:
void* _tempObject; void *_tempObject;
AsyncWebSocketClient(AsyncWebServerRequest* request, AsyncWebSocket* server); AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server);
~AsyncWebSocketClient(); ~AsyncWebSocketClient();
// client id increments for the given server // client id increments for the given server
uint32_t id() const { return _clientId; } uint32_t id() const {
AwsClientStatus status() const { return _status; } return _clientId;
AsyncClient* client() { return _client; } }
const AsyncClient* client() const { return _client; } AwsClientStatus status() const {
AsyncWebSocket* server() { return _server; } return _status;
const AsyncWebSocket* server() const { return _server; } }
AwsFrameInfo const& pinfo() const { return _pinfo; } AsyncClient *client() {
return _client;
}
const AsyncClient *client() const {
return _client;
}
AsyncWebSocket *server() {
return _server;
}
const AsyncWebSocket *server() const {
return _server;
}
AwsFrameInfo const &pinfo() const {
return _pinfo;
}
// - If "true" (default), the connection will be closed if the message queue is full. // - If "true" (default), the connection will be closed if the message queue is full.
// This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection. // This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection.
@ -203,17 +216,23 @@ class AsyncWebSocketClient {
// Use cases:, // Use cases:,
// - if using websocket to send logging messages, maybe some loss is acceptable. // - if using websocket to send logging messages, maybe some loss is acceptable.
// - But if using websocket to send UI update messages, maybe the connection should be closed and the UI redrawn. // - But if using websocket to send UI update messages, maybe the connection should be closed and the UI redrawn.
void setCloseClientOnQueueFull(bool close) { closeWhenFull = close; } void setCloseClientOnQueueFull(bool close) {
bool willCloseClientOnQueueFull() const { return closeWhenFull; } closeWhenFull = close;
}
bool willCloseClientOnQueueFull() const {
return closeWhenFull;
}
IPAddress remoteIP() const; IPAddress remoteIP() const;
uint16_t remotePort() const; uint16_t remotePort() const;
bool shouldBeDeleted() const { return !_client; } bool shouldBeDeleted() const {
return !_client;
}
// control frames // control frames
void close(uint16_t code = 0, const char* message = NULL); void close(uint16_t code = 0, const char *message = NULL);
bool ping(const uint8_t* data = NULL, size_t len = 0); bool ping(const uint8_t *data = NULL, size_t len = 0);
// set auto-ping period in seconds. disabled if zero (default) // set auto-ping period in seconds. disabled if zero (default)
void keepAlivePeriod(uint16_t seconds) { void keepAlivePeriod(uint16_t seconds) {
@ -224,25 +243,27 @@ class AsyncWebSocketClient {
} }
// data packets // data packets
void message(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false) { _queueMessage(buffer, opcode, mask); } void message(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false) {
_queueMessage(buffer, opcode, mask);
}
bool queueIsFull() const; bool queueIsFull() const;
size_t queueLen() const; size_t queueLen() const;
size_t printf(const char* format, ...) __attribute__((format(printf, 2, 3))); size_t printf(const char *format, ...) __attribute__((format(printf, 2, 3)));
bool text(AsyncWebSocketSharedBuffer buffer); bool text(AsyncWebSocketSharedBuffer buffer);
bool text(const uint8_t* message, size_t len); bool text(const uint8_t *message, size_t len);
bool text(const char* message, size_t len); bool text(const char *message, size_t len);
bool text(const char* message); bool text(const char *message);
bool text(const String& message); bool text(const String &message);
bool text(AsyncWebSocketMessageBuffer* buffer); bool text(AsyncWebSocketMessageBuffer *buffer);
bool binary(AsyncWebSocketSharedBuffer buffer); bool binary(AsyncWebSocketSharedBuffer buffer);
bool binary(const uint8_t* message, size_t len); bool binary(const uint8_t *message, size_t len);
bool binary(const char* message, size_t len); bool binary(const char *message, size_t len);
bool binary(const char* message); bool binary(const char *message);
bool binary(const String& message); bool binary(const String &message);
bool binary(AsyncWebSocketMessageBuffer* buffer); bool binary(AsyncWebSocketMessageBuffer *buffer);
bool canSend() const; bool canSend() const;
@ -252,21 +273,21 @@ class AsyncWebSocketClient {
void _onPoll(); void _onPoll();
void _onTimeout(uint32_t time); void _onTimeout(uint32_t time);
void _onDisconnect(); void _onDisconnect();
void _onData(void* pbuf, size_t plen); void _onData(void *pbuf, size_t plen);
#ifdef ESP8266 #ifdef ESP8266
size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3))); size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
bool text(const __FlashStringHelper* message); bool text(const __FlashStringHelper *message);
bool binary(const __FlashStringHelper* message, size_t len); bool binary(const __FlashStringHelper *message, size_t len);
#endif #endif
}; };
using AwsHandshakeHandler = std::function<bool(AsyncWebServerRequest* request)>; using AwsHandshakeHandler = std::function<bool(AsyncWebServerRequest *request)>;
using AwsEventHandler = std::function<void(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)>; using AwsEventHandler = std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len)>;
// WebServer Handler implementation that plays the role of a socket server // WebServer Handler implementation that plays the role of a socket server
class AsyncWebSocket : public AsyncWebHandler { class AsyncWebSocket : public AsyncWebHandler {
private: private:
String _url; String _url;
std::list<AsyncWebSocketClient> _clients; std::list<AsyncWebSocketClient> _clients;
uint32_t _cNextId; uint32_t _cNextId;
@ -277,101 +298,119 @@ class AsyncWebSocket : public AsyncWebHandler {
mutable std::mutex _lock; mutable std::mutex _lock;
#endif #endif
public: public:
typedef enum { typedef enum {
DISCARDED = 0, DISCARDED = 0,
ENQUEUED = 1, ENQUEUED = 1,
PARTIALLY_ENQUEUED = 2, PARTIALLY_ENQUEUED = 2,
} SendStatus; } SendStatus;
explicit AsyncWebSocket(const char* url) : _url(url), _cNextId(1), _enabled(true) {} explicit AsyncWebSocket(const char *url) : _url(url), _cNextId(1), _enabled(true) {}
AsyncWebSocket(const String& url) : _url(url), _cNextId(1), _enabled(true) {} AsyncWebSocket(const String &url) : _url(url), _cNextId(1), _enabled(true) {}
~AsyncWebSocket() {}; ~AsyncWebSocket(){};
const char* url() const { return _url.c_str(); } const char *url() const {
void enable(bool e) { _enabled = e; } return _url.c_str();
bool enabled() const { return _enabled; } }
void enable(bool e) {
_enabled = e;
}
bool enabled() const {
return _enabled;
}
bool availableForWriteAll(); bool availableForWriteAll();
bool availableForWrite(uint32_t id); bool availableForWrite(uint32_t id);
size_t count() const; size_t count() const;
AsyncWebSocketClient* client(uint32_t id); AsyncWebSocketClient *client(uint32_t id);
bool hasClient(uint32_t id) { return client(id) != nullptr; } bool hasClient(uint32_t id) {
return client(id) != nullptr;
}
void close(uint32_t id, uint16_t code = 0, const char* message = NULL); void close(uint32_t id, uint16_t code = 0, const char *message = NULL);
void closeAll(uint16_t code = 0, const char* message = NULL); void closeAll(uint16_t code = 0, const char *message = NULL);
void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS); void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);
bool ping(uint32_t id, const uint8_t* data = NULL, size_t len = 0); bool ping(uint32_t id, const uint8_t *data = NULL, size_t len = 0);
SendStatus pingAll(const uint8_t* data = NULL, size_t len = 0); // done SendStatus pingAll(const uint8_t *data = NULL, size_t len = 0); // done
bool text(uint32_t id, const uint8_t* message, size_t len); bool text(uint32_t id, const uint8_t *message, size_t len);
bool text(uint32_t id, const char* message, size_t len); bool text(uint32_t id, const char *message, size_t len);
bool text(uint32_t id, const char* message); bool text(uint32_t id, const char *message);
bool text(uint32_t id, const String& message); bool text(uint32_t id, const String &message);
bool text(uint32_t id, AsyncWebSocketMessageBuffer* buffer); bool text(uint32_t id, AsyncWebSocketMessageBuffer *buffer);
bool text(uint32_t id, AsyncWebSocketSharedBuffer buffer); bool text(uint32_t id, AsyncWebSocketSharedBuffer buffer);
SendStatus textAll(const uint8_t* message, size_t len); SendStatus textAll(const uint8_t *message, size_t len);
SendStatus textAll(const char* message, size_t len); SendStatus textAll(const char *message, size_t len);
SendStatus textAll(const char* message); SendStatus textAll(const char *message);
SendStatus textAll(const String& message); SendStatus textAll(const String &message);
SendStatus textAll(AsyncWebSocketMessageBuffer* buffer); SendStatus textAll(AsyncWebSocketMessageBuffer *buffer);
SendStatus textAll(AsyncWebSocketSharedBuffer buffer); SendStatus textAll(AsyncWebSocketSharedBuffer buffer);
bool binary(uint32_t id, const uint8_t* message, size_t len); bool binary(uint32_t id, const uint8_t *message, size_t len);
bool binary(uint32_t id, const char* message, size_t len); bool binary(uint32_t id, const char *message, size_t len);
bool binary(uint32_t id, const char* message); bool binary(uint32_t id, const char *message);
bool binary(uint32_t id, const String& message); bool binary(uint32_t id, const String &message);
bool binary(uint32_t id, AsyncWebSocketMessageBuffer* buffer); bool binary(uint32_t id, AsyncWebSocketMessageBuffer *buffer);
bool binary(uint32_t id, AsyncWebSocketSharedBuffer buffer); bool binary(uint32_t id, AsyncWebSocketSharedBuffer buffer);
SendStatus binaryAll(const uint8_t* message, size_t len); SendStatus binaryAll(const uint8_t *message, size_t len);
SendStatus binaryAll(const char* message, size_t len); SendStatus binaryAll(const char *message, size_t len);
SendStatus binaryAll(const char* message); SendStatus binaryAll(const char *message);
SendStatus binaryAll(const String& message); SendStatus binaryAll(const String &message);
SendStatus binaryAll(AsyncWebSocketMessageBuffer* buffer); SendStatus binaryAll(AsyncWebSocketMessageBuffer *buffer);
SendStatus binaryAll(AsyncWebSocketSharedBuffer buffer); SendStatus binaryAll(AsyncWebSocketSharedBuffer buffer);
size_t printf(uint32_t id, const char* format, ...) __attribute__((format(printf, 3, 4))); size_t printf(uint32_t id, const char *format, ...) __attribute__((format(printf, 3, 4)));
size_t printfAll(const char* format, ...) __attribute__((format(printf, 2, 3))); size_t printfAll(const char *format, ...) __attribute__((format(printf, 2, 3)));
#ifdef ESP8266 #ifdef ESP8266
bool text(uint32_t id, const __FlashStringHelper* message); bool text(uint32_t id, const __FlashStringHelper *message);
SendStatus textAll(const __FlashStringHelper* message); SendStatus textAll(const __FlashStringHelper *message);
bool binary(uint32_t id, const __FlashStringHelper* message, size_t len); bool binary(uint32_t id, const __FlashStringHelper *message, size_t len);
SendStatus binaryAll(const __FlashStringHelper* message, size_t len); SendStatus binaryAll(const __FlashStringHelper *message, size_t len);
size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__((format(printf, 3, 4))); size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__((format(printf, 3, 4)));
size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3))); size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
#endif #endif
void onEvent(AwsEventHandler handler) { _eventHandler = handler; } void onEvent(AwsEventHandler handler) {
void handleHandshake(AwsHandshakeHandler handler) { _handshakeHandler = handler; } _eventHandler = handler;
}
void handleHandshake(AwsHandshakeHandler handler) {
_handshakeHandler = handler;
}
// system callbacks (do not call) // system callbacks (do not call)
uint32_t _getNextId() { return _cNextId++; } uint32_t _getNextId() {
AsyncWebSocketClient* _newClient(AsyncWebServerRequest* request); return _cNextId++;
void _handleEvent(AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len); }
bool canHandle(AsyncWebServerRequest* request) const override final; AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request);
void handleRequest(AsyncWebServerRequest* request) override final; 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;
// messagebuffer functions/objects. // messagebuffer functions/objects.
AsyncWebSocketMessageBuffer* makeBuffer(size_t size = 0); AsyncWebSocketMessageBuffer *makeBuffer(size_t size = 0);
AsyncWebSocketMessageBuffer* makeBuffer(const uint8_t* data, size_t size); AsyncWebSocketMessageBuffer *makeBuffer(const uint8_t *data, size_t size);
std::list<AsyncWebSocketClient>& getClients() { return _clients; } std::list<AsyncWebSocketClient> &getClients() {
return _clients;
}
}; };
// WebServer response to authenticate the socket and detach the tcp client from the web server request // WebServer response to authenticate the socket and detach the tcp client from the web server request
class AsyncWebSocketResponse : public AsyncWebServerResponse { class AsyncWebSocketResponse : public AsyncWebServerResponse {
private: private:
String _content; String _content;
AsyncWebSocket* _server; AsyncWebSocket *_server;
public: public:
AsyncWebSocketResponse(const String& key, AsyncWebSocket* server); AsyncWebSocketResponse(const String &key, AsyncWebSocket *server);
void _respond(AsyncWebServerRequest* request); void _respond(AsyncWebServerRequest *request);
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time); size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
bool _sourceValid() const { return true; } bool _sourceValid() const {
return true;
}
}; };
#endif /* ASYNCWEBSOCKET_H_ */ #endif /* ASYNCWEBSOCKET_H_ */

View file

@ -24,19 +24,19 @@
#define SHA1_HASH_SIZE 20 #define SHA1_HASH_SIZE 20
class SHA1Builder { class SHA1Builder {
private: private:
uint32_t total[2]; /* number of bytes processed */ uint32_t total[2]; /* number of bytes processed */
uint32_t state[5]; /* intermediate digest state */ uint32_t state[5]; /* intermediate digest state */
unsigned char buffer[64]; /* data block being processed */ unsigned char buffer[64]; /* data block being processed */
uint8_t hash[SHA1_HASH_SIZE]; /* SHA-1 result */ uint8_t hash[SHA1_HASH_SIZE]; /* SHA-1 result */
void process(const uint8_t* data); void process(const uint8_t *data);
public: public:
void begin(); void begin();
void add(const uint8_t* data, size_t len); void add(const uint8_t *data, size_t len);
void calculate(); void calculate();
void getBytes(uint8_t* output); void getBytes(uint8_t *output);
}; };
#endif // SHA1Builder_h #endif // SHA1Builder_h

View file

@ -1,7 +1,9 @@
#include "ChunkPrint.h" // SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
ChunkPrint::ChunkPrint(uint8_t* destination, size_t from, size_t len) #include <ChunkPrint.h>
: _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
ChunkPrint::ChunkPrint(uint8_t *destination, size_t from, size_t len) : _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
size_t ChunkPrint::write(uint8_t c) { size_t ChunkPrint::write(uint8_t c) {
if (_to_skip > 0) { if (_to_skip > 0) {

View file

@ -1,18 +1,23 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#ifndef CHUNKPRINT_H #ifndef CHUNKPRINT_H
#define CHUNKPRINT_H #define CHUNKPRINT_H
#include <Print.h> #include <Print.h>
class ChunkPrint : public Print { class ChunkPrint : public Print {
private: private:
uint8_t* _destination; uint8_t *_destination;
size_t _to_skip; size_t _to_skip;
size_t _to_write; size_t _to_write;
size_t _pos; size_t _pos;
public: public:
ChunkPrint(uint8_t* destination, size_t from, size_t len); ChunkPrint(uint8_t *destination, size_t from, size_t len);
size_t write(uint8_t c); size_t write(uint8_t c);
size_t write(const uint8_t* buffer, size_t size) { return this->Print::write(buffer, size); } size_t write(const uint8_t *buffer, size_t size) {
return this->Print::write(buffer, size);
}
}; };
#endif #endif

View file

@ -1,70 +1,85 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include "WebAuthentication.h" #include "WebAuthentication.h"
#include "ESPAsyncWebServer.h" #include <ESPAsyncWebServer.h>
AsyncMiddlewareChain::~AsyncMiddlewareChain() { AsyncMiddlewareChain::~AsyncMiddlewareChain() {
for (AsyncMiddleware* m : _middlewares) for (AsyncMiddleware *m : _middlewares) {
if (m->_freeOnRemoval) if (m->_freeOnRemoval) {
delete m; delete m;
}
}
} }
void AsyncMiddlewareChain::addMiddleware(ArMiddlewareCallback fn) { void AsyncMiddlewareChain::addMiddleware(ArMiddlewareCallback fn) {
AsyncMiddlewareFunction* m = new AsyncMiddlewareFunction(fn); AsyncMiddlewareFunction *m = new AsyncMiddlewareFunction(fn);
m->_freeOnRemoval = true; m->_freeOnRemoval = true;
_middlewares.emplace_back(m); _middlewares.emplace_back(m);
} }
void AsyncMiddlewareChain::addMiddleware(AsyncMiddleware* middleware) { void AsyncMiddlewareChain::addMiddleware(AsyncMiddleware *middleware) {
if (middleware) if (middleware) {
_middlewares.emplace_back(middleware); _middlewares.emplace_back(middleware);
}
} }
void AsyncMiddlewareChain::addMiddlewares(std::vector<AsyncMiddleware*> middlewares) { void AsyncMiddlewareChain::addMiddlewares(std::vector<AsyncMiddleware *> middlewares) {
for (AsyncMiddleware* m : middlewares) for (AsyncMiddleware *m : middlewares) {
addMiddleware(m); addMiddleware(m);
}
} }
bool AsyncMiddlewareChain::removeMiddleware(AsyncMiddleware* middleware) { bool AsyncMiddlewareChain::removeMiddleware(AsyncMiddleware *middleware) {
// remove all middlewares from _middlewares vector being equal to middleware, delete them having _freeOnRemoval flag to true and resize the vector. // remove all middlewares from _middlewares vector being equal to middleware, delete them having _freeOnRemoval flag to true and resize the vector.
const size_t size = _middlewares.size(); const size_t size = _middlewares.size();
_middlewares.erase(std::remove_if(_middlewares.begin(), _middlewares.end(), [middleware](AsyncMiddleware* m) { _middlewares.erase(
std::remove_if(
_middlewares.begin(), _middlewares.end(),
[middleware](AsyncMiddleware *m) {
if (m == middleware) { if (m == middleware) {
if (m->_freeOnRemoval) if (m->_freeOnRemoval) {
delete m; delete m;
}
return true; return true;
} }
return false; return false;
}), }
_middlewares.end()); ),
_middlewares.end()
);
return size != _middlewares.size(); return size != _middlewares.size();
} }
void AsyncMiddlewareChain::_runChain(AsyncWebServerRequest* request, ArMiddlewareNext finalizer) { void AsyncMiddlewareChain::_runChain(AsyncWebServerRequest *request, ArMiddlewareNext finalizer) {
if (!_middlewares.size()) if (!_middlewares.size()) {
return finalizer(); return finalizer();
}
ArMiddlewareNext next; ArMiddlewareNext next;
std::list<AsyncMiddleware*>::iterator it = _middlewares.begin(); std::list<AsyncMiddleware *>::iterator it = _middlewares.begin();
next = [this, &next, &it, request, finalizer]() { next = [this, &next, &it, request, finalizer]() {
if (it == _middlewares.end()) if (it == _middlewares.end()) {
return finalizer(); return finalizer();
AsyncMiddleware* m = *it; }
AsyncMiddleware *m = *it;
it++; it++;
return m->run(request, next); return m->run(request, next);
}; };
return next(); return next();
} }
void AsyncAuthenticationMiddleware::setUsername(const char* username) { void AsyncAuthenticationMiddleware::setUsername(const char *username) {
_username = username; _username = username;
_hasCreds = _username.length() && _credentials.length(); _hasCreds = _username.length() && _credentials.length();
} }
void AsyncAuthenticationMiddleware::setPassword(const char* password) { void AsyncAuthenticationMiddleware::setPassword(const char *password) {
_credentials = password; _credentials = password;
_hash = false; _hash = false;
_hasCreds = _username.length() && _credentials.length(); _hasCreds = _username.length() && _credentials.length();
} }
void AsyncAuthenticationMiddleware::setPasswordHash(const char* hash) { void AsyncAuthenticationMiddleware::setPasswordHash(const char *hash) {
_credentials = hash; _credentials = hash;
_hash = _credentials.length(); _hash = _credentials.length();
_hasCreds = _username.length() && _credentials.length(); _hasCreds = _username.length() && _credentials.length();
@ -72,71 +87,86 @@ void AsyncAuthenticationMiddleware::setPasswordHash(const char* hash) {
bool AsyncAuthenticationMiddleware::generateHash() { bool AsyncAuthenticationMiddleware::generateHash() {
// ensure we have all the necessary data // ensure we have all the necessary data
if (!_hasCreds) if (!_hasCreds) {
return false; return false;
}
// if we already have a hash, do nothing // if we already have a hash, do nothing
if (_hash) if (_hash) {
return false; return false;
}
switch (_authMethod) { switch (_authMethod) {
case AsyncAuthType::AUTH_DIGEST: case AsyncAuthType::AUTH_DIGEST:
_credentials = generateDigestHash(_username.c_str(), _credentials.c_str(), _realm.c_str()); _credentials = generateDigestHash(_username.c_str(), _credentials.c_str(), _realm.c_str());
if (_credentials.length()) {
_hash = true; _hash = true;
return true; return true;
} else {
return false;
}
case AsyncAuthType::AUTH_BASIC: case AsyncAuthType::AUTH_BASIC:
_credentials = generateBasicHash(_username.c_str(), _credentials.c_str()); _credentials = generateBasicHash(_username.c_str(), _credentials.c_str());
if (_credentials.length()) {
_hash = true; _hash = true;
return true; return true;
} else {
default:
return false; return false;
} }
default: return false;
}
} }
bool AsyncAuthenticationMiddleware::allowed(AsyncWebServerRequest* request) const { bool AsyncAuthenticationMiddleware::allowed(AsyncWebServerRequest *request) const {
if (_authMethod == AsyncAuthType::AUTH_NONE) if (_authMethod == AsyncAuthType::AUTH_NONE) {
return true; return true;
}
if (_authMethod == AsyncAuthType::AUTH_DENIED) if (_authMethod == AsyncAuthType::AUTH_DENIED) {
return false; return false;
}
if (!_hasCreds) if (!_hasCreds) {
return true; return true;
}
return request->authenticate(_username.c_str(), _credentials.c_str(), _realm.c_str(), _hash); return request->authenticate(_username.c_str(), _credentials.c_str(), _realm.c_str(), _hash);
} }
void AsyncAuthenticationMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) { void AsyncAuthenticationMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
return allowed(request) ? next() : request->requestAuthentication(_authMethod, _realm.c_str(), _authFailMsg.c_str()); return allowed(request) ? next() : request->requestAuthentication(_authMethod, _realm.c_str(), _authFailMsg.c_str());
} }
void AsyncHeaderFreeMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) { void AsyncHeaderFreeMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
std::vector<const char*> reqHeaders; std::list<const char *> toRemove;
request->getHeaderNames(reqHeaders); for (auto &h : request->getHeaders()) {
for (const char* h : reqHeaders) {
bool keep = false; bool keep = false;
for (const char* k : _toKeep) { for (const char *k : _toKeep) {
if (strcasecmp(h, k) == 0) { if (strcasecmp(h.name().c_str(), k) == 0) {
keep = true; keep = true;
break; break;
} }
} }
if (!keep) { if (!keep) {
toRemove.push_back(h.name().c_str());
}
}
for (const char *h : toRemove) {
request->removeHeader(h); request->removeHeader(h);
} }
next();
}
void AsyncHeaderFilterMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
for (auto it = _toRemove.begin(); it != _toRemove.end(); ++it) {
request->removeHeader(*it);
} }
next(); next();
} }
void AsyncHeaderFilterMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) { void AsyncLoggingMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
for (auto it = _toRemove.begin(); it != _toRemove.end(); ++it)
request->removeHeader(*it);
next();
}
void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
if (!isEnabled()) { if (!isEnabled()) {
next(); next();
return; return;
@ -152,7 +182,7 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNex
_out->print(request->url().c_str()); _out->print(request->url().c_str());
_out->print(F(" HTTP/1.")); _out->print(F(" HTTP/1."));
_out->println(request->version()); _out->println(request->version());
for (auto& h : request->getHeaders()) { for (auto &h : request->getHeaders()) {
if (h.value().length()) { if (h.value().length()) {
_out->print('>'); _out->print('>');
_out->print(' '); _out->print(' ');
@ -166,7 +196,7 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNex
uint32_t elapsed = millis(); uint32_t elapsed = millis();
next(); next();
elapsed = millis() - elapsed; elapsed = millis() - elapsed;
AsyncWebServerResponse* response = request->getResponse(); AsyncWebServerResponse *response = request->getResponse();
if (response) { if (response) {
_out->print(F("* Processed in ")); _out->print(F("* Processed in "));
_out->print(elapsed); _out->print(elapsed);
@ -178,7 +208,7 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNex
_out->print(response->code()); _out->print(response->code());
_out->print(' '); _out->print(' ');
_out->println(AsyncWebServerResponse::responseCodeToString(response->code())); _out->println(AsyncWebServerResponse::responseCodeToString(response->code()));
for (auto& h : response->getHeaders()) { for (auto &h : response->getHeaders()) {
if (h.value().length()) { if (h.value().length()) {
_out->print('<'); _out->print('<');
_out->print(' '); _out->print(' ');
@ -194,7 +224,7 @@ void AsyncLoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNex
} }
} }
void AsyncCorsMiddleware::addCORSHeaders(AsyncWebServerResponse* response) { void AsyncCorsMiddleware::addCORSHeaders(AsyncWebServerResponse *response) {
response->addHeader(asyncsrv::T_CORS_ACAO, _origin.c_str()); response->addHeader(asyncsrv::T_CORS_ACAO, _origin.c_str());
response->addHeader(asyncsrv::T_CORS_ACAM, _methods.c_str()); response->addHeader(asyncsrv::T_CORS_ACAM, _methods.c_str());
response->addHeader(asyncsrv::T_CORS_ACAH, _headers.c_str()); response->addHeader(asyncsrv::T_CORS_ACAH, _headers.c_str());
@ -202,12 +232,12 @@ void AsyncCorsMiddleware::addCORSHeaders(AsyncWebServerResponse* response) {
response->addHeader(asyncsrv::T_CORS_ACMA, String(_maxAge).c_str()); response->addHeader(asyncsrv::T_CORS_ACMA, String(_maxAge).c_str());
} }
void AsyncCorsMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) { void AsyncCorsMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
// Origin header ? => CORS handling // Origin header ? => CORS handling
if (request->hasHeader(asyncsrv::T_CORS_O)) { if (request->hasHeader(asyncsrv::T_CORS_O)) {
// check if this is a preflight request => handle it and return // check if this is a preflight request => handle it and return
if (request->method() == HTTP_OPTIONS) { if (request->method() == HTTP_OPTIONS) {
AsyncWebServerResponse* response = request->beginResponse(200); AsyncWebServerResponse *response = request->beginResponse(200);
addCORSHeaders(response); addCORSHeaders(response);
request->send(response); request->send(response);
return; return;
@ -215,7 +245,7 @@ void AsyncCorsMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext n
// CORS request, no options => let the request pass and add CORS headers after // CORS request, no options => let the request pass and add CORS headers after
next(); next();
AsyncWebServerResponse* response = request->getResponse(); AsyncWebServerResponse *response = request->getResponse();
if (response) { if (response) {
addCORSHeaders(response); addCORSHeaders(response);
} }
@ -226,11 +256,12 @@ void AsyncCorsMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext n
} }
} }
bool AsyncRateLimitMiddleware::isRequestAllowed(uint32_t& retryAfterSeconds) { bool AsyncRateLimitMiddleware::isRequestAllowed(uint32_t &retryAfterSeconds) {
uint32_t now = millis(); uint32_t now = millis();
while (!_requestTimes.empty() && _requestTimes.front() <= now - _windowSizeMillis) while (!_requestTimes.empty() && _requestTimes.front() <= now - _windowSizeMillis) {
_requestTimes.pop_front(); _requestTimes.pop_front();
}
_requestTimes.push_back(now); _requestTimes.push_back(now);
@ -244,12 +275,12 @@ bool AsyncRateLimitMiddleware::isRequestAllowed(uint32_t& retryAfterSeconds) {
return true; return true;
} }
void AsyncRateLimitMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) { void AsyncRateLimitMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
uint32_t retryAfterSeconds; uint32_t retryAfterSeconds;
if (isRequestAllowed(retryAfterSeconds)) { if (isRequestAllowed(retryAfterSeconds)) {
next(); next();
} else { } else {
AsyncWebServerResponse* response = request->beginResponse(429); AsyncWebServerResponse *response = request->beginResponse(429);
response->addHeader(asyncsrv::T_retry_after, retryAfterSeconds); response->addHeader(asyncsrv::T_retry_after, retryAfterSeconds);
request->send(response); request->send(response);
} }

View file

@ -1,29 +1,12 @@
/* // SPDX-License-Identifier: LGPL-3.0-or-later
Asynchronous WebServer library for Espressif MCUs // Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "WebAuthentication.h" #include "WebAuthentication.h"
#include <libb64/cencode.h> #include <libb64/cencode.h>
#if defined(ESP32) || defined(TARGET_RP2040) #if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <MD5Builder.h> #include <MD5Builder.h>
#else #else
#include "md5.h" #include "md5.h"
#endif #endif
#include "literals.h" #include "literals.h"
@ -31,23 +14,25 @@ using namespace asyncsrv;
// Basic Auth hash = base64("username:password") // Basic Auth hash = base64("username:password")
bool checkBasicAuthentication(const char* hash, const char* username, const char* password) { bool checkBasicAuthentication(const char *hash, const char *username, const char *password) {
if (username == NULL || password == NULL || hash == NULL) if (username == NULL || password == NULL || hash == NULL) {
return false; return false;
}
return generateBasicHash(username, password).equalsIgnoreCase(hash); return generateBasicHash(username, password).equalsIgnoreCase(hash);
} }
String generateBasicHash(const char* username, const char* password) { String generateBasicHash(const char *username, const char *password) {
if (username == NULL || password == NULL) if (username == NULL || password == NULL) {
return emptyString; return emptyString;
}
size_t toencodeLen = strlen(username) + strlen(password) + 1; size_t toencodeLen = strlen(username) + strlen(password) + 1;
char* toencode = new char[toencodeLen + 1]; char *toencode = new char[toencodeLen + 1];
if (toencode == NULL) { if (toencode == NULL) {
return emptyString; return emptyString;
} }
char* encoded = new char[base64_encode_expected_len(toencodeLen) + 1]; char *encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
if (encoded == NULL) { if (encoded == NULL) {
delete[] toencode; delete[] toencode;
return emptyString; return emptyString;
@ -64,8 +49,8 @@ String generateBasicHash(const char* username, const char* password) {
return emptyString; return emptyString;
} }
static bool getMD5(uint8_t* data, uint16_t len, char* output) { // 33 bytes or more static bool getMD5(uint8_t *data, uint16_t len, char *output) { // 33 bytes or more
#if defined(ESP32) || defined(TARGET_RP2040) #if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
MD5Builder md5; MD5Builder md5;
md5.begin(); md5.begin();
md5.add(data, len); md5.add(data, len);
@ -74,9 +59,10 @@ static bool getMD5(uint8_t* data, uint16_t len, char* output) { // 33 bytes or m
#else #else
md5_context_t _ctx; md5_context_t _ctx;
uint8_t* _buf = (uint8_t*)malloc(16); uint8_t *_buf = (uint8_t *)malloc(16);
if (_buf == NULL) if (_buf == NULL) {
return false; return false;
}
memset(_buf, 0x00, 16); memset(_buf, 0x00, 16);
MD5Init(&_ctx); MD5Init(&_ctx);
@ -98,49 +84,77 @@ String genRandomMD5() {
#else #else
uint32_t r = rand(); uint32_t r = rand();
#endif #endif
char* out = (char*)malloc(33); char *out = (char *)malloc(33);
if (out == NULL || !getMD5((uint8_t*)(&r), 4, out)) if (out == NULL || !getMD5((uint8_t *)(&r), 4, out)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
return emptyString; return emptyString;
}
String res = String(out); String res = String(out);
free(out); free(out);
return res; return res;
} }
static String stringMD5(const String& in) { static String stringMD5(const String &in) {
char* out = (char*)malloc(33); char *out = (char *)malloc(33);
if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) if (out == NULL || !getMD5((uint8_t *)(in.c_str()), in.length(), out)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
return emptyString; return emptyString;
}
String res = String(out); String res = String(out);
free(out); free(out);
return res; return res;
} }
String generateDigestHash(const char* username, const char* password, const char* realm) { String generateDigestHash(const char *username, const char *password, const char *realm) {
if (username == NULL || password == NULL || realm == NULL) { if (username == NULL || password == NULL || realm == NULL) {
return emptyString; return emptyString;
} }
char* out = (char*)malloc(33); char *out = (char *)malloc(33);
if (out == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
return emptyString;
}
String in; String in;
in.reserve(strlen(username) + strlen(realm) + strlen(password) + 2); if (!in.reserve(strlen(username) + strlen(realm) + strlen(password) + 2)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
free(out);
return emptyString;
}
in.concat(username); in.concat(username);
in.concat(':'); in.concat(':');
in.concat(realm); in.concat(realm);
in.concat(':'); in.concat(':');
in.concat(password); in.concat(password);
if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) if (!getMD5((uint8_t *)(in.c_str()), in.length(), out)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
free(out);
return emptyString; return emptyString;
}
in = String(out); in = String(out);
free(out); free(out);
return in; return in;
} }
bool checkDigestAuthentication(const char* header, const char* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri) bool checkDigestAuthentication(
{ const char *header, const char *method, const char *username, const char *password, const char *realm, bool passwordIsHash, const char *nonce,
const char *opaque, const char *uri
) {
if (username == NULL || password == NULL || header == NULL || method == NULL) { if (username == NULL || password == NULL || header == NULL || method == NULL) {
// os_printf("AUTH FAIL: missing requred fields\n"); // os_printf("AUTH FAIL: missing required fields\n");
return false; return false;
} }

View file

@ -1,37 +1,22 @@
/* // SPDX-License-Identifier: LGPL-3.0-or-later
Asynchronous WebServer library for Espressif MCUs // Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef WEB_AUTHENTICATION_H_ #ifndef WEB_AUTHENTICATION_H_
#define WEB_AUTHENTICATION_H_ #define WEB_AUTHENTICATION_H_
#include "Arduino.h" #include "Arduino.h"
bool checkBasicAuthentication(const char* header, const char* username, const char* password); bool checkBasicAuthentication(const char *header, const char *username, const char *password);
bool checkDigestAuthentication(const char* header, const char* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri); bool checkDigestAuthentication(
const char *header, const char *method, const char *username, const char *password, const char *realm, bool passwordIsHash, const char *nonce,
const char *opaque, const char *uri
);
// for storing hashed versions on the device that can be authenticated against // for storing hashed versions on the device that can be authenticated against
String generateDigestHash(const char* username, const char* password, const char* realm); String generateDigestHash(const char *username, const char *password, const char *realm);
String generateBasicHash(const char* username, const char* password); String generateBasicHash(const char *username, const char *password);
String genRandomMD5(); String genRandomMD5();

View file

@ -1,29 +1,12 @@
/* // SPDX-License-Identifier: LGPL-3.0-or-later
Asynchronous WebServer library for Espressif MCUs // Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCWEBSERVERHANDLERIMPL_H_ #ifndef ASYNCWEBSERVERHANDLERIMPL_H_
#define ASYNCWEBSERVERHANDLERIMPL_H_ #define ASYNCWEBSERVERHANDLERIMPL_H_
#include <string> #include <string>
#ifdef ASYNCWEBSERVER_REGEX #ifdef ASYNCWEBSERVER_REGEX
#include <regex> #include <regex>
#endif #endif
#include "stddef.h" #include "stddef.h"
@ -33,12 +16,12 @@ class AsyncStaticWebHandler : public AsyncWebHandler {
using File = fs::File; using File = fs::File;
using FS = fs::FS; using FS = fs::FS;
private: private:
bool _getFile(AsyncWebServerRequest* request) const; bool _getFile(AsyncWebServerRequest *request) const;
bool _searchFile(AsyncWebServerRequest* request, const String& path); bool _searchFile(AsyncWebServerRequest *request, const String &path);
uint8_t _countBits(const uint8_t value) const; uint8_t _countBits(const uint8_t value) const;
protected: protected:
FS _fs; FS _fs;
String _uri; String _uri;
String _path; String _path;
@ -49,14 +32,14 @@ class AsyncStaticWebHandler : public AsyncWebHandler {
bool _isDir; bool _isDir;
bool _tryGzipFirst = true; bool _tryGzipFirst = true;
public: public:
AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control); AsyncStaticWebHandler(const char *uri, FS &fs, const char *path, const char *cache_control);
bool canHandle(AsyncWebServerRequest* request) const override final; bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest* request) override final; void handleRequest(AsyncWebServerRequest *request) override final;
AsyncStaticWebHandler& setTryGzipFirst(bool value); AsyncStaticWebHandler &setTryGzipFirst(bool value);
AsyncStaticWebHandler& setIsDir(bool isDir); AsyncStaticWebHandler &setIsDir(bool isDir);
AsyncStaticWebHandler& setDefaultFile(const char* filename); AsyncStaticWebHandler &setDefaultFile(const char *filename);
AsyncStaticWebHandler& setCacheControl(const char* cache_control); AsyncStaticWebHandler &setCacheControl(const char *cache_control);
/** /**
* @brief Set the Last-Modified time for the object * @brief Set the Last-Modified time for the object
@ -64,18 +47,18 @@ class AsyncStaticWebHandler : public AsyncWebHandler {
* @param last_modified * @param last_modified
* @return AsyncStaticWebHandler& * @return AsyncStaticWebHandler&
*/ */
AsyncStaticWebHandler& setLastModified(const char* last_modified); AsyncStaticWebHandler &setLastModified(const char *last_modified);
AsyncStaticWebHandler& setLastModified(struct tm* last_modified); AsyncStaticWebHandler &setLastModified(struct tm *last_modified);
AsyncStaticWebHandler& setLastModified(time_t last_modified); AsyncStaticWebHandler &setLastModified(time_t last_modified);
// sets to current time. Make sure sntp is runing and time is updated // sets to current time. Make sure sntp is running and time is updated
AsyncStaticWebHandler& setLastModified(); AsyncStaticWebHandler &setLastModified();
AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback); AsyncStaticWebHandler &setTemplateProcessor(AwsTemplateProcessor newCallback);
}; };
class AsyncCallbackWebHandler : public AsyncWebHandler { class AsyncCallbackWebHandler : public AsyncWebHandler {
private: private:
protected: protected:
String _uri; String _uri;
WebRequestMethodComposite _method; WebRequestMethodComposite _method;
ArRequestHandlerFunction _onRequest; ArRequestHandlerFunction _onRequest;
@ -83,19 +66,29 @@ class AsyncCallbackWebHandler : public AsyncWebHandler {
ArBodyHandlerFunction _onBody; ArBodyHandlerFunction _onBody;
bool _isRegex; bool _isRegex;
public: public:
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {}
void setUri(const String& uri); void setUri(const String &uri);
void setMethod(WebRequestMethodComposite method) { _method = method; } void setMethod(WebRequestMethodComposite method) {
void onRequest(ArRequestHandlerFunction fn) { _onRequest = fn; } _method = method;
void onUpload(ArUploadHandlerFunction fn) { _onUpload = fn; } }
void onBody(ArBodyHandlerFunction fn) { _onBody = fn; } void onRequest(ArRequestHandlerFunction fn) {
_onRequest = fn;
}
void onUpload(ArUploadHandlerFunction fn) {
_onUpload = fn;
}
void onBody(ArBodyHandlerFunction fn) {
_onBody = fn;
}
bool canHandle(AsyncWebServerRequest* request) const override final; bool canHandle(AsyncWebServerRequest *request) const override final;
void handleRequest(AsyncWebServerRequest* request) override final; void handleRequest(AsyncWebServerRequest *request) override final;
void handleUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) override final; void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) override final;
void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final; void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final;
bool isRequestHandlerTrivial() const override final { return !_onRequest; } bool isRequestHandlerTrivial() const override final {
return !_onRequest;
}
}; };
#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */ #endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */

View file

@ -1,33 +1,16 @@
/* // SPDX-License-Identifier: LGPL-3.0-or-later
Asynchronous WebServer library for Espressif MCUs // Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ESPAsyncWebServer.h" #include "ESPAsyncWebServer.h"
#include "WebHandlerImpl.h" #include "WebHandlerImpl.h"
using namespace asyncsrv; using namespace asyncsrv;
AsyncWebHandler& AsyncWebHandler::setFilter(ArRequestFilterFunction fn) { AsyncWebHandler &AsyncWebHandler::setFilter(ArRequestFilterFunction fn) {
_filter = fn; _filter = fn;
return *this; return *this;
} }
AsyncWebHandler& AsyncWebHandler::setAuthentication(const char* username, const char* password, AsyncAuthType authMethod) { AsyncWebHandler &AsyncWebHandler::setAuthentication(const char *username, const char *password, AsyncAuthType authMethod) {
if (!_authMiddleware) { if (!_authMiddleware) {
_authMiddleware = new AsyncAuthenticationMiddleware(); _authMiddleware = new AsyncAuthenticationMiddleware();
_authMiddleware->_freeOnRemoval = true; _authMiddleware->_freeOnRemoval = true;
@ -39,13 +22,15 @@ AsyncWebHandler& AsyncWebHandler::setAuthentication(const char* username, const
return *this; return *this;
}; };
AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control) AsyncStaticWebHandler::AsyncStaticWebHandler(const char *uri, FS &fs, const char *path, const char *cache_control)
: _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr) { : _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr) {
// Ensure leading '/' // Ensure leading '/'
if (_uri.length() == 0 || _uri[0] != '/') if (_uri.length() == 0 || _uri[0] != '/') {
_uri = String('/') + _uri; _uri = String('/') + _uri;
if (_path.length() == 0 || _path[0] != '/') }
if (_path.length() == 0 || _path[0] != '/') {
_path = String('/') + _path; _path = String('/') + _path;
}
// If path ends with '/' we assume a hint that this is a directory to improve performance. // If path ends with '/' we assume a hint that this is a directory to improve performance.
// However - if it does not end with '/' we, can't assume a file, path can still be a directory. // However - if it does not end with '/' we, can't assume a file, path can still be a directory.
@ -53,45 +38,47 @@ AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char
// Remove the trailing '/' so we can handle default file // Remove the trailing '/' so we can handle default file
// Notice that root will be "" not "/" // Notice that root will be "" not "/"
if (_uri[_uri.length() - 1] == '/') if (_uri[_uri.length() - 1] == '/') {
_uri = _uri.substring(0, _uri.length() - 1); _uri = _uri.substring(0, _uri.length() - 1);
if (_path[_path.length() - 1] == '/') }
if (_path[_path.length() - 1] == '/') {
_path = _path.substring(0, _path.length() - 1); _path = _path.substring(0, _path.length() - 1);
}
} }
AsyncStaticWebHandler& AsyncStaticWebHandler::setTryGzipFirst(bool value) { AsyncStaticWebHandler &AsyncStaticWebHandler::setTryGzipFirst(bool value) {
_tryGzipFirst = value; _tryGzipFirst = value;
return *this; return *this;
} }
AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir) { AsyncStaticWebHandler &AsyncStaticWebHandler::setIsDir(bool isDir) {
_isDir = isDir; _isDir = isDir;
return *this; return *this;
} }
AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename) { AsyncStaticWebHandler &AsyncStaticWebHandler::setDefaultFile(const char *filename) {
_default_file = filename; _default_file = filename;
return *this; return *this;
} }
AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control) { AsyncStaticWebHandler &AsyncStaticWebHandler::setCacheControl(const char *cache_control) {
_cache_control = cache_control; _cache_control = cache_control;
return *this; return *this;
} }
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified) { AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified(const char *last_modified) {
_last_modified = last_modified; _last_modified = last_modified;
return *this; return *this;
} }
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified) { AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified(struct tm *last_modified) {
char result[30]; char result[30];
#ifdef ESP8266 #ifdef ESP8266
auto formatP = PSTR("%a, %d %b %Y %H:%M:%S GMT"); auto formatP = PSTR("%a, %d %b %Y %H:%M:%S GMT");
char format[strlen_P(formatP) + 1]; char format[strlen_P(formatP) + 1];
strcpy_P(format, formatP); strcpy_P(format, formatP);
#else #else
static constexpr const char* format = "%a, %d %b %Y %H:%M:%S GMT"; static constexpr const char *format = "%a, %d %b %Y %H:%M:%S GMT";
#endif #endif
strftime(result, sizeof(result), format, last_modified); strftime(result, sizeof(result), format, last_modified);
@ -99,22 +86,23 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_mo
return *this; return *this;
} }
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified) { AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified(time_t last_modified) {
return setLastModified((struct tm*)gmtime(&last_modified)); return setLastModified((struct tm *)gmtime(&last_modified));
} }
AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified() { AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified() {
time_t last_modified; time_t last_modified;
if (time(&last_modified) == 0) // time is not yet set if (time(&last_modified) == 0) { // time is not yet set
return *this; return *this;
}
return setLastModified(last_modified); return setLastModified(last_modified);
} }
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest* request) const { bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request) const {
return request->isHTTP() && request->method() == HTTP_GET && request->url().startsWith(_uri) && _getFile(request); return request->isHTTP() && request->method() == HTTP_GET && request->url().startsWith(_uri) && _getFile(request);
} }
bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest* request) const { bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) const {
// Remove the found uri // Remove the found uri
String path = request->url().substring(_uri.length()); String path = request->url().substring(_uri.length());
@ -124,28 +112,31 @@ bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest* request) const {
path = _path + path; path = _path + path;
// Do we have a file or .gz file // Do we have a file or .gz file
if (!canSkipFileCheck && const_cast<AsyncStaticWebHandler*>(this)->_searchFile(request, path)) if (!canSkipFileCheck && const_cast<AsyncStaticWebHandler *>(this)->_searchFile(request, path)) {
return true; return true;
}
// Can't handle if not default file // Can't handle if not default file
if (_default_file.length() == 0) if (_default_file.length() == 0) {
return false; return false;
}
// Try to add default file, ensure there is a trailing '/' ot the path. // Try to add default file, ensure there is a trailing '/' to the path.
if (path.length() == 0 || path[path.length() - 1] != '/') if (path.length() == 0 || path[path.length() - 1] != '/') {
path += String('/'); path += String('/');
}
path += _default_file; path += _default_file;
return const_cast<AsyncStaticWebHandler*>(this)->_searchFile(request, path); return const_cast<AsyncStaticWebHandler *>(this)->_searchFile(request, path);
} }
#ifdef ESP32 #ifdef ESP32
#define FILE_IS_REAL(f) (f == true && !f.isDirectory()) #define FILE_IS_REAL(f) (f == true && !f.isDirectory())
#else #else
#define FILE_IS_REAL(f) (f == true) #define FILE_IS_REAL(f) (f == true)
#endif #endif
bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest* request, const String& path) { bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest *request, const String &path) {
bool fileFound = false; bool fileFound = false;
bool gzipFound = false; bool gzipFound = false;
@ -180,9 +171,17 @@ bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest* request, const St
if (found) { if (found) {
// Extract the file name from the path and keep it in _tempObject // Extract the file name from the path and keep it in _tempObject
size_t pathLen = path.length(); size_t pathLen = path.length();
char* _tempPath = (char*)malloc(pathLen + 1); char *_tempPath = (char *)malloc(pathLen + 1);
if (_tempPath == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
request->_tempFile.close();
return false;
}
snprintf_P(_tempPath, pathLen + 1, PSTR("%s"), path.c_str()); snprintf_P(_tempPath, pathLen + 1, PSTR("%s"), path.c_str());
request->_tempObject = (void*)_tempPath; request->_tempObject = (void *)_tempPath;
} }
return found; return found;
@ -191,18 +190,19 @@ bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest* request, const St
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const { uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const {
uint8_t w = value; uint8_t w = value;
uint8_t n; uint8_t n;
for (n = 0; w != 0; n++) for (n = 0; w != 0; n++) {
w &= w - 1; w &= w - 1;
}
return n; return n;
} }
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) { void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) {
// Get the filename from request->_tempObject and free it // Get the filename from request->_tempObject and free it
String filename((char*)request->_tempObject); String filename((char *)request->_tempObject);
free(request->_tempObject); free(request->_tempObject);
request->_tempObject = NULL; request->_tempObject = NULL;
if (request->_tempFile != true){ if (request->_tempFile != true) {
request->send(404); request->send(404);
return; return;
} }
@ -212,60 +212,75 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
String etag; String etag;
if (lw) { if (lw) {
setLastModified(lw); setLastModified(lw);
#if defined(TARGET_RP2040) #if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
// time_t == long long int // time_t == long long int
constexpr size_t len = 1 + 8 * sizeof(time_t); constexpr size_t len = 1 + 8 * sizeof(time_t);
char buf[len]; char buf[len];
char* ret = lltoa(lw ^ request->_tempFile.size(), buf, len, 10); char *ret = lltoa(lw ^ request->_tempFile.size(), buf, len, 10);
etag = ret ? String(ret) : String(request->_tempFile.size()); etag = ret ? String(ret) : String(request->_tempFile.size());
#else #else
etag = lw ^ request->_tempFile.size(); // etag combines file size and lastmod timestamp etag = lw ^ request->_tempFile.size(); // etag combines file size and lastmod timestamp
#endif #endif
} else { } else {
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
etag = String(request->_tempFile.size());
#else
etag = request->_tempFile.size(); etag = request->_tempFile.size();
#endif
} }
bool not_modified = false; bool not_modified = false;
// if-none-match has precedence over if-modified-since // if-none-match has precedence over if-modified-since
if (request->hasHeader(T_INM)) if (request->hasHeader(T_INM)) {
not_modified = request->header(T_INM).equals(etag); not_modified = request->header(T_INM).equals(etag);
else if (_last_modified.length()) } else if (_last_modified.length()) {
not_modified = request->header(T_IMS).equals(_last_modified); not_modified = request->header(T_IMS).equals(_last_modified);
}
AsyncWebServerResponse* response; AsyncWebServerResponse *response;
if (not_modified){ if (not_modified) {
request->_tempFile.close(); request->_tempFile.close();
response = new AsyncBasicResponse(304); // Not modified response = new AsyncBasicResponse(304); // Not modified
} else { } else {
response = new AsyncFileResponse(request->_tempFile, filename, emptyString, false, _callback); response = new AsyncFileResponse(request->_tempFile, filename, emptyString, false, _callback);
} }
if (!response) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
response->addHeader(T_ETag, etag.c_str()); response->addHeader(T_ETag, etag.c_str());
if (_last_modified.length()) if (_last_modified.length()) {
response->addHeader(T_Last_Modified, _last_modified.c_str()); response->addHeader(T_Last_Modified, _last_modified.c_str());
if (_cache_control.length()) }
if (_cache_control.length()) {
response->addHeader(T_Cache_Control, _cache_control.c_str()); response->addHeader(T_Cache_Control, _cache_control.c_str());
}
request->send(response); request->send(response);
} }
AsyncStaticWebHandler& AsyncStaticWebHandler::setTemplateProcessor(AwsTemplateProcessor newCallback) { AsyncStaticWebHandler &AsyncStaticWebHandler::setTemplateProcessor(AwsTemplateProcessor newCallback) {
_callback = newCallback; _callback = newCallback;
return *this; return *this;
} }
void AsyncCallbackWebHandler::setUri(const String& uri) { void AsyncCallbackWebHandler::setUri(const String &uri) {
_uri = uri; _uri = uri;
_isRegex = uri.startsWith("^") && uri.endsWith("$"); _isRegex = uri.startsWith("^") && uri.endsWith("$");
} }
bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest* request) const { bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest *request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
return false; return false;
}
#ifdef ASYNCWEBSERVER_REGEX #ifdef ASYNCWEBSERVER_REGEX
if (_isRegex) { if (_isRegex) {
@ -284,31 +299,37 @@ bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest* request) const {
if (_uri.length() && _uri.startsWith("/*.")) { if (_uri.length() && _uri.startsWith("/*.")) {
String uriTemplate = String(_uri); String uriTemplate = String(_uri);
uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf(".")); uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf("."));
if (!request->url().endsWith(uriTemplate)) if (!request->url().endsWith(uriTemplate)) {
return false; return false;
}
} else if (_uri.length() && _uri.endsWith("*")) { } else if (_uri.length() && _uri.endsWith("*")) {
String uriTemplate = String(_uri); String uriTemplate = String(_uri);
uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1); uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1);
if (!request->url().startsWith(uriTemplate)) if (!request->url().startsWith(uriTemplate)) {
return false; return false;
} else if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) }
} else if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
return false; return false;
}
return true; return true;
} }
void AsyncCallbackWebHandler::handleRequest(AsyncWebServerRequest* request) { void AsyncCallbackWebHandler::handleRequest(AsyncWebServerRequest *request) {
if (_onRequest) if (_onRequest) {
_onRequest(request); _onRequest(request);
else } else {
request->send(500); request->send(404, T_text_plain, "Not found");
}
} }
void AsyncCallbackWebHandler::handleUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) { void AsyncCallbackWebHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) {
if (_onUpload) if (_onUpload) {
_onUpload(request, filename, index, data, len, final); _onUpload(request, filename, index, data, len, final);
}
} }
void AsyncCallbackWebHandler::handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) { void AsyncCallbackWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
// ESP_LOGD("AsyncWebServer", "AsyncCallbackWebHandler::handleBody"); // ESP_LOGD("AsyncWebServer", "AsyncCallbackWebHandler::handleBody");
if (_onBody) if (_onBody) {
_onBody(request, data, len, index, total); _onBody(request, data, len, index, total);
}
} }

View file

@ -1,30 +1,13 @@
/* // SPDX-License-Identifier: LGPL-3.0-or-later
Asynchronous WebServer library for Espressif MCUs // Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_ #ifndef ASYNCWEBSERVERRESPONSEIMPL_H_
#define ASYNCWEBSERVERRESPONSEIMPL_H_ #define ASYNCWEBSERVERRESPONSEIMPL_H_
#ifdef Arduino_h #ifdef Arduino_h
// arduino is not compatible with std::vector // arduino is not compatible with std::vector
#undef min #undef min
#undef max #undef max
#endif #endif
#include "literals.h" #include "literals.h"
#include <StreamString.h> #include <StreamString.h>
@ -34,21 +17,24 @@
// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max. // It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max.
class AsyncBasicResponse : public AsyncWebServerResponse { class AsyncBasicResponse : public AsyncWebServerResponse {
private: private:
String _content; String _content;
public: public:
explicit AsyncBasicResponse(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty); explicit AsyncBasicResponse(int code, const char *contentType = asyncsrv::empty, const char *content = asyncsrv::empty);
AsyncBasicResponse(int code, const String& contentType, const String& content = emptyString) : AsyncBasicResponse(code, contentType.c_str(), content.c_str()) {} AsyncBasicResponse(int code, const String &contentType, const String &content = emptyString)
void _respond(AsyncWebServerRequest* request) override final; : AsyncBasicResponse(code, contentType.c_str(), content.c_str()) {}
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time) override final; void _respond(AsyncWebServerRequest *request) override final;
bool _sourceValid() const override final { return true; } size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override final;
bool _sourceValid() const override final {
return true;
}
}; };
class AsyncAbstractResponse : public AsyncWebServerResponse { class AsyncAbstractResponse : public AsyncWebServerResponse {
private: private:
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT #if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
// amount of responce data in-flight, i.e. sent, but not acked yet // amount of response data in-flight, i.e. sent, but not acked yet
size_t _in_flight{0}; size_t _in_flight{0};
// in-flight queue credits // in-flight queue credits
size_t _in_flight_credit{2}; size_t _in_flight_credit{2};
@ -59,23 +45,27 @@ class AsyncAbstractResponse : public AsyncWebServerResponse {
// we won't be able to access it as contiguous array of bytes when reading from it, // we won't be able to access it as contiguous array of bytes when reading from it,
// so by gaining performance in one place, we'll lose it in another. // so by gaining performance in one place, we'll lose it in another.
std::vector<uint8_t> _cache; std::vector<uint8_t> _cache;
size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len); size_t _readDataFromCacheOrContent(uint8_t *data, const size_t len);
size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen); size_t _fillBufferAndProcessTemplates(uint8_t *buf, size_t maxLen);
protected: protected:
AwsTemplateProcessor _callback; AwsTemplateProcessor _callback;
public: public:
AsyncAbstractResponse(AwsTemplateProcessor callback = nullptr); AsyncAbstractResponse(AwsTemplateProcessor callback = nullptr);
virtual ~AsyncAbstractResponse() {} virtual ~AsyncAbstractResponse() {}
void _respond(AsyncWebServerRequest* request) override final; void _respond(AsyncWebServerRequest *request) override final;
size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time) override final; size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override final;
virtual bool _sourceValid() const { return false; } virtual bool _sourceValid() const {
virtual size_t _fillBuffer(uint8_t* buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; } return false;
}
virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) {
return 0;
}
}; };
#ifndef TEMPLATE_PLACEHOLDER #ifndef TEMPLATE_PLACEHOLDER
#define TEMPLATE_PLACEHOLDER '%' #define TEMPLATE_PLACEHOLDER '%'
#endif #endif
#define TEMPLATE_PARAM_NAME_LENGTH 32 #define TEMPLATE_PARAM_NAME_LENGTH 32
@ -83,78 +73,100 @@ class AsyncFileResponse : public AsyncAbstractResponse {
using File = fs::File; using File = fs::File;
using FS = fs::FS; using FS = fs::FS;
private: private:
File _content; File _content;
String _path; String _path;
void _setContentTypeFromPath(const String& path); void _setContentTypeFromPath(const String &path);
public: public:
AsyncFileResponse(FS& fs, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr); AsyncFileResponse(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncFileResponse(FS& fs, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callback = nullptr) : AsyncFileResponse(fs, path, contentType.c_str(), download, callback) {} AsyncFileResponse(FS &fs, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr)
AsyncFileResponse(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr); : AsyncFileResponse(fs, path, contentType.c_str(), download, callback) {}
AsyncFileResponse(File content, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callack = nullptr) : AsyncFileResponse(content, path, contentType.c_str(), download, callack) {} AsyncFileResponse(
~AsyncFileResponse() { _content.close(); } File content, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr
bool _sourceValid() const override final { return !!(_content); } );
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final; AsyncFileResponse(File content, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr)
: AsyncFileResponse(content, path, contentType.c_str(), download, callback) {}
~AsyncFileResponse() {
_content.close();
}
bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
}; };
class AsyncStreamResponse : public AsyncAbstractResponse { class AsyncStreamResponse : public AsyncAbstractResponse {
private: private:
Stream* _content; Stream *_content;
public: public:
AsyncStreamResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback = nullptr); AsyncStreamResponse(Stream &stream, const char *contentType, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncStreamResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) : AsyncStreamResponse(stream, contentType.c_str(), len, callback) {} AsyncStreamResponse(Stream &stream, const String &contentType, size_t len, AwsTemplateProcessor callback = nullptr)
bool _sourceValid() const override final { return !!(_content); } : AsyncStreamResponse(stream, contentType.c_str(), len, callback) {}
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final; bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
}; };
class AsyncCallbackResponse : public AsyncAbstractResponse { class AsyncCallbackResponse : public AsyncAbstractResponse {
private: private:
AwsResponseFiller _content; AwsResponseFiller _content;
size_t _filledLength; size_t _filledLength;
public: public:
AsyncCallbackResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); AsyncCallbackResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) : AsyncCallbackResponse(contentType.c_str(), len, callback, templateCallback) {} AsyncCallbackResponse(const String &contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr)
bool _sourceValid() const override final { return !!(_content); } : AsyncCallbackResponse(contentType.c_str(), len, callback, templateCallback) {}
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final; bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
}; };
class AsyncChunkedResponse : public AsyncAbstractResponse { class AsyncChunkedResponse : public AsyncAbstractResponse {
private: private:
AwsResponseFiller _content; AwsResponseFiller _content;
size_t _filledLength; size_t _filledLength;
public: public:
AsyncChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); AsyncChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) : AsyncChunkedResponse(contentType.c_str(), callback, templateCallback) {} AsyncChunkedResponse(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr)
bool _sourceValid() const override final { return !!(_content); } : AsyncChunkedResponse(contentType.c_str(), callback, templateCallback) {}
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final; bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
}; };
class AsyncProgmemResponse : public AsyncAbstractResponse { class AsyncProgmemResponse : public AsyncAbstractResponse {
private: private:
const uint8_t* _content; const uint8_t *_content;
size_t _readLength; size_t _readLength;
public: public:
AsyncProgmemResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr); AsyncProgmemResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncProgmemResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) : AsyncProgmemResponse(code, contentType.c_str(), content, len, callback) {} AsyncProgmemResponse(int code, const String &contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr)
bool _sourceValid() const override final { return true; } : AsyncProgmemResponse(code, contentType.c_str(), content, len, callback) {}
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final; bool _sourceValid() const override final {
return true;
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
}; };
class AsyncResponseStream : public AsyncAbstractResponse, public Print { class AsyncResponseStream : public AsyncAbstractResponse, public Print {
private: private:
StreamString _content; StreamString _content;
public: public:
AsyncResponseStream(const char* contentType, size_t bufferSize); AsyncResponseStream(const char *contentType, size_t bufferSize);
AsyncResponseStream(const String& contentType, size_t bufferSize) : AsyncResponseStream(contentType.c_str(), bufferSize) {} AsyncResponseStream(const String &contentType, size_t bufferSize) : AsyncResponseStream(contentType.c_str(), bufferSize) {}
bool _sourceValid() const override final { return (_state < RESPONSE_END); } bool _sourceValid() const override final {
size_t _fillBuffer(uint8_t* buf, size_t maxLen) override final; return (_state < RESPONSE_END);
size_t write(const uint8_t* data, size_t len); }
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); size_t write(uint8_t data);
using Print::write; using Print::write;
}; };

View file

@ -1,34 +1,19 @@
/* // SPDX-License-Identifier: LGPL-3.0-or-later
Asynchronous WebServer library for Espressif MCUs // Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ESPAsyncWebServer.h" #include "ESPAsyncWebServer.h"
#include "WebResponseImpl.h" #include "WebResponseImpl.h"
using namespace asyncsrv; using namespace asyncsrv;
// Since ESP8266 does not link memchr by default, here's its implementation. // Since ESP8266 does not link memchr by default, here's its implementation.
void* memchr(void* ptr, int ch, size_t count) { void *memchr(void *ptr, int ch, size_t count) {
unsigned char* p = static_cast<unsigned char*>(ptr); unsigned char *p = static_cast<unsigned char *>(ptr);
while (count--) while (count--) {
if (*p++ == static_cast<unsigned char>(ch)) if (*p++ == static_cast<unsigned char>(ch)) {
return --p; return --p;
}
}
return nullptr; return nullptr;
} }
@ -37,120 +22,95 @@ void* memchr(void* ptr, int ch, size_t count) {
* *
*/ */
const char* AsyncWebServerResponse::responseCodeToString(int code) { const char *AsyncWebServerResponse::responseCodeToString(int code) {
switch (code) { switch (code) {
case 100: case 100: return T_HTTP_CODE_100;
return T_HTTP_CODE_100; case 101: return T_HTTP_CODE_101;
case 101: case 200: return T_HTTP_CODE_200;
return T_HTTP_CODE_101; case 201: return T_HTTP_CODE_201;
case 200: case 202: return T_HTTP_CODE_202;
return T_HTTP_CODE_200; case 203: return T_HTTP_CODE_203;
case 201: case 204: return T_HTTP_CODE_204;
return T_HTTP_CODE_201; case 205: return T_HTTP_CODE_205;
case 202: case 206: return T_HTTP_CODE_206;
return T_HTTP_CODE_202; case 300: return T_HTTP_CODE_300;
case 203: case 301: return T_HTTP_CODE_301;
return T_HTTP_CODE_203; case 302: return T_HTTP_CODE_302;
case 204: case 303: return T_HTTP_CODE_303;
return T_HTTP_CODE_204; case 304: return T_HTTP_CODE_304;
case 205: case 305: return T_HTTP_CODE_305;
return T_HTTP_CODE_205; case 307: return T_HTTP_CODE_307;
case 206: case 400: return T_HTTP_CODE_400;
return T_HTTP_CODE_206; case 401: return T_HTTP_CODE_401;
case 300: case 402: return T_HTTP_CODE_402;
return T_HTTP_CODE_300; case 403: return T_HTTP_CODE_403;
case 301: case 404: return T_HTTP_CODE_404;
return T_HTTP_CODE_301; case 405: return T_HTTP_CODE_405;
case 302: case 406: return T_HTTP_CODE_406;
return T_HTTP_CODE_302; case 407: return T_HTTP_CODE_407;
case 303: case 408: return T_HTTP_CODE_408;
return T_HTTP_CODE_303; case 409: return T_HTTP_CODE_409;
case 304: case 410: return T_HTTP_CODE_410;
return T_HTTP_CODE_304; case 411: return T_HTTP_CODE_411;
case 305: case 412: return T_HTTP_CODE_412;
return T_HTTP_CODE_305; case 413: return T_HTTP_CODE_413;
case 307: case 414: return T_HTTP_CODE_414;
return T_HTTP_CODE_307; case 415: return T_HTTP_CODE_415;
case 400: case 416: return T_HTTP_CODE_416;
return T_HTTP_CODE_400; case 417: return T_HTTP_CODE_417;
case 401: case 429: return T_HTTP_CODE_429;
return T_HTTP_CODE_401; case 500: return T_HTTP_CODE_500;
case 402: case 501: return T_HTTP_CODE_501;
return T_HTTP_CODE_402; case 502: return T_HTTP_CODE_502;
case 403: case 503: return T_HTTP_CODE_503;
return T_HTTP_CODE_403; case 504: return T_HTTP_CODE_504;
case 404: case 505: return T_HTTP_CODE_505;
return T_HTTP_CODE_404; default: return T_HTTP_CODE_ANY;
case 405:
return T_HTTP_CODE_405;
case 406:
return T_HTTP_CODE_406;
case 407:
return T_HTTP_CODE_407;
case 408:
return T_HTTP_CODE_408;
case 409:
return T_HTTP_CODE_409;
case 410:
return T_HTTP_CODE_410;
case 411:
return T_HTTP_CODE_411;
case 412:
return T_HTTP_CODE_412;
case 413:
return T_HTTP_CODE_413;
case 414:
return T_HTTP_CODE_414;
case 415:
return T_HTTP_CODE_415;
case 416:
return T_HTTP_CODE_416;
case 417:
return T_HTTP_CODE_417;
case 429:
return T_HTTP_CODE_429;
case 500:
return T_HTTP_CODE_500;
case 501:
return T_HTTP_CODE_501;
case 502:
return T_HTTP_CODE_502;
case 503:
return T_HTTP_CODE_503;
case 504:
return T_HTTP_CODE_504;
case 505:
return T_HTTP_CODE_505;
default:
return T_HTTP_CODE_ANY;
} }
} }
AsyncWebServerResponse::AsyncWebServerResponse() AsyncWebServerResponse::AsyncWebServerResponse()
: _code(0), _contentType(), _contentLength(0), _sendContentLength(true), _chunked(false), _headLength(0), _sentLength(0), _ackedLength(0), _writtenLength(0), _state(RESPONSE_SETUP) { : _code(0), _contentType(), _contentLength(0), _sendContentLength(true), _chunked(false), _headLength(0), _sentLength(0), _ackedLength(0), _writtenLength(0),
for (const auto& header : DefaultHeaders::Instance()) { _state(RESPONSE_SETUP) {
for (const auto &header : DefaultHeaders::Instance()) {
_headers.emplace_back(header); _headers.emplace_back(header);
} }
} }
void AsyncWebServerResponse::setCode(int code) { void AsyncWebServerResponse::setCode(int code) {
if (_state == RESPONSE_SETUP) if (_state == RESPONSE_SETUP) {
_code = code; _code = code;
}
} }
void AsyncWebServerResponse::setContentLength(size_t len) { void AsyncWebServerResponse::setContentLength(size_t len) {
if (_state == RESPONSE_SETUP && addHeader(T_Content_Length, len, true)) if (_state == RESPONSE_SETUP && addHeader(T_Content_Length, len, true)) {
_contentLength = len; _contentLength = len;
}
} }
void AsyncWebServerResponse::setContentType(const char* type) { void AsyncWebServerResponse::setContentType(const char *type) {
if (_state == RESPONSE_SETUP && addHeader(T_Content_Type, type, true)) if (_state == RESPONSE_SETUP && addHeader(T_Content_Type, type, true)) {
_contentType = type; _contentType = type;
}
} }
bool AsyncWebServerResponse::removeHeader(const char* name) { bool AsyncWebServerResponse::removeHeader(const char *name) {
for (auto i = _headers.begin(); i != _headers.end(); ++i) { bool h_erased = false;
for (auto i = _headers.begin(); i != _headers.end();) {
if (i->name().equalsIgnoreCase(name)) { if (i->name().equalsIgnoreCase(name)) {
_headers.erase(i);
h_erased = true;
} else {
++i;
}
}
return h_erased;
}
bool AsyncWebServerResponse::removeHeader(const char *name, const char *value) {
for (auto i = _headers.begin(); i != _headers.end(); ++i) {
if (i->name().equalsIgnoreCase(name) && i->value().equalsIgnoreCase(value)) {
_headers.erase(i); _headers.erase(i);
return true; return true;
} }
@ -158,12 +118,23 @@ bool AsyncWebServerResponse::removeHeader(const char* name) {
return false; return false;
} }
const AsyncWebHeader* AsyncWebServerResponse::getHeader(const char* name) const { const AsyncWebHeader *AsyncWebServerResponse::getHeader(const char *name) const {
auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader& header) { return header.name().equalsIgnoreCase(name); }); auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader &header) {
return header.name().equalsIgnoreCase(name);
});
return (iter == std::end(_headers)) ? nullptr : &(*iter); return (iter == std::end(_headers)) ? nullptr : &(*iter);
} }
bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool replaceExisting) { bool AsyncWebServerResponse::headerMustBePresentOnce(const String &name) {
for (uint8_t i = 0; i < T_only_once_headers_len; i++) {
if (name.equalsIgnoreCase(T_only_once_headers[i])) {
return true;
}
}
return false;
}
bool AsyncWebServerResponse::addHeader(const char *name, const char *value, bool replaceExisting) {
for (auto i = _headers.begin(); i != _headers.end(); ++i) { for (auto i = _headers.begin(); i != _headers.end(); ++i) {
if (i->name().equalsIgnoreCase(name)) { if (i->name().equalsIgnoreCase(name)) {
// header already set // header already set
@ -171,9 +142,11 @@ bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool
// remove, break and add the new one // remove, break and add the new one
_headers.erase(i); _headers.erase(i);
break; break;
} else { } else if (headerMustBePresentOnce(i->name())) { // we can have only one header with that name
// do not update // do not update
return false; return false;
} else {
break; // accept multiple headers with the same name
} }
} }
} }
@ -182,24 +155,28 @@ bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool
return true; return true;
} }
void AsyncWebServerResponse::_assembleHead(String& buffer, uint8_t version) { void AsyncWebServerResponse::_assembleHead(String &buffer, uint8_t version) {
if (version) { if (version) {
addHeader(T_Accept_Ranges, T_none, false); addHeader(T_Accept_Ranges, T_none, false);
if (_chunked) if (_chunked) {
addHeader(T_Transfer_Encoding, T_chunked, false); addHeader(T_Transfer_Encoding, T_chunked, false);
} }
}
if (_sendContentLength) if (_sendContentLength) {
addHeader(T_Content_Length, String(_contentLength), false); addHeader(T_Content_Length, String(_contentLength), false);
}
if (_contentType.length()) if (_contentType.length()) {
addHeader(T_Content_Type, _contentType.c_str(), false); addHeader(T_Content_Type, _contentType.c_str(), false);
}
// precompute buffer size to avoid reallocations by String class // precompute buffer size to avoid reallocations by String class
size_t len = 0; size_t len = 0;
len += 50; // HTTP/1.1 200 <reason>\r\n len += 50; // HTTP/1.1 200 <reason>\r\n
for (const auto& header : _headers) for (const auto &header : _headers) {
len += header.name().length() + header.value().length() + 4; len += header.name().length() + header.value().length() + 4;
}
// prepare buffer // prepare buffer
buffer.reserve(len); buffer.reserve(len);
@ -218,7 +195,7 @@ void AsyncWebServerResponse::_assembleHead(String& buffer, uint8_t version) {
buffer.concat(T_rn); buffer.concat(T_rn);
// Add headers // Add headers
for (const auto& header : _headers) { for (const auto &header : _headers) {
buffer.concat(header.name()); buffer.concat(header.name());
#ifdef ESP8266 #ifdef ESP8266
buffer.concat(PSTR(": ")); buffer.concat(PSTR(": "));
@ -233,15 +210,23 @@ void AsyncWebServerResponse::_assembleHead(String& buffer, uint8_t version) {
_headLength = buffer.length(); _headLength = buffer.length();
} }
bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; } bool AsyncWebServerResponse::_started() const {
bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; } return _state > RESPONSE_SETUP;
bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; } }
bool AsyncWebServerResponse::_sourceValid() const { return false; } bool AsyncWebServerResponse::_finished() const {
void AsyncWebServerResponse::_respond(AsyncWebServerRequest* request) { return _state > RESPONSE_WAIT_ACK;
}
bool AsyncWebServerResponse::_failed() const {
return _state == RESPONSE_FAILED;
}
bool AsyncWebServerResponse::_sourceValid() const {
return false;
}
void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request) {
_state = RESPONSE_END; _state = RESPONSE_END;
request->client()->close(); request->client()->close();
} }
size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) { size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
(void)request; (void)request;
(void)len; (void)len;
(void)time; (void)time;
@ -251,19 +236,20 @@ size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest* request, size_t len,
/* /*
* String/Code Response * String/Code Response
* */ * */
AsyncBasicResponse::AsyncBasicResponse(int code, const char* contentType, const char* content) { AsyncBasicResponse::AsyncBasicResponse(int code, const char *contentType, const char *content) {
_code = code; _code = code;
_content = content; _content = content;
_contentType = contentType; _contentType = contentType;
if (_content.length()) { if (_content.length()) {
_contentLength = _content.length(); _contentLength = _content.length();
if (!_contentType.length()) if (!_contentType.length()) {
_contentType = T_text_plain; _contentType = T_text_plain;
} }
}
addHeader(T_Connection, T_close, false); addHeader(T_Connection, T_close, false);
} }
void AsyncBasicResponse::_respond(AsyncWebServerRequest* request) { void AsyncBasicResponse::_respond(AsyncWebServerRequest *request) {
_state = RESPONSE_HEADERS; _state = RESPONSE_HEADERS;
String out; String out;
_assembleHead(out, request->version()); _assembleHead(out, request->version());
@ -298,7 +284,7 @@ void AsyncBasicResponse::_respond(AsyncWebServerRequest* request) {
} }
} }
size_t AsyncBasicResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) { size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
(void)time; (void)time;
_ackedLength += len; _ackedLength += len;
if (_state == RESPONSE_CONTENT) { if (_state == RESPONSE_CONTENT) {
@ -338,14 +324,14 @@ AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) : _c
} }
} }
void AsyncAbstractResponse::_respond(AsyncWebServerRequest* request) { void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request) {
addHeader(T_Connection, T_close, false); addHeader(T_Connection, T_close, false);
_assembleHead(_head, request->version()); _assembleHead(_head, request->version());
_state = RESPONSE_HEADERS; _state = RESPONSE_HEADERS;
_ack(request, 0, 0); _ack(request, 0, 0);
} }
size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) { size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
(void)time; (void)time;
if (!_sourceValid()) { if (!_sourceValid()) {
_state = RESPONSE_FAILED; _state = RESPONSE_FAILED;
@ -355,14 +341,15 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT #if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
// return a credit for each chunk of acked data (polls does not give any credits) // return a credit for each chunk of acked data (polls does not give any credits)
if (len) if (len) {
++_in_flight_credit; ++_in_flight_credit;
}
// for chunked responses ignore acks if there are no _in_flight_credits left // for chunked responses ignore acks if there are no _in_flight_credits left
if (_chunked && !_in_flight_credit) { if (_chunked && !_in_flight_credit) {
#ifdef ESP32 #ifdef ESP32
log_d("(chunk) out of in-flight credits"); log_d("(chunk) out of in-flight credits");
#endif #endif
return 0; return 0;
} }
@ -399,8 +386,9 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
if (_in_flight > space) { if (_in_flight > space) {
// log_d("defer user call %u/%u", _in_flight, space); // log_d("defer user call %u/%u", _in_flight, space);
// take the credit back since we are ignoring this ack and rely on other inflight data // take the credit back since we are ignoring this ack and rely on other inflight data
if (len) if (len) {
--_in_flight_credit; --_in_flight_credit;
}
return 0; return 0;
} }
#endif #endif
@ -418,9 +406,12 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength); outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength);
} }
uint8_t* buf = (uint8_t*)malloc(outLen + headLen); uint8_t *buf = (uint8_t *)malloc(outLen + headLen);
if (!buf) { if (!buf) {
// os_printf("_ack malloc %d failed\n", outLen+headLen); #ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return 0; return 0;
} }
@ -438,7 +429,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
free(buf); free(buf);
return 0; return 0;
} }
outLen = sprintf((char*)buf + headLen, "%04x", readLen) + headLen; outLen = sprintf((char *)buf + headLen, "%04x", readLen) + headLen;
buf[outLen++] = '\r'; buf[outLen++] = '\r';
buf[outLen++] = '\n'; buf[outLen++] = '\n';
outLen += readLen; outLen += readLen;
@ -458,7 +449,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
} }
if (outLen) { if (outLen) {
_writtenLength += request->client()->write((const char*)buf, outLen); _writtenLength += request->client()->write((const char *)buf, outLen);
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT #if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
_in_flight += outLen; _in_flight += outLen;
--_in_flight_credit; // take a credit --_in_flight_credit; // take a credit
@ -481,14 +472,15 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
} else if (_state == RESPONSE_WAIT_ACK) { } else if (_state == RESPONSE_WAIT_ACK) {
if (!_sendContentLength || _ackedLength >= _writtenLength) { if (!_sendContentLength || _ackedLength >= _writtenLength) {
_state = RESPONSE_END; _state = RESPONSE_END;
if (!_chunked && !_sendContentLength) if (!_chunked && !_sendContentLength) {
request->client()->close(true); request->client()->close(true);
} }
} }
}
return 0; return 0;
} }
size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) { size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t *data, const size_t len) {
// If we have something in cache, copy it to buffer // If we have something in cache, copy it to buffer
const size_t readFromCache = std::min(len, _cache.size()); const size_t readFromCache = std::min(len, _cache.size());
if (readFromCache) { if (readFromCache) {
@ -501,17 +493,20 @@ size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const s
return readFromCache + readFromContent; return readFromCache + readFromContent;
} }
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) { size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t *data, size_t len) {
if (!_callback) if (!_callback) {
return _fillBuffer(data, len); return _fillBuffer(data, len);
}
const size_t originalLen = len; const size_t originalLen = len;
len = _readDataFromCacheOrContent(data, len); len = _readDataFromCacheOrContent(data, len);
// Now we've read 'len' bytes, either from cache or from file // Now we've read 'len' bytes, either from cache or from file
// Search for template placeholders // Search for template placeholders
uint8_t* pTemplateStart = data; uint8_t *pTemplateStart = data;
while ((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1] while ((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t *)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))
uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr; ) { // data[0] ... data[len - 1]
uint8_t *pTemplateEnd =
(pTemplateStart < &data[len - 1]) ? (uint8_t *)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
// temporary buffer to hold parameter name // temporary buffer to hold parameter name
uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
String paramName; String paramName;
@ -522,22 +517,24 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
if (paramNameLength) { if (paramNameLength) {
memcpy(buf, pTemplateStart + 1, paramNameLength); memcpy(buf, pTemplateStart + 1, paramNameLength);
buf[paramNameLength] = 0; buf[paramNameLength] = 0;
paramName = String(reinterpret_cast<char*>(buf)); paramName = String(reinterpret_cast<char *>(buf));
} else { // double percent sign encountered, this is single percent sign escaped. } else { // double percent sign encountered, this is single percent sign escaped.
// remove the 2nd percent sign // remove the 2nd percent sign
memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1;
++pTemplateStart; ++pTemplateStart;
} }
} else if (&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data } else if (&data[len - 1] - pTemplateStart + 1
< TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data
memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart); memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart);
const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1)); const size_t readFromCacheOrContent =
_readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1));
if (readFromCacheOrContent) { if (readFromCacheOrContent) {
pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); pTemplateEnd = (uint8_t *)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
if (pTemplateEnd) { if (pTemplateEnd) {
// prepare argument to callback // prepare argument to callback
*pTemplateEnd = 0; *pTemplateEnd = 0;
paramName = String(reinterpret_cast<char*>(buf)); paramName = String(reinterpret_cast<char *>(buf));
// Copy remaining read-ahead data into cache // Copy remaining read-ahead data into cache
_cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
pTemplateEnd = &data[len - 1]; pTemplateEnd = &data[len - 1];
@ -547,10 +544,12 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
_cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
++pTemplateStart; ++pTemplateStart;
} }
} else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position } else { // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
++pTemplateStart; ++pTemplateStart;
} else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position }
} else { // closing placeholder not found in content data, store found percent symbol as is and advance to the next position
++pTemplateStart; ++pTemplateStart;
}
if (paramName.length()) { if (paramName.length()) {
// call callback and replace with result. // call callback and replace with result.
// Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value.
@ -558,7 +557,7 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
// The first byte of data after placeholder is located at pTemplateEnd + 1. // The first byte of data after placeholder is located at pTemplateEnd + 1.
// It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value). // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value).
const String paramValue(_callback(paramName)); const String paramValue(_callback(paramName));
const char* pvstr = paramValue.c_str(); const char *pvstr = paramValue.c_str();
const unsigned int pvlen = paramValue.length(); const unsigned int pvlen = paramValue.length();
const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1)); const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1));
// make room for param value // make room for param value
@ -568,10 +567,11 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
// 2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end // 2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied); memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied);
len = originalLen; // fix issue with truncated data, not sure if it has any side effects len = originalLen; // fix issue with truncated data, not sure if it has any side effects
} else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied) } else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied) {
// 2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit. // 2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit.
// Move the entire data after the placeholder // Move the entire data after the placeholder
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
}
// 3. replace placeholder with actual value // 3. replace placeholder with actual value
memcpy(pTemplateStart, pvstr, numBytesCopied); memcpy(pTemplateStart, pvstr, numBytesCopied);
// If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer) // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer)
@ -595,57 +595,59 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
* File Response * File Response
* */ * */
void AsyncFileResponse::_setContentTypeFromPath(const String& path) { void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
#if HAVE_EXTERN_GET_Content_Type_FUNCTION #if HAVE_EXTERN_GET_Content_Type_FUNCTION
#ifndef ESP8266 #ifndef ESP8266
extern const char* getContentType(const String& path); extern const char *getContentType(const String &path);
#else #else
extern const __FlashStringHelper* getContentType(const String& path); extern const __FlashStringHelper *getContentType(const String &path);
#endif #endif
_contentType = getContentType(path); _contentType = getContentType(path);
#else #else
if (path.endsWith(T__html)) if (path.endsWith(T__html)) {
_contentType = T_text_html; _contentType = T_text_html;
else if (path.endsWith(T__htm)) } else if (path.endsWith(T__htm)) {
_contentType = T_text_html; _contentType = T_text_html;
else if (path.endsWith(T__css)) } else if (path.endsWith(T__css)) {
_contentType = T_text_css; _contentType = T_text_css;
else if (path.endsWith(T__json)) } else if (path.endsWith(T__json)) {
_contentType = T_application_json; _contentType = T_application_json;
else if (path.endsWith(T__js)) } else if (path.endsWith(T__js)) {
_contentType = T_application_javascript; _contentType = T_application_javascript;
else if (path.endsWith(T__png)) } else if (path.endsWith(T__png)) {
_contentType = T_image_png; _contentType = T_image_png;
else if (path.endsWith(T__gif)) } else if (path.endsWith(T__gif)) {
_contentType = T_image_gif; _contentType = T_image_gif;
else if (path.endsWith(T__jpg)) } else if (path.endsWith(T__jpg)) {
_contentType = T_image_jpeg; _contentType = T_image_jpeg;
else if (path.endsWith(T__ico)) } else if (path.endsWith(T__ico)) {
_contentType = T_image_x_icon; _contentType = T_image_x_icon;
else if (path.endsWith(T__svg)) } else if (path.endsWith(T__svg)) {
_contentType = T_image_svg_xml; _contentType = T_image_svg_xml;
else if (path.endsWith(T__eot)) } else if (path.endsWith(T__eot)) {
_contentType = T_font_eot; _contentType = T_font_eot;
else if (path.endsWith(T__woff)) } else if (path.endsWith(T__woff)) {
_contentType = T_font_woff; _contentType = T_font_woff;
else if (path.endsWith(T__woff2)) } else if (path.endsWith(T__woff2)) {
_contentType = T_font_woff2; _contentType = T_font_woff2;
else if (path.endsWith(T__ttf)) } else if (path.endsWith(T__ttf)) {
_contentType = T_font_ttf; _contentType = T_font_ttf;
else if (path.endsWith(T__xml)) } else if (path.endsWith(T__xml)) {
_contentType = T_text_xml; _contentType = T_text_xml;
else if (path.endsWith(T__pdf)) } else if (path.endsWith(T__pdf)) {
_contentType = T_application_pdf; _contentType = T_application_pdf;
else if (path.endsWith(T__zip)) } else if (path.endsWith(T__zip)) {
_contentType = T_application_zip; _contentType = T_application_zip;
else if (path.endsWith(T__gz)) } else if (path.endsWith(T__gz)) {
_contentType = T_application_x_gzip; _contentType = T_application_x_gzip;
else } else {
_contentType = T_text_plain; _contentType = T_text_plain;
}
#endif #endif
} }
AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) { AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = 200; _code = 200;
_path = path; _path = path;
@ -660,14 +662,15 @@ AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* con
_content = fs.open(_path, fs::FileOpenMode::read); _content = fs.open(_path, fs::FileOpenMode::read);
_contentLength = _content.size(); _contentLength = _content.size();
if (strlen(contentType) == 0) if (strlen(contentType) == 0) {
_setContentTypeFromPath(path); _setContentTypeFromPath(path);
else } else {
_contentType = contentType; _contentType = contentType;
}
int filenameStart = path.lastIndexOf('/') + 1; int filenameStart = path.lastIndexOf('/') + 1;
char buf[26 + path.length() - filenameStart]; char buf[26 + path.length() - filenameStart];
char* filename = (char*)path.c_str() + filenameStart; char *filename = (char *)path.c_str() + filenameStart;
if (download) { if (download) {
// set filename and force download // set filename and force download
@ -679,7 +682,8 @@ AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* con
addHeader(T_Content_Disposition, buf, false); addHeader(T_Content_Disposition, buf, false);
} }
AsyncFileResponse::AsyncFileResponse(File content, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) { AsyncFileResponse::AsyncFileResponse(File content, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = 200; _code = 200;
_path = path; _path = path;
@ -693,14 +697,15 @@ AsyncFileResponse::AsyncFileResponse(File content, const String& path, const cha
_content = content; _content = content;
_contentLength = _content.size(); _contentLength = _content.size();
if (strlen(contentType) == 0) if (strlen(contentType) == 0) {
_setContentTypeFromPath(path); _setContentTypeFromPath(path);
else } else {
_contentType = contentType; _contentType = contentType;
}
int filenameStart = path.lastIndexOf('/') + 1; int filenameStart = path.lastIndexOf('/') + 1;
char buf[26 + path.length() - filenameStart]; char buf[26 + path.length() - filenameStart];
char* filename = (char*)path.c_str() + filenameStart; char *filename = (char *)path.c_str() + filenameStart;
if (download) { if (download) {
snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename); snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
@ -710,7 +715,7 @@ AsyncFileResponse::AsyncFileResponse(File content, const String& path, const cha
addHeader(T_Content_Disposition, buf, false); addHeader(T_Content_Disposition, buf, false);
} }
size_t AsyncFileResponse::_fillBuffer(uint8_t* data, size_t len) { size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len) {
return _content.read(data, len); return _content.read(data, len);
} }
@ -718,19 +723,20 @@ size_t AsyncFileResponse::_fillBuffer(uint8_t* data, size_t len) {
* Stream Response * Stream Response
* */ * */
AsyncStreamResponse::AsyncStreamResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) { AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const char *contentType, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
_code = 200; _code = 200;
_content = &stream; _content = &stream;
_contentLength = len; _contentLength = len;
_contentType = contentType; _contentType = contentType;
} }
size_t AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) { size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len) {
size_t available = _content->available(); size_t available = _content->available();
size_t outLen = (available > len) ? len : available; size_t outLen = (available > len) ? len : available;
size_t i; size_t i;
for (i = 0; i < outLen; i++) for (i = 0; i < outLen; i++) {
data[i] = _content->read(); data[i] = _content->read();
}
return outLen; return outLen;
} }
@ -738,17 +744,19 @@ size_t AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) {
* Callback Response * Callback Response
* */ * */
AsyncCallbackResponse::AsyncCallbackResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) : AsyncAbstractResponse(templateCallback) { AsyncCallbackResponse::AsyncCallbackResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback)
: AsyncAbstractResponse(templateCallback) {
_code = 200; _code = 200;
_content = callback; _content = callback;
_contentLength = len; _contentLength = len;
if (!len) if (!len) {
_sendContentLength = false; _sendContentLength = false;
}
_contentType = contentType; _contentType = contentType;
_filledLength = 0; _filledLength = 0;
} }
size_t AsyncCallbackResponse::_fillBuffer(uint8_t* data, size_t len) { size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len) {
size_t ret = _content(data, len, _filledLength); size_t ret = _content(data, len, _filledLength);
if (ret != RESPONSE_TRY_AGAIN) { if (ret != RESPONSE_TRY_AGAIN) {
_filledLength += ret; _filledLength += ret;
@ -760,7 +768,8 @@ size_t AsyncCallbackResponse::_fillBuffer(uint8_t* data, size_t len) {
* Chunked Response * Chunked Response
* */ * */
AsyncChunkedResponse::AsyncChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback) : AsyncAbstractResponse(processorCallback) { AsyncChunkedResponse::AsyncChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback)
: AsyncAbstractResponse(processorCallback) {
_code = 200; _code = 200;
_content = callback; _content = callback;
_contentLength = 0; _contentLength = 0;
@ -770,7 +779,7 @@ AsyncChunkedResponse::AsyncChunkedResponse(const char* contentType, AwsResponseF
_filledLength = 0; _filledLength = 0;
} }
size_t AsyncChunkedResponse::_fillBuffer(uint8_t* data, size_t len) { size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len) {
size_t ret = _content(data, len, _filledLength); size_t ret = _content(data, len, _filledLength);
if (ret != RESPONSE_TRY_AGAIN) { if (ret != RESPONSE_TRY_AGAIN) {
_filledLength += ret; _filledLength += ret;
@ -782,7 +791,8 @@ size_t AsyncChunkedResponse::_fillBuffer(uint8_t* data, size_t len) {
* Progmem Response * Progmem Response
* */ * */
AsyncProgmemResponse::AsyncProgmemResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) { AsyncProgmemResponse::AsyncProgmemResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback)
: AsyncAbstractResponse(callback) {
_code = code; _code = code;
_content = content; _content = content;
_contentType = contentType; _contentType = contentType;
@ -790,7 +800,7 @@ AsyncProgmemResponse::AsyncProgmemResponse(int code, const char* contentType, co
_readLength = 0; _readLength = 0;
} }
size_t AsyncProgmemResponse::_fillBuffer(uint8_t* data, size_t len) { size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len) {
size_t left = _contentLength - _readLength; size_t left = _contentLength - _readLength;
if (left > len) { if (left > len) {
memcpy_P(data, _content + _readLength, len); memcpy_P(data, _content + _readLength, len);
@ -806,20 +816,25 @@ size_t AsyncProgmemResponse::_fillBuffer(uint8_t* data, size_t len) {
* Response Stream (You can print/write/printf to it, up to the contentLen bytes) * Response Stream (You can print/write/printf to it, up to the contentLen bytes)
* */ * */
AsyncResponseStream::AsyncResponseStream(const char* contentType, size_t bufferSize) { AsyncResponseStream::AsyncResponseStream(const char *contentType, size_t bufferSize) {
_code = 200; _code = 200;
_contentLength = 0; _contentLength = 0;
_contentType = contentType; _contentType = contentType;
_content.reserve(bufferSize); if (!_content.reserve(bufferSize)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
}
} }
size_t AsyncResponseStream::_fillBuffer(uint8_t* buf, size_t maxLen) { size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen) {
return _content.readBytes((char*)buf, maxLen); return _content.readBytes((char *)buf, maxLen);
} }
size_t AsyncResponseStream::write(const uint8_t* data, size_t len) { size_t AsyncResponseStream::write(const uint8_t *data, size_t len) {
if (_started()) if (_started()) {
return 0; return 0;
}
size_t written = _content.write(data, len); size_t written = _content.write(data, len);
_contentLength += written; _contentLength += written;
return written; return written;

View file

@ -1,29 +1,12 @@
/* // SPDX-License-Identifier: LGPL-3.0-or-later
Asynchronous WebServer library for Espressif MCUs // Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ESPAsyncWebServer.h" #include "ESPAsyncWebServer.h"
#include "WebHandlerImpl.h" #include "WebHandlerImpl.h"
using namespace asyncsrv; using namespace asyncsrv;
bool ON_STA_FILTER(AsyncWebServerRequest* request) { bool ON_STA_FILTER(AsyncWebServerRequest *request) {
#ifndef CONFIG_IDF_TARGET_ESP32H2 #ifndef CONFIG_IDF_TARGET_ESP32H2
return WiFi.localIP() == request->client()->localIP(); return WiFi.localIP() == request->client()->localIP();
#else #else
@ -31,7 +14,7 @@ bool ON_STA_FILTER(AsyncWebServerRequest* request) {
#endif #endif
} }
bool ON_AP_FILTER(AsyncWebServerRequest* request) { bool ON_AP_FILTER(AsyncWebServerRequest *request) {
#ifndef CONFIG_IDF_TARGET_ESP32H2 #ifndef CONFIG_IDF_TARGET_ESP32H2
return WiFi.localIP() != request->client()->localIP(); return WiFi.localIP() != request->client()->localIP();
#else #else
@ -40,51 +23,51 @@ bool ON_AP_FILTER(AsyncWebServerRequest* request) {
} }
#ifndef HAVE_FS_FILE_OPEN_MODE #ifndef HAVE_FS_FILE_OPEN_MODE
const char* fs::FileOpenMode::read = "r"; const char *fs::FileOpenMode::read = "r";
const char* fs::FileOpenMode::write = "w"; const char *fs::FileOpenMode::write = "w";
const char* fs::FileOpenMode::append = "a"; const char *fs::FileOpenMode::append = "a";
#endif #endif
AsyncWebServer::AsyncWebServer(uint16_t port) AsyncWebServer::AsyncWebServer(uint16_t port) : _server(port) {
: _server(port) {
_catchAllHandler = new AsyncCallbackWebHandler(); _catchAllHandler = new AsyncCallbackWebHandler();
if (_catchAllHandler == NULL) _server.onClient(
return; [](void *s, AsyncClient *c) {
_server.onClient([](void* s, AsyncClient* c) { if (c == NULL) {
if (c == NULL)
return; return;
}
c->setRxTimeout(3); c->setRxTimeout(3);
AsyncWebServerRequest* r = new AsyncWebServerRequest((AsyncWebServer*)s, c); AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer *)s, c);
if (r == NULL) { if (r == NULL) {
c->abort(); c->abort();
delete c; delete c;
} }
}, },
this); this
);
} }
AsyncWebServer::~AsyncWebServer() { AsyncWebServer::~AsyncWebServer() {
reset(); reset();
end(); end();
if (_catchAllHandler)
delete _catchAllHandler; delete _catchAllHandler;
_catchAllHandler = nullptr; // Prevent potential use-after-free
} }
AsyncWebRewrite& AsyncWebServer::addRewrite(std::shared_ptr<AsyncWebRewrite> rewrite) { AsyncWebRewrite &AsyncWebServer::addRewrite(std::shared_ptr<AsyncWebRewrite> rewrite) {
_rewrites.emplace_back(rewrite); _rewrites.emplace_back(rewrite);
return *_rewrites.back().get(); return *_rewrites.back().get();
} }
AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite) { AsyncWebRewrite &AsyncWebServer::addRewrite(AsyncWebRewrite *rewrite) {
_rewrites.emplace_back(rewrite); _rewrites.emplace_back(rewrite);
return *_rewrites.back().get(); return *_rewrites.back().get();
} }
bool AsyncWebServer::removeRewrite(AsyncWebRewrite* rewrite) { bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite) {
return removeRewrite(rewrite->from().c_str(), rewrite->toUrl().c_str()); return removeRewrite(rewrite->from().c_str(), rewrite->toUrl().c_str());
} }
bool AsyncWebServer::removeRewrite(const char* from, const char* to) { bool AsyncWebServer::removeRewrite(const char *from, const char *to) {
for (auto r = _rewrites.begin(); r != _rewrites.end(); ++r) { for (auto r = _rewrites.begin(); r != _rewrites.end(); ++r) {
if (r->get()->from() == from && r->get()->toUrl() == to) { if (r->get()->from() == from && r->get()->toUrl() == to) {
_rewrites.erase(r); _rewrites.erase(r);
@ -94,17 +77,17 @@ bool AsyncWebServer::removeRewrite(const char* from, const char* to) {
return false; return false;
} }
AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to) { AsyncWebRewrite &AsyncWebServer::rewrite(const char *from, const char *to) {
_rewrites.emplace_back(std::make_shared<AsyncWebRewrite>(from, to)); _rewrites.emplace_back(std::make_shared<AsyncWebRewrite>(from, to));
return *_rewrites.back().get(); return *_rewrites.back().get();
} }
AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler) { AsyncWebHandler &AsyncWebServer::addHandler(AsyncWebHandler *handler) {
_handlers.emplace_back(handler); _handlers.emplace_back(handler);
return *(_handlers.back().get()); return *(_handlers.back().get());
} }
bool AsyncWebServer::removeHandler(AsyncWebHandler* handler) { bool AsyncWebServer::removeHandler(AsyncWebHandler *handler) {
for (auto i = _handlers.begin(); i != _handlers.end(); ++i) { for (auto i = _handlers.begin(); i != _handlers.end(); ++i) {
if (i->get() == handler) { if (i->get() == handler) {
_handlers.erase(i); _handlers.erase(i);
@ -124,21 +107,23 @@ void AsyncWebServer::end() {
} }
#if ASYNC_TCP_SSL_ENABLED #if ASYNC_TCP_SSL_ENABLED
void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg) { void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void *arg) {
_server.onSslFileRequest(cb, arg); _server.onSslFileRequest(cb, arg);
} }
void AsyncWebServer::beginSecure(const char* cert, const char* key, const char* password) { void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password) {
_server.beginSecure(cert, key, password); _server.beginSecure(cert, key, password);
} }
#endif #endif
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest* request) { void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request) {
delete request; delete request;
} }
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest* request) { void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request) {
for (const auto& r : _rewrites) { // the last rewrite that matches the request will be used
// we do not break the loop to allow for multiple rewrites to be applied and only the last one to be used (allows overriding)
for (const auto &r : _rewrites) {
if (r->match(request)) { if (r->match(request)) {
request->_url = r->toUrl(); request->_url = r->toUrl();
request->_addGetParams(r->params()); request->_addGetParams(r->params());
@ -146,8 +131,8 @@ void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest* request) {
} }
} }
void AsyncWebServer::_attachHandler(AsyncWebServerRequest* request) { void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request) {
for (auto& h : _handlers) { for (auto &h : _handlers) {
if (h->filter(request) && h->canHandle(request)) { if (h->filter(request) && h->canHandle(request)) {
request->setHandler(h.get()); request->setHandler(h.get());
return; return;
@ -157,8 +142,10 @@ void AsyncWebServer::_attachHandler(AsyncWebServerRequest* request) {
request->setHandler(_catchAllHandler); request->setHandler(_catchAllHandler);
} }
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody) { AsyncCallbackWebHandler &AsyncWebServer::on(
AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); const char *uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody
) {
AsyncCallbackWebHandler *handler = new AsyncCallbackWebHandler();
handler->setUri(uri); handler->setUri(uri);
handler->setMethod(method); handler->setMethod(method);
handler->onRequest(onRequest); handler->onRequest(onRequest);
@ -168,8 +155,8 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom
return *handler; return *handler;
} }
AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control) { AsyncStaticWebHandler &AsyncWebServer::serveStatic(const char *uri, fs::FS &fs, const char *path, const char *cache_control) {
AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control); AsyncStaticWebHandler *handler = new AsyncStaticWebHandler(uri, fs, path, cache_control);
addHandler(handler); addHandler(handler);
return *handler; return *handler;
} }
@ -186,13 +173,15 @@ void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn) {
_catchAllHandler->onBody(fn); _catchAllHandler->onBody(fn);
} }
AsyncWebHandler &AsyncWebServer::catchAllHandler() const {
return *_catchAllHandler;
}
void AsyncWebServer::reset() { void AsyncWebServer::reset() {
_rewrites.clear(); _rewrites.clear();
_handlers.clear(); _handlers.clear();
if (_catchAllHandler != NULL) {
_catchAllHandler->onRequest(NULL); _catchAllHandler->onRequest(NULL);
_catchAllHandler->onUpload(NULL); _catchAllHandler->onUpload(NULL);
_catchAllHandler->onBody(NULL); _catchAllHandler->onBody(NULL);
}
} }

View file

@ -1,183 +1,193 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#pragma once #pragma once
namespace asyncsrv { namespace asyncsrv {
static constexpr const char* empty = ""; static constexpr const char *empty = "";
static constexpr const char* T__opaque = "\", opaque=\""; static constexpr const char *T__opaque = "\", opaque=\"";
static constexpr const char* T_100_CONTINUE = "100-continue"; static constexpr const char *T_100_CONTINUE = "100-continue";
static constexpr const char* T_13 = "13"; static constexpr const char *T_13 = "13";
static constexpr const char* T_ACCEPT = "accept"; static constexpr const char *T_ACCEPT = "accept";
static constexpr const char* T_Accept_Ranges = "accept-ranges"; static constexpr const char *T_Accept_Ranges = "accept-ranges";
static constexpr const char* T_app_xform_urlencoded = "application/x-www-form-urlencoded"; static constexpr const char *T_app_xform_urlencoded = "application/x-www-form-urlencoded";
static constexpr const char* T_AUTH = "authorization"; static constexpr const char *T_AUTH = "authorization";
static constexpr const char* T_auth_nonce = "\", qop=\"auth\", nonce=\""; static constexpr const char *T_auth_nonce = "\", qop=\"auth\", nonce=\"";
static constexpr const char* T_BASIC = "basic"; static constexpr const char *T_BASIC = "basic";
static constexpr const char* T_BASIC_REALM = "basic realm=\""; static constexpr const char *T_BASIC_REALM = "basic realm=\"";
static constexpr const char* T_BEARER = "bearer"; static constexpr const char *T_BEARER = "bearer";
static constexpr const char* T_BODY = "body"; static constexpr const char *T_BODY = "body";
static constexpr const char* T_Cache_Control = "cache-control"; static constexpr const char *T_Cache_Control = "cache-control";
static constexpr const char* T_chunked = "chunked"; static constexpr const char *T_chunked = "chunked";
static constexpr const char* T_close = "close"; static constexpr const char *T_close = "close";
static constexpr const char* T_cnonce = "cnonce"; static constexpr const char *T_cnonce = "cnonce";
static constexpr const char* T_Connection = "connection"; static constexpr const char *T_Connection = "connection";
static constexpr const char* T_Content_Disposition = "content-disposition"; static constexpr const char *T_Content_Disposition = "content-disposition";
static constexpr const char* T_Content_Encoding = "content-encoding"; static constexpr const char *T_Content_Encoding = "content-encoding";
static constexpr const char* T_Content_Length = "content-length"; static constexpr const char *T_Content_Length = "content-length";
static constexpr const char* T_Content_Type = "content-type"; static constexpr const char *T_Content_Type = "content-type";
static constexpr const char* T_Cookie = "cookie"; static constexpr const char *T_Content_Location = "content-location";
static constexpr const char* T_CORS_ACAC = "access-control-allow-credentials"; static constexpr const char *T_Cookie = "cookie";
static constexpr const char* T_CORS_ACAH = "access-control-allow-headers"; static constexpr const char *T_CORS_ACAC = "access-control-allow-credentials";
static constexpr const char* T_CORS_ACAM = "access-control-allow-methods"; static constexpr const char *T_CORS_ACAH = "access-control-allow-headers";
static constexpr const char* T_CORS_ACAO = "access-control-allow-origin"; static constexpr const char *T_CORS_ACAM = "access-control-allow-methods";
static constexpr const char* T_CORS_ACMA = "access-control-max-age"; static constexpr const char *T_CORS_ACAO = "access-control-allow-origin";
static constexpr const char* T_CORS_O = "origin"; static constexpr const char *T_CORS_ACMA = "access-control-max-age";
static constexpr const char* T_data_ = "data: "; static constexpr const char *T_CORS_O = "origin";
static constexpr const char* T_DIGEST = "digest"; static constexpr const char *T_data_ = "data: ";
static constexpr const char* T_DIGEST_ = "digest "; static constexpr const char *T_Date = "date";
static constexpr const char* T_ETag = "etag"; static constexpr const char *T_DIGEST = "digest";
static constexpr const char* T_event_ = "event: "; static constexpr const char *T_DIGEST_ = "digest ";
static constexpr const char* T_EXPECT = "expect"; static constexpr const char *T_ETag = "etag";
static constexpr const char* T_FALSE = "false"; static constexpr const char *T_event_ = "event: ";
static constexpr const char* T_filename = "filename"; static constexpr const char *T_EXPECT = "expect";
static constexpr const char* T_gzip = "gzip"; static constexpr const char *T_FALSE = "false";
static constexpr const char* T_Host = "host"; static constexpr const char *T_filename = "filename";
static constexpr const char* T_HTTP_1_0 = "HTTP/1.0"; static constexpr const char *T_gzip = "gzip";
static constexpr const char* T_HTTP_100_CONT = "HTTP/1.1 100 Continue\r\n\r\n"; static constexpr const char *T_Host = "host";
static constexpr const char* T_id__ = "id: "; static constexpr const char *T_HTTP_1_0 = "HTTP/1.0";
static constexpr const char* T_IMS = "if-modified-since"; static constexpr const char *T_HTTP_100_CONT = "HTTP/1.1 100 Continue\r\n\r\n";
static constexpr const char* T_INM = "if-none-match"; static constexpr const char *T_id__ = "id: ";
static constexpr const char* T_keep_alive = "keep-alive"; static constexpr const char *T_IMS = "if-modified-since";
static constexpr const char* T_Last_Event_ID = "last-event-id"; static constexpr const char *T_INM = "if-none-match";
static constexpr const char* T_Last_Modified = "last-modified"; static constexpr const char *T_keep_alive = "keep-alive";
static constexpr const char* T_LOCATION = "location"; static constexpr const char *T_Last_Event_ID = "last-event-id";
static constexpr const char* T_LOGIN_REQ = "Login Required"; static constexpr const char *T_Last_Modified = "last-modified";
static constexpr const char* T_MULTIPART_ = "multipart/"; static constexpr const char *T_LOCATION = "location";
static constexpr const char* T_name = "name"; static constexpr const char *T_LOGIN_REQ = "Login Required";
static constexpr const char* T_nc = "nc"; static constexpr const char *T_MULTIPART_ = "multipart/";
static constexpr const char* T_no_cache = "no-cache"; static constexpr const char *T_name = "name";
static constexpr const char* T_nonce = "nonce"; static constexpr const char *T_nc = "nc";
static constexpr const char* T_none = "none"; static constexpr const char *T_no_cache = "no-cache";
static constexpr const char* T_opaque = "opaque"; static constexpr const char *T_nonce = "nonce";
static constexpr const char* T_qop = "qop"; static constexpr const char *T_none = "none";
static constexpr const char* T_realm = "realm"; static constexpr const char *T_opaque = "opaque";
static constexpr const char* T_realm__ = "realm=\""; static constexpr const char *T_qop = "qop";
static constexpr const char* T_response = "response"; static constexpr const char *T_realm = "realm";
static constexpr const char* T_retry_ = "retry: "; static constexpr const char *T_realm__ = "realm=\"";
static constexpr const char* T_retry_after = "retry-after"; static constexpr const char *T_response = "response";
static constexpr const char* T_nn = "\n\n"; static constexpr const char *T_retry_ = "retry: ";
static constexpr const char* T_rn = "\r\n"; static constexpr const char *T_retry_after = "retry-after";
static constexpr const char* T_rnrn = "\r\n\r\n"; static constexpr const char *T_nn = "\n\n";
static constexpr const char* T_Transfer_Encoding = "transfer-encoding"; static constexpr const char *T_rn = "\r\n";
static constexpr const char* T_TRUE = "true"; static constexpr const char *T_rnrn = "\r\n\r\n";
static constexpr const char* T_UPGRADE = "upgrade"; static constexpr const char *T_Server = "server";
static constexpr const char* T_uri = "uri"; static constexpr const char *T_Transfer_Encoding = "transfer-encoding";
static constexpr const char* T_username = "username"; static constexpr const char *T_TRUE = "true";
static constexpr const char* T_WS = "websocket"; static constexpr const char *T_UPGRADE = "upgrade";
static constexpr const char* T_WWW_AUTH = "www-authenticate"; static constexpr const char *T_uri = "uri";
static constexpr const char *T_username = "username";
static constexpr const char *T_WS = "websocket";
static constexpr const char *T_WWW_AUTH = "www-authenticate";
// HTTP Methods // HTTP Methods
static constexpr const char* T_ANY = "ANY"; static constexpr const char *T_ANY = "ANY";
static constexpr const char* T_GET = "GET"; static constexpr const char *T_GET = "GET";
static constexpr const char* T_POST = "POST"; static constexpr const char *T_POST = "POST";
static constexpr const char* T_PUT = "PUT"; static constexpr const char *T_PUT = "PUT";
static constexpr const char* T_DELETE = "DELETE"; static constexpr const char *T_DELETE = "DELETE";
static constexpr const char* T_PATCH = "PATCH"; static constexpr const char *T_PATCH = "PATCH";
static constexpr const char* T_HEAD = "HEAD"; static constexpr const char *T_HEAD = "HEAD";
static constexpr const char* T_OPTIONS = "OPTIONS"; static constexpr const char *T_OPTIONS = "OPTIONS";
static constexpr const char* T_UNKNOWN = "UNKNOWN"; static constexpr const char *T_UNKNOWN = "UNKNOWN";
// Req content types // Req content types
static constexpr const char* T_RCT_NOT_USED = "RCT_NOT_USED"; static constexpr const char *T_RCT_NOT_USED = "RCT_NOT_USED";
static constexpr const char* T_RCT_DEFAULT = "RCT_DEFAULT"; static constexpr const char *T_RCT_DEFAULT = "RCT_DEFAULT";
static constexpr const char* T_RCT_HTTP = "RCT_HTTP"; static constexpr const char *T_RCT_HTTP = "RCT_HTTP";
static constexpr const char* T_RCT_WS = "RCT_WS"; static constexpr const char *T_RCT_WS = "RCT_WS";
static constexpr const char* T_RCT_EVENT = "RCT_EVENT"; static constexpr const char *T_RCT_EVENT = "RCT_EVENT";
static constexpr const char* T_ERROR = "ERROR"; static constexpr const char *T_ERROR = "ERROR";
// extentions & MIME-Types // extensions & MIME-Types
static constexpr const char* T__css = ".css"; static constexpr const char *T__css = ".css";
static constexpr const char* T__eot = ".eot"; static constexpr const char *T__eot = ".eot";
static constexpr const char* T__gif = ".gif"; static constexpr const char *T__gif = ".gif";
static constexpr const char* T__gz = ".gz"; static constexpr const char *T__gz = ".gz";
static constexpr const char* T__htm = ".htm"; static constexpr const char *T__htm = ".htm";
static constexpr const char* T__html = ".html"; static constexpr const char *T__html = ".html";
static constexpr const char* T__ico = ".ico"; static constexpr const char *T__ico = ".ico";
static constexpr const char* T__jpg = ".jpg"; static constexpr const char *T__jpg = ".jpg";
static constexpr const char* T__js = ".js"; static constexpr const char *T__js = ".js";
static constexpr const char* T__json = ".json"; static constexpr const char *T__json = ".json";
static constexpr const char* T__pdf = ".pdf"; static constexpr const char *T__pdf = ".pdf";
static constexpr const char* T__png = ".png"; static constexpr const char *T__png = ".png";
static constexpr const char* T__svg = ".svg"; static constexpr const char *T__svg = ".svg";
static constexpr const char* T__ttf = ".ttf"; static constexpr const char *T__ttf = ".ttf";
static constexpr const char* T__woff = ".woff"; static constexpr const char *T__woff = ".woff";
static constexpr const char* T__woff2 = ".woff2"; static constexpr const char *T__woff2 = ".woff2";
static constexpr const char* T__xml = ".xml"; static constexpr const char *T__xml = ".xml";
static constexpr const char* T__zip = ".zip"; static constexpr const char *T__zip = ".zip";
static constexpr const char* T_application_javascript = "application/javascript"; static constexpr const char *T_application_javascript = "application/javascript";
static constexpr const char* T_application_json = "application/json"; static constexpr const char *T_application_json = "application/json";
static constexpr const char* T_application_msgpack = "application/msgpack"; static constexpr const char *T_application_msgpack = "application/msgpack";
static constexpr const char* T_application_pdf = "application/pdf"; static constexpr const char *T_application_pdf = "application/pdf";
static constexpr const char* T_application_x_gzip = "application/x-gzip"; static constexpr const char *T_application_x_gzip = "application/x-gzip";
static constexpr const char* T_application_zip = "application/zip"; static constexpr const char *T_application_zip = "application/zip";
static constexpr const char* T_font_eot = "font/eot"; static constexpr const char *T_font_eot = "font/eot";
static constexpr const char* T_font_ttf = "font/ttf"; static constexpr const char *T_font_ttf = "font/ttf";
static constexpr const char* T_font_woff = "font/woff"; static constexpr const char *T_font_woff = "font/woff";
static constexpr const char* T_font_woff2 = "font/woff2"; static constexpr const char *T_font_woff2 = "font/woff2";
static constexpr const char* T_image_gif = "image/gif"; static constexpr const char *T_image_gif = "image/gif";
static constexpr const char* T_image_jpeg = "image/jpeg"; static constexpr const char *T_image_jpeg = "image/jpeg";
static constexpr const char* T_image_png = "image/png"; static constexpr const char *T_image_png = "image/png";
static constexpr const char* T_image_svg_xml = "image/svg+xml"; static constexpr const char *T_image_svg_xml = "image/svg+xml";
static constexpr const char* T_image_x_icon = "image/x-icon"; static constexpr const char *T_image_x_icon = "image/x-icon";
static constexpr const char* T_text_css = "text/css"; static constexpr const char *T_text_css = "text/css";
static constexpr const char* T_text_event_stream = "text/event-stream"; static constexpr const char *T_text_event_stream = "text/event-stream";
static constexpr const char* T_text_html = "text/html"; static constexpr const char *T_text_html = "text/html";
static constexpr const char* T_text_plain = "text/plain"; static constexpr const char *T_text_plain = "text/plain";
static constexpr const char* T_text_xml = "text/xml"; static constexpr const char *T_text_xml = "text/xml";
// Responce codes // Response codes
static constexpr const char* T_HTTP_CODE_100 = "Continue"; static constexpr const char *T_HTTP_CODE_100 = "Continue";
static constexpr const char* T_HTTP_CODE_101 = "Switching Protocols"; static constexpr const char *T_HTTP_CODE_101 = "Switching Protocols";
static constexpr const char* T_HTTP_CODE_200 = "OK"; static constexpr const char *T_HTTP_CODE_200 = "OK";
static constexpr const char* T_HTTP_CODE_201 = "Created"; static constexpr const char *T_HTTP_CODE_201 = "Created";
static constexpr const char* T_HTTP_CODE_202 = "Accepted"; static constexpr const char *T_HTTP_CODE_202 = "Accepted";
static constexpr const char* T_HTTP_CODE_203 = "Non-Authoritative Information"; static constexpr const char *T_HTTP_CODE_203 = "Non-Authoritative Information";
static constexpr const char* T_HTTP_CODE_204 = "No Content"; static constexpr const char *T_HTTP_CODE_204 = "No Content";
static constexpr const char* T_HTTP_CODE_205 = "Reset Content"; static constexpr const char *T_HTTP_CODE_205 = "Reset Content";
static constexpr const char* T_HTTP_CODE_206 = "Partial Content"; static constexpr const char *T_HTTP_CODE_206 = "Partial Content";
static constexpr const char* T_HTTP_CODE_300 = "Multiple Choices"; static constexpr const char *T_HTTP_CODE_300 = "Multiple Choices";
static constexpr const char* T_HTTP_CODE_301 = "Moved Permanently"; static constexpr const char *T_HTTP_CODE_301 = "Moved Permanently";
static constexpr const char* T_HTTP_CODE_302 = "Found"; static constexpr const char *T_HTTP_CODE_302 = "Found";
static constexpr const char* T_HTTP_CODE_303 = "See Other"; static constexpr const char *T_HTTP_CODE_303 = "See Other";
static constexpr const char* T_HTTP_CODE_304 = "Not Modified"; static constexpr const char *T_HTTP_CODE_304 = "Not Modified";
static constexpr const char* T_HTTP_CODE_305 = "Use Proxy"; static constexpr const char *T_HTTP_CODE_305 = "Use Proxy";
static constexpr const char* T_HTTP_CODE_307 = "Temporary Redirect"; static constexpr const char *T_HTTP_CODE_307 = "Temporary Redirect";
static constexpr const char* T_HTTP_CODE_400 = "Bad Request"; static constexpr const char *T_HTTP_CODE_400 = "Bad Request";
static constexpr const char* T_HTTP_CODE_401 = "Unauthorized"; static constexpr const char *T_HTTP_CODE_401 = "Unauthorized";
static constexpr const char* T_HTTP_CODE_402 = "Payment Required"; static constexpr const char *T_HTTP_CODE_402 = "Payment Required";
static constexpr const char* T_HTTP_CODE_403 = "Forbidden"; static constexpr const char *T_HTTP_CODE_403 = "Forbidden";
static constexpr const char* T_HTTP_CODE_404 = "Not Found"; static constexpr const char *T_HTTP_CODE_404 = "Not Found";
static constexpr const char* T_HTTP_CODE_405 = "Method Not Allowed"; static constexpr const char *T_HTTP_CODE_405 = "Method Not Allowed";
static constexpr const char* T_HTTP_CODE_406 = "Not Acceptable"; static constexpr const char *T_HTTP_CODE_406 = "Not Acceptable";
static constexpr const char* T_HTTP_CODE_407 = "Proxy Authentication Required"; static constexpr const char *T_HTTP_CODE_407 = "Proxy Authentication Required";
static constexpr const char* T_HTTP_CODE_408 = "Request Time-out"; static constexpr const char *T_HTTP_CODE_408 = "Request Time-out";
static constexpr const char* T_HTTP_CODE_409 = "Conflict"; static constexpr const char *T_HTTP_CODE_409 = "Conflict";
static constexpr const char* T_HTTP_CODE_410 = "Gone"; static constexpr const char *T_HTTP_CODE_410 = "Gone";
static constexpr const char* T_HTTP_CODE_411 = "Length Required"; static constexpr const char *T_HTTP_CODE_411 = "Length Required";
static constexpr const char* T_HTTP_CODE_412 = "Precondition Failed"; static constexpr const char *T_HTTP_CODE_412 = "Precondition Failed";
static constexpr const char* T_HTTP_CODE_413 = "Request Entity Too Large"; static constexpr const char *T_HTTP_CODE_413 = "Request Entity Too Large";
static constexpr const char* T_HTTP_CODE_414 = "Request-URI Too Large"; static constexpr const char *T_HTTP_CODE_414 = "Request-URI Too Large";
static constexpr const char* T_HTTP_CODE_415 = "Unsupported Media Type"; static constexpr const char *T_HTTP_CODE_415 = "Unsupported Media Type";
static constexpr const char* T_HTTP_CODE_416 = "Requested range not satisfiable"; static constexpr const char *T_HTTP_CODE_416 = "Requested range not satisfiable";
static constexpr const char* T_HTTP_CODE_417 = "Expectation Failed"; static constexpr const char *T_HTTP_CODE_417 = "Expectation Failed";
static constexpr const char* T_HTTP_CODE_429 = "Too Many Requests"; static constexpr const char *T_HTTP_CODE_429 = "Too Many Requests";
static constexpr const char* T_HTTP_CODE_500 = "Internal Server Error"; static constexpr const char *T_HTTP_CODE_500 = "Internal Server Error";
static constexpr const char* T_HTTP_CODE_501 = "Not Implemented"; static constexpr const char *T_HTTP_CODE_501 = "Not Implemented";
static constexpr const char* T_HTTP_CODE_502 = "Bad Gateway"; static constexpr const char *T_HTTP_CODE_502 = "Bad Gateway";
static constexpr const char* T_HTTP_CODE_503 = "Service Unavailable"; static constexpr const char *T_HTTP_CODE_503 = "Service Unavailable";
static constexpr const char* T_HTTP_CODE_504 = "Gateway Time-out"; static constexpr const char *T_HTTP_CODE_504 = "Gateway Time-out";
static constexpr const char* T_HTTP_CODE_505 = "HTTP Version not supported"; static constexpr const char *T_HTTP_CODE_505 = "HTTP Version not supported";
static constexpr const char* T_HTTP_CODE_ANY = "Unknown code"; static constexpr const char *T_HTTP_CODE_ANY = "Unknown code";
} // namespace asyncsrv {} static constexpr const uint8_t T_only_once_headers_len = 11;
static constexpr const char *T_only_once_headers[] = {T_Content_Length, T_Content_Type, T_Date, T_ETag, T_Last_Modified, T_LOCATION, T_retry_after,
T_Transfer_Encoding, T_Content_Location, T_Server, T_WWW_AUTH};
} // namespace asyncsrv