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"
)
set(COMPONENT_REQUIRES
"arduino-esp32"
"AsyncTCP"
)
register_component()
target_compile_definitions(${COMPONENT_TARGET} PUBLIC -DESP32)
target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti)

View file

@ -6,7 +6,7 @@
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
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
and orientation.

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"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.",
"keywords": "http,async,websocket,webserver",
"homepage": "https://github.com/ESP32Async/ESPAsyncWebServer",
@ -24,7 +24,7 @@
{
"owner": "ESP32Async",
"name": "AsyncTCP",
"version": "^3.3.2",
"version": "^3.3.6",
"platforms": "espressif32"
},
{
@ -38,9 +38,9 @@
"platforms": "espressif8266"
},
{
"owner": "khoih-prog",
"name": "AsyncTCP_RP2040W",
"version": "^1.2.0",
"owner": "ayushsharma82",
"name": "RPAsyncTCP",
"version": "^1.3.1",
"platforms": "raspberrypi"
}
],

View file

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

View file

@ -1,22 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// 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"
#if defined(ESP32)
#include <rom/ets_sys.h>
@ -30,15 +14,22 @@ using namespace asyncsrv;
static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
String str;
size_t len{0};
if (message)
if (message) {
len += strlen(message);
}
if (event)
if (event) {
len += strlen(event);
}
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) {
str += T_retry_;
@ -58,8 +49,9 @@ static String generateEventMessage(const char* message, const char* event, uint3
str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
}
if (!message)
if (!message) {
return str;
}
size_t messageLen = strlen(message);
char *lineStart = (char *)message;
@ -79,7 +71,7 @@ static String generateEventMessage(const char* message, const char* event, uint3
char *nextLine = NULL;
if (nextN != NULL && nextR != NULL) { // windows line-ending \r\n
if (nextR + 1 == nextN) {
// normal \r\n sequense
// normal \r\n sequence
lineEnd = nextR;
nextLine = nextN + 1;
} else {
@ -124,8 +116,9 @@ size_t AsyncEventSourceMessage::ack(size_t len, __attribute__((unused)) uint32_t
}
size_t AsyncEventSourceMessage::write(AsyncClient *client) {
if (!client)
if (!client) {
return 0;
}
if (_sent >= _data->length() || !client->canSend()) {
return 0;
@ -155,19 +148,42 @@ size_t AsyncEventSourceMessage::send(AsyncClient* client) {
// Client
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server)
: _client(request->client()), _server(server) {
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *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());
}
_client->setRxTimeout(0);
_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->onPoll([](void* r, AsyncClient* c) { (void)c; static_cast<AsyncEventSourceClient*>(r)->_onPoll(); }, this);
_client->onAck(
[](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->onTimeout([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);
_client->onTimeout(
[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);
delete request;
@ -249,10 +265,11 @@ void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t
#endif
// adjust in-flight len
if (len < _inflight)
if (len < _inflight) {
_inflight -= len;
else
} else {
_inflight = 0;
}
// acknowledge as much messages's data as we got confirmed len from a AsyncTCP
while (len && _messageQueue.size()) {
@ -264,9 +281,10 @@ void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t
}
// try to send another batch of data
if (_messageQueue.size())
if (_messageQueue.size()) {
_runQueue();
}
}
void AsyncEventSourceClient::_onPoll() {
if (_messageQueue.size()) {
@ -279,31 +297,36 @@ void AsyncEventSourceClient::_onPoll() {
}
void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))) {
if (_client)
if (_client) {
_client->close(true);
}
}
void AsyncEventSourceClient::_onDisconnect() {
if (!_client)
if (!_client) {
return;
}
_client = nullptr;
_server->_handleDisconnect(this);
}
void AsyncEventSourceClient::close() {
if (_client)
if (_client) {
_client->close();
}
}
bool AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
if (!connected())
if (!connected()) {
return false;
}
return _queueMessage(std::make_shared<String>(generateEventMessage(message, event, id, reconnect)));
}
void AsyncEventSourceClient::_runQueue() {
if (!_client)
if (!_client) {
return;
}
// 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;
@ -320,14 +343,16 @@ void AsyncEventSourceClient::_runQueue() {
}
// flush socket
if (total_bytes_written)
if (total_bytes_written) {
_client->send();
}
}
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;
}
}
/* AsyncEventSource */
@ -338,27 +363,32 @@ void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) {
}
void AsyncEventSource::_addClient(AsyncEventSourceClient *client) {
if (!client)
if (!client) {
return;
}
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
_clients.emplace_back(client);
if (_connectcb)
if (_connectcb) {
_connectcb(client);
}
_adjust_inflight_window();
}
void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient *client) {
if (_disconnectcb)
if (_disconnectcb) {
_disconnectcb(client);
}
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
for (auto i = _clients.begin(); i != _clients.end(); ++i) {
if (i->get() == client)
if (i->get() == client) {
_clients.erase(i);
break;
}
}
_adjust_inflight_window();
}
@ -371,10 +401,11 @@ void AsyncEventSource::close() {
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
for (const auto &c : _clients) {
if (c->connected())
if (c->connected()) {
c->close();
}
}
}
// pmb fix
size_t AsyncEventSource::avgPacketsWaiting() const {
@ -383,8 +414,9 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
if (!_clients.size())
if (!_clients.size()) {
return 0;
}
for (const auto &c : _clients) {
if (c->connected()) {
@ -395,8 +427,7 @@ size_t AsyncEventSource::avgPacketsWaiting() const {
return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up
}
AsyncEventSource::SendStatus AsyncEventSource::send(
const char* message, const char* event, uint32_t id, uint32_t reconnect) {
AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
AsyncEvent_SharedData_t shared_msg = std::make_shared<String>(generateEventMessage(message, event, id, reconnect));
#ifdef ESP32
std::lock_guard<std::mutex> lock(_client_queue_lock);
@ -404,11 +435,12 @@ AsyncEventSource::SendStatus AsyncEventSource::send(
size_t hits = 0;
size_t miss = 0;
for (const auto &c : _clients) {
if (c->write(shared_msg))
if (c->write(shared_msg)) {
++hits;
else
} else {
++miss;
}
}
return hits == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
}
@ -417,9 +449,11 @@ size_t AsyncEventSource::count() const {
std::lock_guard<std::mutex> lock(_client_queue_lock);
#endif
size_t n_clients{0};
for (const auto& i : _clients)
if (i->connected())
for (const auto &i : _clients) {
if (i->connected()) {
++n_clients;
}
}
return n_clients;
}
@ -435,8 +469,9 @@ void AsyncEventSource::handleRequest(AsyncWebServerRequest* request) {
void AsyncEventSource::_adjust_inflight_window() {
if (_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);
}
// Serial.printf("adjusted inflight to: %u\n", inflight);
}
}

View file

@ -1,29 +1,13 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// 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_
#define ASYNCEVENTSOURCE_H_
#include <Arduino.h>
#ifdef ESP32
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h"
#include <AsyncTCP.h>
#include <mutex>
#ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 32
@ -37,8 +21,8 @@
#endif
#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
#elif defined(TARGET_RP2040)
#include <AsyncTCP_RP2040W.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#ifndef SSE_MAX_QUEUED_MESSAGES
#define SSE_MAX_QUEUED_MESSAGES 32
#endif
@ -46,7 +30,7 @@
#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
#include "ESPAsyncWebServer.h"
#include <ESPAsyncWebServer.h>
#ifdef ESP8266
#include <Hash.h>
@ -76,18 +60,26 @@ class AsyncEventSourceMessage {
public:
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)){};
#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
// 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
/**
* @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
*
* @param len bytes to acknowlegde
* @param len bytes to acknowledge
* @param time
* @return size_t number of extra bytes carried over
*/
@ -111,13 +103,17 @@ class AsyncEventSourceMessage {
size_t send(AsyncClient *client);
// 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
*
*/
bool sent() { return _sent == _data->length(); }
bool sent() {
return _sent == _data->length();
}
};
/**
@ -155,8 +151,12 @@ class AsyncEventSourceClient {
* @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 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 char* event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event, id, reconnect); }
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 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
@ -166,20 +166,32 @@ class AsyncEventSourceClient {
* @return true on success
* @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")]]
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
void close();
// getters
AsyncClient* client() { return _client; }
bool connected() const { return _client && _client->connected(); }
uint32_t lastId() const { return _lastId; }
size_t packetsWaiting() const { return _messageQueue.size(); };
AsyncClient *client() {
return _client;
}
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
@ -195,7 +207,9 @@ class AsyncEventSourceClient {
*
* @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!)
void _onAck(size_t len, uint32_t time);
@ -233,9 +247,13 @@ class AsyncEventSource : public AsyncWebHandler {
AsyncEventSource(const char *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
void close();
@ -245,7 +263,9 @@ class AsyncEventSource : public AsyncWebHandler {
*
* @param cb
*/
void onConnect(ArEventHandlerFunction cb) { _connectcb = cb; }
void onConnect(ArEventHandlerFunction cb) {
_connectcb = cb;
}
/**
* @brief Send an SSE message to client
@ -258,11 +278,17 @@ class AsyncEventSource : public AsyncWebHandler {
* @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 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 char* event, uint32_t id = 0, uint32_t reconnect = 0) { return send(message.c_str(), event, id, reconnect); }
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 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 !
void onDisconnect(ArEventHandlerFunction cb) { _disconnectcb = cb; }
void onDisconnect(ArEventHandlerFunction cb) {
_disconnectcb = cb;
}
void authorizeConnect(ArAuthorizeConnectHandler cb);
// returns number of connected clients
@ -286,7 +312,9 @@ class AsyncEventSourceResponse : public AsyncWebServerResponse {
AsyncEventSourceResponse(AsyncEventSource *server);
void _respond(AsyncWebServerRequest *request);
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
bool _sourceValid() const { return true; }
bool _sourceValid() const {
return true;
}
};
#endif /* ASYNCEVENTSOURCE_H_ */

View file

@ -1,3 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include "AsyncJson.h"
#if ASYNC_JSON_SUPPORT == 1
@ -6,29 +9,32 @@
AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_json;
if (isArray)
if (isArray) {
_root = _jsonBuffer.createArray();
else
} else {
_root = _jsonBuffer.createObject();
}
}
#elif ARDUINOJSON_VERSION_MAJOR == 6
AsyncJsonResponse::AsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_json;
if (isArray)
if (isArray) {
_root = _jsonBuffer.createNestedArray();
else
} else {
_root = _jsonBuffer.createNestedObject();
}
}
#else
AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_json;
if (isArray)
if (isArray) {
_root = _jsonBuffer.add<JsonArray>();
else
} else {
_root = _jsonBuffer.add<JsonObject>();
}
}
#endif
size_t AsyncJsonResponse::setLength() {
@ -90,14 +96,17 @@ AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String& uri, ArJs
#endif
bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method()))
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
return false;
}
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
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 true;
}
@ -141,6 +150,13 @@ void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest* request, uin
_contentLength = total;
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
request->_tempObject = malloc(total);
if (request->_tempObject == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
}
if (request->_tempObject != NULL) {
memcpy((uint8_t *)(request->_tempObject) + index, data, len);

View file

@ -1,37 +1,6 @@
// AsyncJson.h
/*
Async Response to use with ArduinoJson and AsyncWebServer
Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon.
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
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_
#define ASYNC_JSON_H_
@ -74,13 +43,21 @@ class AsyncJsonResponse : public AsyncAbstractResponse {
#else
AsyncJsonResponse(bool isArray = false);
#endif
JsonVariant& getRoot() { return _root; }
bool _sourceValid() const { return _isValid; }
JsonVariant &getRoot() {
return _root;
}
bool _sourceValid() const {
return _isValid;
}
size_t setLength();
size_t getSize() const { return _jsonBuffer.size(); }
size_t getSize() const {
return _jsonBuffer.size();
}
size_t _fillBuffer(uint8_t *data, size_t len);
#if ARDUINOJSON_VERSION_MAJOR >= 6
bool overflowed() const { return _jsonBuffer.overflowed(); }
bool overflowed() const {
return _jsonBuffer.overflowed();
}
#endif
};
@ -115,15 +92,26 @@ class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest = nullptr);
#endif
void setMethod(WebRequestMethodComposite method) { _method = method; }
void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; }
void onRequest(ArJsonRequestHandlerFunction fn) { _onRequest = fn; }
void setMethod(WebRequestMethodComposite method) {
_method = method;
}
void setMaxContentLength(int maxContentLength) {
_maxContentLength = maxContentLength;
}
void onRequest(ArJsonRequestHandlerFunction fn) {
_onRequest = fn;
}
bool canHandle(AsyncWebServerRequest *request) const 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(
__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 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 // ASYNC_JSON_SUPPORT == 1

View file

@ -1,3 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include "AsyncMessagePack.h"
#if ASYNC_MSG_PACK_SUPPORT == 1
@ -6,20 +9,22 @@
AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_msgpack;
if (isArray)
if (isArray) {
_root = _jsonBuffer.createNestedArray();
else
} else {
_root = _jsonBuffer.createNestedObject();
}
}
#else
AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray) : _isValid{false} {
_code = 200;
_contentType = asyncsrv::T_application_msgpack;
if (isArray)
if (isArray) {
_root = _jsonBuffer.add<JsonArray>();
else
} else {
_root = _jsonBuffer.add<JsonObject>();
}
}
#endif
size_t AsyncMessagePackResponse::setLength() {
@ -37,7 +42,9 @@ size_t AsyncMessagePackResponse::_fillBuffer(uint8_t* data, size_t len) {
}
#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) {}
#else
AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest)
@ -45,14 +52,17 @@ AsyncCallbackMessagePackWebHandler::AsyncCallbackMessagePackWebHandler(const Str
#endif
bool AsyncCallbackMessagePackWebHandler::canHandle(AsyncWebServerRequest *request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method()))
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
return false;
}
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/")))
if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
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 true;
}
@ -92,6 +102,13 @@ void AsyncCallbackMessagePackWebHandler::handleBody(AsyncWebServerRequest* reque
_contentLength = total;
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
request->_tempObject = malloc(total);
if (request->_tempObject == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
}
if (request->_tempObject != NULL) {
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
/*
@ -58,13 +61,21 @@ class AsyncMessagePackResponse : public AsyncAbstractResponse {
#else
AsyncMessagePackResponse(bool isArray = false);
#endif
JsonVariant& getRoot() { return _root; }
bool _sourceValid() const { return _isValid; }
JsonVariant &getRoot() {
return _root;
}
bool _sourceValid() const {
return _isValid;
}
size_t setLength();
size_t getSize() const { return _jsonBuffer.size(); }
size_t getSize() const {
return _jsonBuffer.size();
}
size_t _fillBuffer(uint8_t *data, size_t len);
#if ARDUINOJSON_VERSION_MAJOR >= 6
bool overflowed() const { return _jsonBuffer.overflowed(); }
bool overflowed() const {
return _jsonBuffer.overflowed();
}
#endif
};
@ -83,20 +94,33 @@ class AsyncCallbackMessagePackWebHandler : public AsyncWebHandler {
public:
#if ARDUINOJSON_VERSION_MAJOR == 6
AsyncCallbackMessagePackWebHandler(const String& uri, ArMessagePackRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
AsyncCallbackMessagePackWebHandler(
const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE
);
#else
AsyncCallbackMessagePackWebHandler(const String &uri, ArMessagePackRequestHandlerFunction onRequest = nullptr);
#endif
void setMethod(WebRequestMethodComposite method) { _method = method; }
void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; }
void onRequest(ArMessagePackRequestHandlerFunction fn) { _onRequest = fn; }
void setMethod(WebRequestMethodComposite method) {
_method = method;
}
void setMaxContentLength(int maxContentLength) {
_maxContentLength = maxContentLength;
}
void onRequest(ArMessagePackRequestHandlerFunction fn) {
_onRequest = fn;
}
bool canHandle(AsyncWebServerRequest *request) const 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(
__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 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 // 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
#include <ESPAsyncWebServer.h>
AsyncWebHeader::AsyncWebHeader(const String &data) {
if (!data)
if (!data) {
return;
}
int index = data.indexOf(':');
if (index < 0)
if (index < 0) {
return;
}
_name = data.substring(0, index);
_value = data.substring(index + 2);
}
String AsyncWebHeader::toString() const {
String str;
str.reserve(_name.length() + _value.length() + 2);
if (str.reserve(_name.length() + _value.length() + 2)) {
str.concat(_name);
str.concat((char)0x3a);
str.concat((char)0x20);
str.concat(_value);
str.concat(asyncsrv::T_rn);
} else {
#ifdef ESP32
log_e("Failed to allocate");
#endif
}
return str;
}

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,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// 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 "AsyncWebSocket.h"
#include "Arduino.h"
@ -32,18 +15,20 @@
#include <SHA1Builder.h>
#endif
#include <rom/ets_sys.h>
#elif defined(TARGET_RP2040) || defined(ESP8266)
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(ESP8266)
#include <Hash.h>
#endif
using namespace asyncsrv;
size_t webSocketSendFrameWindow(AsyncClient *client) {
if (!client || !client->canSend())
if (!client || !client->canSend()) {
return 0;
}
size_t space = client->space();
if (space < 9)
if (space < 9) {
return 0;
}
return space - 8;
}
@ -66,30 +51,35 @@ size_t webSocketSendFrame(AsyncClient* client, bool final, uint8_t opcode, bool
mbuf[2] = rand() % 0xFF;
mbuf[3] = rand() % 0xFF;
}
if (len > 125)
if (len > 125) {
headLen += 2;
}
if (space < headLen) {
// Serial.println("SF 2");
return 0;
}
space -= headLen;
if (len > space)
if (len > space) {
len = space;
}
uint8_t *buf = (uint8_t *)malloc(headLen);
if (buf == NULL) {
// os_printf("could not malloc %u bytes for frame header\n", headLen);
// Serial.println("SF 3");
#ifdef ESP32
log_e("Failed to allocate");
#endif
client->abort();
return 0;
}
buf[0] = opcode & 0x0F;
if (final)
if (final) {
buf[0] |= 0x80;
if (len < 126)
}
if (len < 126) {
buf[1] = len & 0x7F;
else {
} else {
buf[1] = 126;
buf[2] = (uint8_t)((len >> 8) & 0xFF);
buf[3] = (uint8_t)(len & 0xFF);
@ -109,9 +99,10 @@ size_t webSocketSendFrame(AsyncClient* client, bool final, uint8_t opcode, bool
if (len) {
if (len && mask) {
size_t i;
for (i = 0; i < len; i++)
for (i = 0; i < len; i++) {
data[i] = data[i] ^ mbuf[i % 4];
}
}
if (client->add((const char *)data, len) != len) {
// os_printf("error adding %lu data bytes\n", len);
// Serial.println("SF 5");
@ -131,8 +122,7 @@ size_t webSocketSendFrame(AsyncClient* client, bool final, uint8_t opcode, bool
* AsyncWebSocketMessageBuffer
*/
AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size)
: _buffer(std::make_shared<std::vector<uint8_t>>(size)) {
AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const uint8_t *data, size_t size) : _buffer(std::make_shared<std::vector<uint8_t>>(size)) {
if (_buffer->capacity() < size) {
_buffer->reserve(size);
} else {
@ -140,16 +130,16 @@ AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const uint8_t* data, si
}
}
AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size)
: _buffer(std::make_shared<std::vector<uint8_t>>(size)) {
AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size) : _buffer(std::make_shared<std::vector<uint8_t>>(size)) {
if (_buffer->capacity() < size) {
_buffer->reserve(size);
}
}
bool AsyncWebSocketMessageBuffer::reserve(size_t size) {
if (_buffer->capacity() >= size)
if (_buffer->capacity() >= size) {
return true;
}
_buffer->reserve(size);
return _buffer->capacity() >= size;
}
@ -169,30 +159,44 @@ class AsyncWebSocketControl {
public:
AsyncWebSocketControl(uint8_t opcode, const uint8_t *data = NULL, size_t len = 0, bool mask = false)
: _opcode(opcode), _len(len), _mask(len && mask), _finished(false) {
if (data == NULL)
if (data == NULL) {
_len = 0;
}
if (_len) {
if (_len > 125)
if (_len > 125) {
_len = 125;
}
_data = (uint8_t *)malloc(_len);
if (_data == NULL)
if (_data == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
_len = 0;
else
} else {
memcpy(_data, data, len);
} else
}
} else {
_data = NULL;
}
}
~AsyncWebSocketControl() {
if (_data != NULL)
if (_data != NULL) {
free(_data);
}
}
bool finished() const { return _finished; }
uint8_t opcode() { return _opcode; }
uint8_t len() { return _len + 2; }
bool finished() const {
return _finished;
}
uint8_t opcode() {
return _opcode;
}
uint8_t len() {
return _len + 2;
}
size_t send(AsyncClient *client) {
_finished = true;
return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len);
@ -203,11 +207,8 @@ class AsyncWebSocketControl {
* AsyncWebSocketMessage Message
*/
AsyncWebSocketMessage::AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask) : _WSbuffer{buffer},
_opcode(opcode & 0x07),
_mask{mask},
_status{_WSbuffer ? WS_MSG_SENDING : WS_MSG_ERROR} {
}
AsyncWebSocketMessage::AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask)
: _WSbuffer{buffer}, _opcode(opcode & 0x07), _mask{mask}, _status{_WSbuffer ? WS_MSG_SENDING : WS_MSG_ERROR} {}
void AsyncWebSocketMessage::ack(size_t len, uint32_t time) {
(void)time;
@ -219,17 +220,20 @@ void AsyncWebSocketMessage::ack(size_t len, uint32_t time) {
}
size_t AsyncWebSocketMessage::send(AsyncClient *client) {
if (!client)
if (!client) {
return 0;
}
if (_status != WS_MSG_SENDING)
if (_status != WS_MSG_SENDING) {
return 0;
}
if (_acked < _ack) {
return 0;
}
if (_sent == _WSbuffer->size()) {
if (_acked == _ack)
if (_acked == _ack) {
_status = WS_MSG_SENT;
}
return 0;
}
if (_sent > _WSbuffer->size()) {
@ -271,8 +275,7 @@ size_t AsyncWebSocketMessage::send(AsyncClient* client) {
const char *AWSC_PING_PAYLOAD = "ESPAsyncWebServer-PING";
const size_t AWSC_PING_PAYLOAD_LEN = 22;
AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest* request, AsyncWebSocket* server)
: _tempObject(NULL) {
AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server) : _tempObject(NULL) {
_client = request->client();
_server = server;
_clientId = _server->_getNextId();
@ -281,12 +284,48 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest* request, Async
_lastMessageTime = millis();
_keepAlivePeriod = 0;
_client->setRxTimeout(0);
_client->onError([](void* r, AsyncClient* c, int8_t error) { (void)c; ((AsyncWebSocketClient*)(r))->_onError(error); }, this);
_client->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; ((AsyncWebSocketClient*)(r))->_onAck(len, time); }, this);
_client->onDisconnect([](void* r, AsyncClient* c) { ((AsyncWebSocketClient*)(r))->_onDisconnect(); delete c; }, this);
_client->onTimeout([](void* r, AsyncClient* c, uint32_t time) { (void)c; ((AsyncWebSocketClient*)(r))->_onTimeout(time); }, this);
_client->onData([](void* r, AsyncClient* c, void* buf, size_t len) { (void)c; ((AsyncWebSocketClient*)(r))->_onData(buf, len); }, this);
_client->onPoll([](void* r, AsyncClient* c) { (void)c; ((AsyncWebSocketClient*)(r))->_onPoll(); }, this);
_client->onError(
[](void *r, AsyncClient *c, int8_t error) {
(void)c;
((AsyncWebSocketClient *)(r))->_onError(error);
},
this
);
_client->onAck(
[](void *r, AsyncClient *c, size_t len, uint32_t time) {
(void)c;
((AsyncWebSocketClient *)(r))->_onAck(len, time);
},
this
);
_client->onDisconnect(
[](void *r, AsyncClient *c) {
((AsyncWebSocketClient *)(r))->_onDisconnect();
delete c;
},
this
);
_client->onTimeout(
[](void *r, AsyncClient *c, uint32_t time) {
(void)c;
((AsyncWebSocketClient *)(r))->_onTimeout(time);
},
this
);
_client->onData(
[](void *r, AsyncClient *c, void *buf, size_t len) {
(void)c;
((AsyncWebSocketClient *)(r))->_onData(buf, len);
},
this
);
_client->onPoll(
[](void *r, AsyncClient *c) {
(void)c;
((AsyncWebSocketClient *)(r))->_onPoll();
},
this
);
delete request;
memset(&_pinfo, 0, sizeof(_pinfo));
}
@ -303,9 +342,10 @@ AsyncWebSocketClient::~AsyncWebSocketClient() {
}
void AsyncWebSocketClient::_clearQueue() {
while (!_messageQueue.empty() && _messageQueue.front().finished())
while (!_messageQueue.empty() && _messageQueue.front().finished()) {
_messageQueue.pop_front();
}
}
void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
_lastMessageTime = millis();
@ -321,8 +361,9 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
if (_status == WS_DISCONNECTING && head.opcode() == WS_DISCONNECT) {
_controlQueue.pop_front();
_status = WS_DISCONNECTED;
if (_client)
if (_client) {
_client->close(true);
}
return;
}
_controlQueue.pop_front();
@ -339,8 +380,9 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
}
void AsyncWebSocketClient::_onPoll() {
if (!_client)
if (!_client) {
return;
}
#ifdef ESP32
std::unique_lock<std::mutex> lock(_lock);
@ -357,12 +399,14 @@ void AsyncWebSocketClient::_onPoll() {
void AsyncWebSocketClient::_runQueue() {
// all calls to this method MUST be protected by a mutex lock!
if (!_client)
if (!_client) {
return;
}
_clearQueue();
if (!_controlQueue.empty() && (_messageQueue.empty() || _messageQueue.front().betweenFrames()) && webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front().len() - 1)) {
if (!_controlQueue.empty() && (_messageQueue.empty() || _messageQueue.front().betweenFrames())
&& webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front().len() - 1)) {
_controlQueue.front().send(_client);
} else if (!_messageQueue.empty() && _messageQueue.front().betweenFrames() && webSocketSendFrameWindow(_client)) {
_messageQueue.front().send(_client);
@ -391,8 +435,9 @@ bool AsyncWebSocketClient::canSend() const {
}
bool AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t *data, size_t len, bool mask) {
if (!_client)
if (!_client) {
return false;
}
#ifdef ESP32
std::lock_guard<std::mutex> lock(_lock);
@ -400,15 +445,17 @@ bool AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t* data, si
_controlQueue.emplace_back(opcode, data, len, mask);
if (_client && _client->canSend())
if (_client && _client->canSend()) {
_runQueue();
}
return true;
}
bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask) {
if (!_client || buffer->size() == 0 || _status != WS_CONNECTED)
if (!_client || buffer->size() == 0 || _status != WS_CONNECTED) {
return false;
}
#ifdef ESP32
std::lock_guard<std::mutex> lock(_lock);
@ -418,8 +465,9 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint
if (closeWhenFull) {
_status = WS_DISCONNECTED;
if (_client)
if (_client) {
_client->close(true);
}
#ifdef ESP8266
ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: closing connection\n");
@ -440,15 +488,17 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint
_messageQueue.emplace_back(buffer, opcode, mask);
if (_client && _client->canSend())
if (_client && _client->canSend()) {
_runQueue();
}
return true;
}
void AsyncWebSocketClient::close(uint16_t code, const char *message) {
if (_status != WS_CONNECTED)
if (_status != WS_CONNECTED) {
return;
}
_status = WS_DISCONNECTING;
@ -456,8 +506,9 @@ void AsyncWebSocketClient::close(uint16_t code, const char* message) {
uint8_t packetLen = 2;
if (message != NULL) {
size_t mlen = strlen(message);
if (mlen > 123)
if (mlen > 123) {
mlen = 123;
}
packetLen += mlen;
}
char *buf = (char *)malloc(packetLen);
@ -470,6 +521,11 @@ void AsyncWebSocketClient::close(uint16_t code, const char* message) {
_queueControl(WS_DISCONNECT, (uint8_t *)buf, packetLen);
free(buf);
return;
} else {
#ifdef ESP32
log_e("Failed to allocate");
_client->abort();
#endif
}
}
_queueControl(WS_DISCONNECT);
@ -484,8 +540,9 @@ void AsyncWebSocketClient::_onError(int8_t) {
}
void AsyncWebSocketClient::_onTimeout(uint32_t time) {
if (!_client)
if (!_client) {
return;
}
// Serial.println("onTime");
(void)time;
_client->close(true);
@ -522,12 +579,14 @@ void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
plen -= 2;
} else if (_pinfo.len == 127 && plen >= 8) {
_pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56;
_pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32
| (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56;
data += 8;
plen -= 8;
}
if (_pinfo.masked && plen >= 4) { // if ws.close() is called, Safari sends a close frame with plen 2 and masked bit set. We must not decrement plen which is already 0.
if (_pinfo.masked
&& plen >= 4) { // if ws.close() is called, Safari sends a close frame with plen 2 and masked bit set. We must not decrement plen which is already 0.
memcpy(_pinfo.mask, data, 4);
data += 4;
plen -= 4;
@ -538,9 +597,10 @@ void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
const auto datalast = data[datalen];
if (_pinfo.masked) {
for (size_t i = 0; i < datalen; i++)
for (size_t i = 0; i < datalen; i++) {
data[i] ^= _pinfo.mask[(_pinfo.index + i) % 4];
}
}
if ((datalen + _pinfo.index) < _pinfo.len) {
_pstate = 1;
@ -551,8 +611,9 @@ void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
_pinfo.num = 0;
}
}
if (datalen > 0)
if (datalen > 0) {
_server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen);
}
_pinfo.index += datalen;
} else if ((datalen + _pinfo.index) == _pinfo.len) {
@ -567,27 +628,31 @@ void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
}
if (_status == WS_DISCONNECTING) {
_status = WS_DISCONNECTED;
if (_client)
if (_client) {
_client->close(true);
}
} else {
_status = WS_DISCONNECTING;
if (_client)
if (_client) {
_client->ackLater();
}
_queueControl(WS_DISCONNECT, data, datalen);
}
} else if (_pinfo.opcode == WS_PING) {
_server->_handleEvent(this, WS_EVT_PING, NULL, NULL, 0);
_queueControl(WS_PONG, data, datalen);
} else if (_pinfo.opcode == WS_PONG) {
if (datalen != AWSC_PING_PAYLOAD_LEN || memcmp(AWSC_PING_PAYLOAD, data, AWSC_PING_PAYLOAD_LEN) != 0)
if (datalen != AWSC_PING_PAYLOAD_LEN || memcmp(AWSC_PING_PAYLOAD, data, AWSC_PING_PAYLOAD_LEN) != 0) {
_server->_handleEvent(this, WS_EVT_PONG, NULL, NULL, 0);
}
} else if (_pinfo.opcode < WS_DISCONNECT) { // continuation or text/binary frame
_server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen);
if (_pinfo.final)
if (_pinfo.final) {
_pinfo.num = 0;
else
} else {
_pinfo.num += 1;
}
}
} else {
// os_printf("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len);
// what should we do?
@ -595,8 +660,9 @@ void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) {
}
// restore byte as _handleEvent may have added a null terminator i.e., data[len] = 0;
if (datalen)
if (datalen) {
data[datalen] = datalast;
}
data += datalen;
plen -= datalen;
@ -609,13 +675,15 @@ size_t AsyncWebSocketClient::printf(const char* format, ...) {
size_t len = vsnprintf(nullptr, 0, format, arg);
va_end(arg);
if (len == 0)
if (len == 0) {
return 0;
}
char *buffer = new char[len + 1];
if (!buffer)
if (!buffer) {
return 0;
}
va_start(arg, format);
len = vsnprintf(buffer, len + 1, format, arg);
@ -633,13 +701,15 @@ size_t AsyncWebSocketClient::printf_P(PGM_P formatP, ...) {
size_t len = vsnprintf_P(nullptr, 0, formatP, arg);
va_end(arg);
if (len == 0)
if (len == 0) {
return 0;
}
char *buffer = new char[len + 1];
if (!buffer)
if (!buffer) {
return 0;
}
va_start(arg, formatP);
len = vsnprintf_P(buffer, len + 1, formatP, arg);
@ -657,7 +727,7 @@ namespace {
std::memcpy(buffer->data(), message, len);
return buffer;
}
}
} // namespace
bool AsyncWebSocketClient::text(AsyncWebSocketMessageBuffer *buffer) {
bool enqueued = false;
@ -694,8 +764,9 @@ bool AsyncWebSocketClient::text(const __FlashStringHelper* data) {
size_t n = 0;
while (1) {
if (pgm_read_byte(p + n) == 0)
if (pgm_read_byte(p + n) == 0) {
break;
}
n += 1;
}
@ -755,15 +826,17 @@ bool AsyncWebSocketClient::binary(const __FlashStringHelper* data, size_t len) {
#endif
IPAddress AsyncWebSocketClient::remoteIP() const {
if (!_client)
if (!_client) {
return IPAddress((uint32_t)0U);
}
return _client->remoteIP();
}
uint16_t AsyncWebSocketClient::remotePort() const {
if (!_client)
if (!_client) {
return 0;
}
return _client->remotePort();
}
@ -785,48 +858,62 @@ AsyncWebSocketClient* AsyncWebSocket::_newClient(AsyncWebServerRequest* request)
}
bool AsyncWebSocket::availableForWriteAll() {
return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient& c) { return c.queueIsFull(); });
return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) {
return c.queueIsFull();
});
}
bool AsyncWebSocket::availableForWrite(uint32_t id) {
const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [id](const AsyncWebSocketClient& c) { return c.id() == id; });
if (iter == std::end(_clients))
const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [id](const AsyncWebSocketClient &c) {
return c.id() == id;
});
if (iter == std::end(_clients)) {
return true;
}
return !iter->queueIsFull();
}
size_t AsyncWebSocket::count() const {
return std::count_if(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient& c) { return c.status() == WS_CONNECTED; });
return std::count_if(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) {
return c.status() == WS_CONNECTED;
});
}
AsyncWebSocketClient *AsyncWebSocket::client(uint32_t id) {
const auto iter = std::find_if(_clients.begin(), _clients.end(), [id](const AsyncWebSocketClient& c) { return c.id() == id && c.status() == WS_CONNECTED; });
if (iter == std::end(_clients))
const auto iter = std::find_if(_clients.begin(), _clients.end(), [id](const AsyncWebSocketClient &c) {
return c.id() == id && c.status() == WS_CONNECTED;
});
if (iter == std::end(_clients)) {
return nullptr;
}
return &(*iter);
}
void AsyncWebSocket::close(uint32_t id, uint16_t code, const char *message) {
if (AsyncWebSocketClient* c = client(id))
if (AsyncWebSocketClient *c = client(id)) {
c->close(code, message);
}
}
void AsyncWebSocket::closeAll(uint16_t code, const char *message) {
for (auto& c : _clients)
if (c.status() == WS_CONNECTED)
for (auto &c : _clients) {
if (c.status() == WS_CONNECTED) {
c.close(code, message);
}
}
}
void AsyncWebSocket::cleanupClients(uint16_t maxClients) {
if (count() > maxClients)
if (count() > maxClients) {
_clients.front().close();
}
for (auto iter = std::begin(_clients); iter != std::end(_clients);) {
if (iter->shouldBeDeleted())
iter = _clients.erase(iter);
else
iter++;
for (auto i = _clients.begin(); i != _clients.end(); ++i) {
if (i->shouldBeDeleted()) {
_clients.erase(i);
break;
}
}
}
@ -838,11 +925,13 @@ bool AsyncWebSocket::ping(uint32_t id, const uint8_t* data, size_t len) {
AsyncWebSocket::SendStatus AsyncWebSocket::pingAll(const uint8_t *data, size_t len) {
size_t hit = 0;
size_t miss = 0;
for (auto& c : _clients)
if (c.status() == WS_CONNECTED && c.ping(data, len))
for (auto &c : _clients) {
if (c.status() == WS_CONNECTED && c.ping(data, len)) {
hit++;
else
} else {
miss++;
}
}
return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
}
@ -866,8 +955,9 @@ bool AsyncWebSocket::text(uint32_t id, const __FlashStringHelper* data) {
size_t n = 0;
while (true) {
if (pgm_read_byte(p + n) == 0)
if (pgm_read_byte(p + n) == 0) {
break;
}
n += 1;
}
@ -914,8 +1004,9 @@ AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const __FlashStringHelper* da
size_t n = 0;
while (1) {
if (pgm_read_byte(p + n) == 0)
if (pgm_read_byte(p + n) == 0) {
break;
}
n += 1;
}
@ -942,11 +1033,13 @@ AsyncWebSocket::SendStatus AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer*
AsyncWebSocket::SendStatus AsyncWebSocket::textAll(AsyncWebSocketSharedBuffer buffer) {
size_t hit = 0;
size_t miss = 0;
for (auto& c : _clients)
if (c.status() == WS_CONNECTED && c.text(buffer))
for (auto &c : _clients) {
if (c.status() == WS_CONNECTED && c.text(buffer)) {
hit++;
else
} else {
miss++;
}
}
return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
}
@ -1029,11 +1122,13 @@ AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer
AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(AsyncWebSocketSharedBuffer buffer) {
size_t hit = 0;
size_t miss = 0;
for (auto& c : _clients)
if (c.status() == WS_CONNECTED && c.binary(buffer))
for (auto &c : _clients) {
if (c.status() == WS_CONNECTED && c.binary(buffer)) {
hit++;
else
} else {
miss++;
}
}
return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
}
@ -1055,13 +1150,15 @@ size_t AsyncWebSocket::printfAll(const char* format, ...) {
size_t len = vsnprintf(nullptr, 0, format, arg);
va_end(arg);
if (len == 0)
if (len == 0) {
return 0;
}
char *buffer = new char[len + 1];
if (!buffer)
if (!buffer) {
return 0;
}
va_start(arg, format);
len = vsnprintf(buffer, len + 1, format, arg);
@ -1091,13 +1188,15 @@ size_t AsyncWebSocket::printfAll_P(PGM_P formatP, ...) {
size_t len = vsnprintf_P(nullptr, 0, formatP, arg);
va_end(arg);
if (len == 0)
if (len == 0) {
return 0;
}
char *buffer = new char[len + 1];
if (!buffer)
if (!buffer) {
return 0;
}
va_start(arg, formatP);
len = vsnprintf_P(buffer, len + 1, formatP, arg);
@ -1155,6 +1254,13 @@ void AsyncWebSocket::handleRequest(AsyncWebServerRequest* request) {
}
const AsyncWebHeader *key = request->getHeader(WS_STR_KEY);
AsyncWebServerResponse *response = new AsyncWebSocketResponse(key->value(), this);
if (response == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return;
}
if (request->hasHeader(WS_STR_PROTOCOL)) {
const AsyncWebHeader *protocol = request->getHeader(WS_STR_PROTOCOL);
// ToDo: check protocol
@ -1164,23 +1270,11 @@ void AsyncWebSocket::handleRequest(AsyncWebServerRequest* request) {
}
AsyncWebSocketMessageBuffer *AsyncWebSocket::makeBuffer(size_t size) {
AsyncWebSocketMessageBuffer* buffer = new AsyncWebSocketMessageBuffer(size);
if (buffer->length() != size) {
delete buffer;
return nullptr;
} else {
return buffer;
}
return new AsyncWebSocketMessageBuffer(size);
}
AsyncWebSocketMessageBuffer *AsyncWebSocket::makeBuffer(const uint8_t *data, size_t size) {
AsyncWebSocketMessageBuffer* buffer = new AsyncWebSocketMessageBuffer(data, size);
if (buffer->length() != size) {
delete buffer;
return nullptr;
} else {
return buffer;
}
return new AsyncWebSocketMessageBuffer(data, size);
}
/*
@ -1196,11 +1290,14 @@ AsyncWebSocketResponse::AsyncWebSocketResponse(const String& key, AsyncWebSocket
uint8_t hash[20];
char buffer[33];
#if defined(ESP8266) || defined(TARGET_RP2040)
#if defined(ESP8266) || defined(TARGET_RP2040) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(TARGET_RP2350)
sha1(key + WS_STR_UUID, hash);
#else
String k;
k.reserve(key.length() + WS_STR_UUID_LEN);
if (!k.reserve(key.length() + WS_STR_UUID_LEN)) {
log_e("Failed to allocate");
return;
}
k.concat(key);
k.concat(WS_STR_UUID);
SHA1Builder sha1;
@ -1232,8 +1329,9 @@ void AsyncWebSocketResponse::_respond(AsyncWebServerRequest* request) {
size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
(void)time;
if (len)
if (len) {
_server->_newClient(request);
}
return 0;
}

View file

@ -1,29 +1,12 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// 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_
#define ASYNCWEBSOCKET_H_
#include <Arduino.h>
#ifdef ESP32
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h"
#include <AsyncTCP.h>
#include <mutex>
#ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 32
@ -33,14 +16,14 @@
#ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 8
#endif
#elif defined(TARGET_RP2040)
#include <AsyncTCP_RP2040W.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#ifndef WS_MAX_QUEUED_MESSAGES
#define WS_MAX_QUEUED_MESSAGES 32
#endif
#endif
#include "ESPAsyncWebServer.h"
#include <ESPAsyncWebServer.h>
#include <memory>
@ -90,24 +73,32 @@ typedef struct {
uint64_t index;
} AwsFrameInfo;
typedef enum { WS_DISCONNECTED,
typedef enum {
WS_DISCONNECTED,
WS_CONNECTED,
WS_DISCONNECTING } AwsClientStatus;
typedef enum { WS_CONTINUATION,
WS_DISCONNECTING
} AwsClientStatus;
typedef enum {
WS_CONTINUATION,
WS_TEXT,
WS_BINARY,
WS_DISCONNECT = 0x08,
WS_PING,
WS_PONG } AwsFrameType;
typedef enum { WS_MSG_SENDING,
WS_PONG
} AwsFrameType;
typedef enum {
WS_MSG_SENDING,
WS_MSG_SENT,
WS_MSG_ERROR } AwsMessageStatus;
typedef enum { WS_EVT_CONNECT,
WS_MSG_ERROR
} AwsMessageStatus;
typedef enum {
WS_EVT_CONNECT,
WS_EVT_DISCONNECT,
WS_EVT_PING,
WS_EVT_PONG,
WS_EVT_ERROR,
WS_EVT_DATA } AwsEventType;
WS_EVT_DATA
} AwsEventType;
class AsyncWebSocketMessageBuffer {
friend AsyncWebSocket;
@ -122,8 +113,12 @@ class AsyncWebSocketMessageBuffer {
AsyncWebSocketMessageBuffer(const uint8_t *data, size_t size);
//~AsyncWebSocketMessageBuffer();
bool reserve(size_t size);
uint8_t* get() { return _buffer->data(); }
size_t length() const { return _buffer->size(); }
uint8_t *get() {
return _buffer->data();
}
size_t length() const {
return _buffer->size();
}
};
class AsyncWebSocketMessage {
@ -139,8 +134,12 @@ class AsyncWebSocketMessage {
public:
AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
bool finished() const { return _status != WS_MSG_SENDING; }
bool betweenFrames() const { return _acked == _ack; }
bool finished() const {
return _status != WS_MSG_SENDING;
}
bool betweenFrames() const {
return _acked == _ack;
}
void ack(size_t len, uint32_t time);
size_t send(AsyncClient *client);
@ -177,13 +176,27 @@ class AsyncWebSocketClient {
~AsyncWebSocketClient();
// client id increments for the given server
uint32_t id() const { return _clientId; }
AwsClientStatus status() const { return _status; }
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; }
uint32_t id() const {
return _clientId;
}
AwsClientStatus status() const {
return _status;
}
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.
// This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection.
@ -203,13 +216,19 @@ class AsyncWebSocketClient {
// Use cases:,
// - 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.
void setCloseClientOnQueueFull(bool close) { closeWhenFull = close; }
bool willCloseClientOnQueueFull() const { return closeWhenFull; }
void setCloseClientOnQueueFull(bool close) {
closeWhenFull = close;
}
bool willCloseClientOnQueueFull() const {
return closeWhenFull;
}
IPAddress remoteIP() const;
uint16_t remotePort() const;
bool shouldBeDeleted() const { return !_client; }
bool shouldBeDeleted() const {
return !_client;
}
// control frames
void close(uint16_t code = 0, const char *message = NULL);
@ -224,7 +243,9 @@ class AsyncWebSocketClient {
}
// 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;
size_t queueLen() const;
@ -287,15 +308,23 @@ class AsyncWebSocket : public AsyncWebHandler {
explicit AsyncWebSocket(const char *url) : _url(url), _cNextId(1), _enabled(true) {}
AsyncWebSocket(const String &url) : _url(url), _cNextId(1), _enabled(true) {}
~AsyncWebSocket(){};
const char* url() const { return _url.c_str(); }
void enable(bool e) { _enabled = e; }
bool enabled() const { return _enabled; }
const char *url() const {
return _url.c_str();
}
void enable(bool e) {
_enabled = e;
}
bool enabled() const {
return _enabled;
}
bool availableForWriteAll();
bool availableForWrite(uint32_t id);
size_t count() const;
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 closeAll(uint16_t code = 0, const char *message = NULL);
@ -344,11 +373,17 @@ class AsyncWebSocket : public AsyncWebHandler {
size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
#endif
void onEvent(AwsEventHandler handler) { _eventHandler = handler; }
void handleHandshake(AwsHandshakeHandler handler) { _handshakeHandler = handler; }
void onEvent(AwsEventHandler handler) {
_eventHandler = handler;
}
void handleHandshake(AwsHandshakeHandler handler) {
_handshakeHandler = handler;
}
// system callbacks (do not call)
uint32_t _getNextId() { return _cNextId++; }
uint32_t _getNextId() {
return _cNextId++;
}
AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request);
void _handleEvent(AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);
bool canHandle(AsyncWebServerRequest *request) const override final;
@ -358,7 +393,9 @@ class AsyncWebSocket : public AsyncWebHandler {
AsyncWebSocketMessageBuffer *makeBuffer(size_t size = 0);
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
@ -371,7 +408,9 @@ class AsyncWebSocketResponse : public AsyncWebServerResponse {
AsyncWebSocketResponse(const String &key, AsyncWebSocket *server);
void _respond(AsyncWebServerRequest *request);
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
bool _sourceValid() const { return true; }
bool _sourceValid() const {
return true;
}
};
#endif /* ASYNCWEBSOCKET_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)
: _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
#include <ChunkPrint.h>
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) {
if (_to_skip > 0) {

View file

@ -1,3 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#ifndef CHUNKPRINT_H
#define CHUNKPRINT_H
@ -13,6 +16,8 @@ class ChunkPrint : public Print {
public:
ChunkPrint(uint8_t *destination, size_t from, size_t len);
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

View file

@ -1,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// 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 _ESPAsyncWebServer_H_
#define _ESPAsyncWebServer_H_
@ -32,13 +15,13 @@
#include <vector>
#ifdef ESP32
#include "../../ESP32Async-AsyncTCP/src/AsyncTCP.h"
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040)
#include <AsyncTCP_RP2040W.h>
#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
#include <RPAsyncTCP.h>
#include <HTTP_Method.h>
#include <WiFi.h>
#include <http_parser.h>
@ -48,10 +31,7 @@
#include "literals.h"
#define ASYNCWEBSERVER_VERSION "3.6.2"
#define ASYNCWEBSERVER_VERSION_MAJOR 3
#define ASYNCWEBSERVER_VERSION_MINOR 6
#define ASYNCWEBSERVER_VERSION_REVISION 2
#include "AsyncWebServerVersion.h"
#define ASYNCWEBSERVER_FORK_ESP32Async
#ifdef ASYNCWEBSERVER_REGEX
@ -78,6 +58,10 @@ class AsyncCallbackWebHandler;
class AsyncResponseStream;
class AsyncMiddlewareChain;
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
typedef enum http_method WebRequestMethod;
#else
#ifndef WEBSERVER_H
typedef enum {
HTTP_GET = 0b00000001,
HTTP_POST = 0b00000010,
@ -88,6 +72,8 @@ typedef enum {
HTTP_OPTIONS = 0b01000000,
HTTP_ANY = 0b01111111,
} WebRequestMethod;
#endif
#endif
#ifndef HAVE_FS_FILE_OPEN_MODE
namespace fs {
@ -97,7 +83,7 @@ namespace fs {
static const char *write;
static const char *append;
};
};
}; // namespace fs
#else
#include "FileOpenMode.h"
#endif
@ -122,12 +108,23 @@ class AsyncWebParameter {
bool _isFile;
public:
AsyncWebParameter(const String& name, const String& value, bool form = false, bool file = false, size_t size = 0) : _name(name), _value(value), _size(size), _isForm(form), _isFile(file) {}
const String& name() const { return _name; }
const String& value() const { return _value; }
size_t size() const { return _size; }
bool isPost() const { return _isForm; }
bool isFile() const { return _isFile; }
AsyncWebParameter(const String &name, const String &value, bool form = false, bool file = false, size_t size = 0)
: _name(name), _value(value), _size(size), _isForm(form), _isFile(file) {}
const String &name() const {
return _name;
}
const String &value() const {
return _value;
}
size_t size() const {
return _size;
}
bool isPost() const {
return _isForm;
}
bool isFile() const {
return _isFile;
}
};
/*
@ -147,8 +144,12 @@ class AsyncWebHeader {
AsyncWebHeader &operator=(const AsyncWebHeader &) = default;
const String& name() const { return _name; }
const String& value() const { return _value; }
const String &name() const {
return _name;
}
const String &value() const {
return _value;
}
String toString() const;
};
@ -156,12 +157,14 @@ class AsyncWebHeader {
* REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect
* */
typedef enum { RCT_NOT_USED = -1,
typedef enum {
RCT_NOT_USED = -1,
RCT_DEFAULT = 0,
RCT_HTTP,
RCT_WS,
RCT_EVENT,
RCT_MAX } RequestedConnectionType;
RCT_MAX
} RequestedConnectionType;
// this enum is similar to Arduino WebServer's AsyncAuthType and PsychicHttp
typedef enum {
@ -176,6 +179,8 @@ typedef enum {
typedef std::function<size_t(uint8_t *, size_t, size_t)> AwsResponseFiller;
typedef std::function<String(const String &)> AwsTemplateProcessor;
using AsyncWebServerRequestPtr = std::weak_ptr<AsyncWebServerRequest>;
class AsyncWebServerRequest {
using File = fs::File;
using FS = fs::FS;
@ -189,8 +194,9 @@ class AsyncWebServerRequest {
AsyncWebServerResponse *_response;
ArDisconnectHandler _onDisconnectfn;
// response is sent
bool _sent = false;
bool _sent = false; // response is sent
bool _paused = false; // request is paused (request continuation)
std::shared_ptr<AsyncWebServerRequest> _this; // shared pointer to this request
String _temp;
uint8_t _parseState;
@ -212,7 +218,7 @@ class AsyncWebServerRequest {
std::list<AsyncWebHeader> _headers;
std::list<AsyncWebParameter> _params;
std::vector<String> _pathParams;
std::list<String> _pathParams;
std::unordered_map<const char *, String, std::hash<const char *>, std::equal_to<const char *>> _attributes;
@ -248,6 +254,9 @@ class AsyncWebServerRequest {
void _handleUploadByte(uint8_t data, bool last);
void _handleUploadEnd();
void _send();
void _runMiddlewareChain();
public:
File _tempFile;
void *_tempObject;
@ -255,23 +264,48 @@ class AsyncWebServerRequest {
AsyncWebServerRequest(AsyncWebServer *, AsyncClient *);
~AsyncWebServerRequest();
AsyncClient* client() { return _client; }
uint8_t version() const { return _version; }
WebRequestMethodComposite method() const { return _method; }
const String& url() const { return _url; }
const String& host() const { return _host; }
const String& contentType() const { return _contentType; }
size_t contentLength() const { return _contentLength; }
bool multipart() const { return _isMultipart; }
AsyncClient *client() {
return _client;
}
uint8_t version() const {
return _version;
}
WebRequestMethodComposite method() const {
return _method;
}
const String &url() const {
return _url;
}
const String &host() const {
return _host;
}
const String &contentType() const {
return _contentType;
}
size_t contentLength() const {
return _contentLength;
}
bool multipart() const {
return _isMultipart;
}
const char *methodToString() const;
const char *requestedConnTypeToString() const;
RequestedConnectionType requestedConnType() const { return _reqconntype; }
bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED) const;
bool isWebSocketUpgrade() const { return _method == HTTP_GET && isExpectedRequestedConnType(RCT_WS); }
bool isSSE() const { return _method == HTTP_GET && isExpectedRequestedConnType(RCT_EVENT); }
bool isHTTP() const { return isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP); }
RequestedConnectionType requestedConnType() const {
return _reqconntype;
}
bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED)
const;
bool isWebSocketUpgrade() const {
return _method == HTTP_GET && isExpectedRequestedConnType(RCT_WS);
}
bool isSSE() const {
return _method == HTTP_GET && isExpectedRequestedConnType(RCT_EVENT);
}
bool isHTTP() const {
return isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP);
}
void onDisconnect(ArDisconnectHandler fn);
// hash is the string representation of:
@ -279,10 +313,17 @@ class AsyncWebServerRequest {
// user:realm:md5(user:realm:pass) for digest
bool authenticate(const char *hash) const;
bool authenticate(const char *username, const char *credentials, const char *realm = NULL, bool isHash = false) const;
void requestAuthentication(const char* realm = nullptr, bool isDigest = true) { requestAuthentication(isDigest ? AsyncAuthType::AUTH_DIGEST : AsyncAuthType::AUTH_BASIC, realm); }
void requestAuthentication(const char *realm = nullptr, bool isDigest = true) {
requestAuthentication(isDigest ? AsyncAuthType::AUTH_DIGEST : AsyncAuthType::AUTH_BASIC, realm);
}
void requestAuthentication(AsyncAuthType method, const char *realm = nullptr, const char *_authFailMsg = nullptr);
void setHandler(AsyncWebHandler* handler) { _handler = handler; }
// IMPORTANT: this method is for internal use ONLY
// Please do not use it!
// It can be removed or modified at any time without notice
void setHandler(AsyncWebHandler *handler) {
_handler = handler;
}
#ifndef ESP8266
[[deprecated("All headers are now collected. Use removeHeader(name) or AsyncHeaderFreeMiddleware if you really need to free some headers.")]]
@ -296,48 +337,80 @@ class AsyncWebServerRequest {
}
/**
* @brief issue HTTP redirect responce with Location header
* @brief issue HTTP redirect response with Location header
*
* @param url - url to redirect to
* @param code - responce code, default is 302 : temporary redirect
* @param code - response code, default is 302 : temporary redirect
*/
void redirect(const char *url, int code = 302);
void redirect(const String& url, int code = 302) { return redirect(url.c_str(), code); };
void redirect(const String &url, int code = 302) {
return redirect(url.c_str(), code);
};
void send(AsyncWebServerResponse *response);
AsyncWebServerResponse* getResponse() const { return _response; }
AsyncWebServerResponse *getResponse() const {
return _response;
}
void send(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, callback)); }
void send(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType.c_str(), content, callback)); }
void send(int code, const String& contentType, const String& content, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType.c_str(), content.c_str(), callback)); }
void send(int code, const char *contentType = asyncsrv::empty, const char *content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(code, contentType, content, callback));
}
void send(int code, const String &contentType, const char *content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(code, contentType.c_str(), content, callback));
}
void send(int code, const String &contentType, const String &content, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(code, contentType.c_str(), content.c_str(), callback));
}
void send(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, len, callback)); }
void send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, len, callback)); }
void send(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(code, contentType, content, len, callback));
}
void send(int code, const String &contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(code, contentType, content, len, callback));
}
void send(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) {
if (fs.exists(path) || (!download && fs.exists(path + asyncsrv::T__gz))) {
send(beginResponse(fs, path, contentType, download, callback));
} else
} else {
send(404);
}
void send(FS& fs, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callback = nullptr) { send(fs, path, contentType.c_str(), download, callback); }
}
void send(FS &fs, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr) {
send(fs, path, contentType.c_str(), download, callback);
}
void send(File content, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) {
if (content) {
send(beginResponse(content, path, contentType, download, callback));
} else
} else {
send(404);
}
void send(File content, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callback = nullptr) { send(content, path, contentType.c_str(), download, callback); }
}
void send(File content, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr) {
send(content, path, contentType.c_str(), download, callback);
}
void send(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(stream, contentType, len, callback)); }
void send(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(stream, contentType, len, callback)); }
void send(Stream &stream, const char *contentType, size_t len, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(stream, contentType, len, callback));
}
void send(Stream &stream, const String &contentType, size_t len, AwsTemplateProcessor callback = nullptr) {
send(beginResponse(stream, contentType, len, callback));
}
void send(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginResponse(contentType, len, callback, templateCallback)); }
void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginResponse(contentType, len, callback, templateCallback)); }
void send(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
send(beginResponse(contentType, len, callback, templateCallback));
}
void send(const String &contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
send(beginResponse(contentType, len, callback, templateCallback));
}
void sendChunked(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginChunkedResponse(contentType, callback, templateCallback)); }
void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginChunkedResponse(contentType, callback, templateCallback)); }
void sendChunked(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
send(beginChunkedResponse(contentType, callback, templateCallback));
}
void sendChunked(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
send(beginChunkedResponse(contentType, callback, templateCallback));
}
#ifndef ESP8266
[[deprecated("Replaced by send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr)")]]
@ -356,30 +429,51 @@ class AsyncWebServerRequest {
}
#endif
AsyncWebServerResponse* beginResponse(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse* beginResponse(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) { return beginResponse(code, contentType.c_str(), content, callback); }
AsyncWebServerResponse* beginResponse(int code, const String& contentType, const String& content, AwsTemplateProcessor callback = nullptr) { return beginResponse(code, contentType.c_str(), content.c_str(), callback); }
AsyncWebServerResponse *
beginResponse(int code, const char *contentType = asyncsrv::empty, const char *content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse *beginResponse(int code, const String &contentType, const char *content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) {
return beginResponse(code, contentType.c_str(), content, callback);
}
AsyncWebServerResponse *beginResponse(int code, const String &contentType, const String &content, AwsTemplateProcessor callback = nullptr) {
return beginResponse(code, contentType.c_str(), content.c_str(), callback);
}
AsyncWebServerResponse *beginResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse* beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { return beginResponse(code, contentType.c_str(), content, len, callback); }
AsyncWebServerResponse *beginResponse(int code, const String &contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr) {
return beginResponse(code, contentType.c_str(), content, len, callback);
}
AsyncWebServerResponse* beginResponse(FS& fs, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse* beginResponse(FS& fs, const String& path, const String& contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { return beginResponse(fs, path, contentType.c_str(), download, callback); }
AsyncWebServerResponse *
beginResponse(FS &fs, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse *
beginResponse(FS &fs, const String &path, const String &contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) {
return beginResponse(fs, path, contentType.c_str(), download, callback);
}
AsyncWebServerResponse* beginResponse(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse* beginResponse(File content, const String& path, const String& contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { return beginResponse(content, path, contentType.c_str(), download, callback); }
AsyncWebServerResponse *
beginResponse(File content, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse *
beginResponse(File content, const String &path, const String &contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) {
return beginResponse(content, path, contentType.c_str(), download, callback);
}
AsyncWebServerResponse *beginResponse(Stream &stream, const char *contentType, size_t len, AwsTemplateProcessor callback = nullptr);
AsyncWebServerResponse* beginResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) { return beginResponse(stream, contentType.c_str(), len, callback); }
AsyncWebServerResponse *beginResponse(Stream &stream, const String &contentType, size_t len, AwsTemplateProcessor callback = nullptr) {
return beginResponse(stream, contentType.c_str(), len, callback);
}
AsyncWebServerResponse *beginResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncWebServerResponse* beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { return beginResponse(contentType.c_str(), len, callback, templateCallback); }
AsyncWebServerResponse *beginResponse(const String &contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
return beginResponse(contentType.c_str(), len, callback, templateCallback);
}
AsyncWebServerResponse *beginChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncWebServerResponse *beginChunkedResponse(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncResponseStream *beginResponseStream(const char *contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE);
AsyncResponseStream* beginResponseStream(const String& contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE) { return beginResponseStream(contentType.c_str(), bufferSize); }
AsyncResponseStream *beginResponseStream(const String &contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE) {
return beginResponseStream(contentType.c_str(), bufferSize);
}
#ifndef ESP8266
[[deprecated("Replaced by beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr)")]]
@ -388,10 +482,37 @@ class AsyncWebServerRequest {
return beginResponse(code, contentType.c_str(), content, len, callback);
}
#ifndef ESP8266
[[deprecated("Replaced by beginResponse(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr)")]]
[[deprecated("Replaced by beginResponse(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr)"
)]]
#endif
AsyncWebServerResponse *beginResponse_P(int code, const String &contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);
/**
* @brief Request Continuation: this function pauses the current request and returns a weak pointer (AsyncWebServerRequestPtr is a std::weak_ptr) to the request in order to reuse it later on.
* The middelware chain will continue to be processed until the end, but no response will be sent.
* To resume operations (send the request), the request must be retrieved from the weak pointer and a send() function must be called.
* AsyncWebServerRequestPtr is the only object allowed to exist the scope of the request handler.
* @warning This function should be called from within the context of a request (in a handler or middleware for example).
* @warning While the request is paused, if the client aborts the request, the latter will be disconnected and deleted.
* So it is the responsibility of the user to check the validity of the request pointer (AsyncWebServerRequestPtr) before using it by calling lock() and/or expired().
*/
AsyncWebServerRequestPtr pause();
bool isPaused() const {
return _paused;
}
/**
* @brief Aborts the request and close the client (RST).
* Mark the request as sent.
* If it was paused, it will be unpaused and it won't be possible to resume it.
*/
void abort();
bool isSent() const {
return _sent;
}
/**
* @brief Get the Request parameter by name
*
@ -402,7 +523,9 @@ class AsyncWebServerRequest {
*/
const AsyncWebParameter *getParam(const char *name, bool post = false, bool file = false) const;
const AsyncWebParameter* getParam(const String& name, bool post = false, bool file = false) const { return getParam(name.c_str(), post, file); };
const AsyncWebParameter *getParam(const String &name, bool post = false, bool file = false) const {
return getParam(name.c_str(), post, file);
};
#ifdef ESP8266
const AsyncWebParameter *getParam(const __FlashStringHelper *data, bool post, bool file) const;
#endif
@ -415,19 +538,25 @@ class AsyncWebServerRequest {
*/
const AsyncWebParameter *getParam(size_t num) const;
size_t args() const { return params(); } // get arguments count
size_t args() const {
return params();
} // get arguments count
// get request argument value by name
const String &arg(const char *name) const;
// get request argument value by name
const String& arg(const String& name) const { return arg(name.c_str()); };
const String &arg(const String &name) const {
return arg(name.c_str());
};
#ifdef ESP8266
const String &arg(const __FlashStringHelper *data) const; // get request argument value by F(name)
#endif
const String &arg(size_t i) const; // get request argument value by number
const String &argName(size_t i) const; // get request argument name by number
bool hasArg(const char *name) const; // check if argument exists
bool hasArg(const String& name) const { return hasArg(name.c_str()); };
bool hasArg(const String &name) const {
return hasArg(name.c_str());
};
#ifdef ESP8266
bool hasArg(const __FlashStringHelper *data) const; // check if F(argument) exists
#endif
@ -436,7 +565,9 @@ class AsyncWebServerRequest {
// get request header value by name
const String &header(const char *name) const;
const String& header(const String& name) const { return header(name.c_str()); };
const String &header(const String &name) const {
return header(name.c_str());
};
#ifdef ESP8266
const String &header(const __FlashStringHelper *data) const; // get request header value by F(name)
@ -449,20 +580,26 @@ class AsyncWebServerRequest {
// check if header exists
bool hasHeader(const char *name) const;
bool hasHeader(const String& name) const { return hasHeader(name.c_str()); };
bool hasHeader(const String &name) const {
return hasHeader(name.c_str());
};
#ifdef ESP8266
bool hasHeader(const __FlashStringHelper *data) const; // check if header exists
#endif
const AsyncWebHeader *getHeader(const char *name) const;
const AsyncWebHeader* getHeader(const String& name) const { return getHeader(name.c_str()); };
const AsyncWebHeader *getHeader(const String &name) const {
return getHeader(name.c_str());
};
#ifdef ESP8266
const AsyncWebHeader *getHeader(const __FlashStringHelper *data) const;
#endif
const AsyncWebHeader *getHeader(size_t num) const;
const std::list<AsyncWebHeader>& getHeaders() const { return _headers; }
const std::list<AsyncWebHeader> &getHeaders() const {
return _headers;
}
size_t getHeaderNames(std::vector<const char *> &names) const;
@ -470,24 +607,42 @@ class AsyncWebServerRequest {
// It will free the memory and prevent the header to be seen during request processing.
bool removeHeader(const char *name);
// Remove all request headers.
void removeHeaders() { _headers.clear(); }
void removeHeaders() {
_headers.clear();
}
size_t params() const; // get arguments count
bool hasParam(const char *name, bool post = false, bool file = false) const;
bool hasParam(const String& name, bool post = false, bool file = false) const { return hasParam(name.c_str(), post, file); };
bool hasParam(const String &name, bool post = false, bool file = false) const {
return hasParam(name.c_str(), post, file);
};
#ifdef ESP8266
bool hasParam(const __FlashStringHelper* data, bool post = false, bool file = false) const { return hasParam(String(data).c_str(), post, file); };
bool hasParam(const __FlashStringHelper *data, bool post = false, bool file = false) const {
return hasParam(String(data).c_str(), post, file);
};
#endif
// REQUEST ATTRIBUTES
void setAttribute(const char* name, const char* value) { _attributes[name] = value; }
void setAttribute(const char* name, bool value) { _attributes[name] = value ? "1" : emptyString; }
void setAttribute(const char* name, long value) { _attributes[name] = String(value); }
void setAttribute(const char* name, float value, unsigned int decimalPlaces = 2) { _attributes[name] = String(value, decimalPlaces); }
void setAttribute(const char* name, double value, unsigned int decimalPlaces = 2) { _attributes[name] = String(value, decimalPlaces); }
void setAttribute(const char *name, const char *value) {
_attributes[name] = value;
}
void setAttribute(const char *name, bool value) {
_attributes[name] = value ? "1" : emptyString;
}
void setAttribute(const char *name, long value) {
_attributes[name] = String(value);
}
void setAttribute(const char *name, float value, unsigned int decimalPlaces = 2) {
_attributes[name] = String(value, decimalPlaces);
}
void setAttribute(const char *name, double value, unsigned int decimalPlaces = 2) {
_attributes[name] = String(value, decimalPlaces);
}
bool hasAttribute(const char* name) const { return _attributes.find(name) != _attributes.end(); }
bool hasAttribute(const char *name) const {
return _attributes.find(name) != _attributes.end();
}
const String &getAttribute(const char *name, const String &defaultValue = emptyString) const;
bool getAttribute(const char *name, bool defaultValue) const;
@ -521,7 +676,9 @@ using ArMiddlewareCallback = std::function<void(AsyncWebServerRequest* request,
class AsyncMiddleware {
public:
virtual ~AsyncMiddleware() {}
virtual void run(__unused AsyncWebServerRequest* request, __unused ArMiddlewareNext next) { return next(); };
virtual void run(__unused AsyncWebServerRequest *request, __unused ArMiddlewareNext next) {
return next();
};
private:
friend class AsyncWebHandler;
@ -534,7 +691,9 @@ class AsyncMiddleware {
class AsyncMiddlewareFunction : public AsyncMiddleware {
public:
AsyncMiddlewareFunction(ArMiddlewareCallback fn) : _fn(fn) {}
void run(AsyncWebServerRequest* request, ArMiddlewareNext next) override { return _fn(request, next); };
void run(AsyncWebServerRequest *request, ArMiddlewareNext next) override {
return _fn(request, next);
};
private:
ArMiddlewareCallback _fn;
@ -564,8 +723,12 @@ class AsyncAuthenticationMiddleware : public AsyncMiddleware {
void setPassword(const char *password);
void setPasswordHash(const char *hash);
void setRealm(const char* realm) { _realm = realm; }
void setAuthFailureMessage(const char* message) { _authFailMsg = message; }
void setRealm(const char *realm) {
_realm = realm;
}
void setAuthFailureMessage(const char *message) {
_authFailMsg = message;
}
// set the authentication method to use
// default is AUTH_NONE: no authentication required
@ -575,7 +738,9 @@ class AsyncAuthenticationMiddleware : public AsyncMiddleware {
// AUTH_OTHER: other authentication method
// AUTH_DENIED: always return 401 Unauthorized
// if a method is set but no username or password is set, authentication will be ignored
void setAuthType(AsyncAuthType authMethod) { _authMethod = authMethod; }
void setAuthType(AsyncAuthType authMethod) {
_authMethod = authMethod;
}
// precompute and store the hash value based on the username, password, realm.
// can be used for DIGEST and BASIC to avoid recomputing the hash for each request.
@ -583,7 +748,9 @@ class AsyncAuthenticationMiddleware : public AsyncMiddleware {
bool generateHash();
// returns true if the username and password (or hash) are set
bool hasCredentials() const { return _hasCreds; }
bool hasCredentials() const {
return _hasCreds;
}
bool allowed(AsyncWebServerRequest *request) const;
@ -607,7 +774,9 @@ class AsyncAuthorizationMiddleware : public AsyncMiddleware {
AsyncAuthorizationMiddleware(ArAuthorizeFunction authorizeConnectHandler) : _code(403), _authz(authorizeConnectHandler) {}
AsyncAuthorizationMiddleware(int code, ArAuthorizeFunction authorizeConnectHandler) : _code(code), _authz(authorizeConnectHandler) {}
void run(AsyncWebServerRequest* request, ArMiddlewareNext next) { return _authz && !_authz(request) ? request->send(_code) : next(); }
void run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
return _authz && !_authz(request) ? request->send(_code) : next();
}
private:
int _code;
@ -617,33 +786,47 @@ class AsyncAuthorizationMiddleware : public AsyncMiddleware {
// remove all headers from the incoming request except the ones provided in the constructor
class AsyncHeaderFreeMiddleware : public AsyncMiddleware {
public:
void keep(const char* name) { _toKeep.push_back(name); }
void unKeep(const char* name) { _toKeep.erase(std::remove(_toKeep.begin(), _toKeep.end(), name), _toKeep.end()); }
void keep(const char *name) {
_toKeep.push_back(name);
}
void unKeep(const char *name) {
_toKeep.remove(name);
}
void run(AsyncWebServerRequest *request, ArMiddlewareNext next);
private:
std::vector<const char*> _toKeep;
std::list<const char *> _toKeep;
};
// filter out specific headers from the incoming request
class AsyncHeaderFilterMiddleware : public AsyncMiddleware {
public:
void filter(const char* name) { _toRemove.push_back(name); }
void unFilter(const char* name) { _toRemove.erase(std::remove(_toRemove.begin(), _toRemove.end(), name), _toRemove.end()); }
void filter(const char *name) {
_toRemove.push_back(name);
}
void unFilter(const char *name) {
_toRemove.remove(name);
}
void run(AsyncWebServerRequest *request, ArMiddlewareNext next);
private:
std::vector<const char*> _toRemove;
std::list<const char *> _toRemove;
};
// curl-like logging of incoming requests
class AsyncLoggingMiddleware : public AsyncMiddleware {
public:
void setOutput(Print& output) { _out = &output; }
void setEnabled(bool enabled) { _enabled = enabled; }
bool isEnabled() const { return _enabled && _out; }
void setOutput(Print &output) {
_out = &output;
}
void setEnabled(bool enabled) {
_enabled = enabled;
}
bool isEnabled() const {
return _enabled && _out;
}
void run(AsyncWebServerRequest *request, ArMiddlewareNext next);
@ -655,11 +838,21 @@ class AsyncLoggingMiddleware : public AsyncMiddleware {
// CORS Middleware
class AsyncCorsMiddleware : public AsyncMiddleware {
public:
void setOrigin(const char* origin) { _origin = origin; }
void setMethods(const char* methods) { _methods = methods; }
void setHeaders(const char* headers) { _headers = headers; }
void setAllowCredentials(bool credentials) { _credentials = credentials; }
void setMaxAge(uint32_t seconds) { _maxAge = seconds; }
void setOrigin(const char *origin) {
_origin = origin;
}
void setMethods(const char *methods) {
_methods = methods;
}
void setHeaders(const char *headers) {
_headers = headers;
}
void setAllowCredentials(bool credentials) {
_credentials = credentials;
}
void setMaxAge(uint32_t seconds) {
_maxAge = seconds;
}
void addCORSHeaders(AsyncWebServerResponse *response);
@ -676,8 +869,12 @@ class AsyncCorsMiddleware : public AsyncMiddleware {
// Rate limit Middleware
class AsyncRateLimitMiddleware : public AsyncMiddleware {
public:
void setMaxRequests(size_t maxRequests) { _maxRequests = maxRequests; }
void setWindowSize(uint32_t seconds) { _windowSizeMillis = seconds * 1000; }
void setMaxRequests(size_t maxRequests) {
_maxRequests = maxRequests;
}
void setWindowSize(uint32_t seconds) {
_windowSizeMillis = seconds * 1000;
}
bool isRequestAllowed(uint32_t &retryAfterSeconds);
@ -713,11 +910,21 @@ class AsyncWebRewrite {
_filter = fn;
return *this;
}
bool filter(AsyncWebServerRequest* request) const { return _filter == NULL || _filter(request); }
const String& from(void) const { return _from; }
const String& toUrl(void) const { return _toUrl; }
const String& params(void) const { return _params; }
virtual bool match(AsyncWebServerRequest* request) { return from() == request->url() && filter(request); }
bool filter(AsyncWebServerRequest *request) const {
return _filter == NULL || _filter(request);
}
const String &from(void) const {
return _from;
}
const String &toUrl(void) const {
return _toUrl;
}
const String &params(void) const {
return _params;
}
virtual bool match(AsyncWebServerRequest *request) {
return from() == request->url() && filter(request);
}
};
/*
@ -728,19 +935,42 @@ class AsyncWebHandler : public AsyncMiddlewareChain {
protected:
ArRequestFilterFunction _filter = nullptr;
AsyncAuthenticationMiddleware *_authMiddleware = nullptr;
bool _skipServerMiddlewares = false;
public:
AsyncWebHandler() {}
virtual ~AsyncWebHandler() {}
AsyncWebHandler &setFilter(ArRequestFilterFunction fn);
AsyncWebHandler &setAuthentication(const char *username, const char *password, AsyncAuthType authMethod = AsyncAuthType::AUTH_DIGEST);
AsyncWebHandler& setAuthentication(const String& username, const String& password, AsyncAuthType authMethod = AsyncAuthType::AUTH_DIGEST) { return setAuthentication(username.c_str(), password.c_str(), authMethod); };
bool filter(AsyncWebServerRequest* request) { return _filter == NULL || _filter(request); }
virtual bool canHandle(AsyncWebServerRequest* request __attribute__((unused))) const { return false; }
AsyncWebHandler &setAuthentication(const String &username, const String &password, AsyncAuthType authMethod = AsyncAuthType::AUTH_DIGEST) {
return setAuthentication(username.c_str(), password.c_str(), authMethod);
};
AsyncWebHandler &setSkipServerMiddlewares(bool state) {
_skipServerMiddlewares = state;
return *this;
}
// skip all globally defined server middlewares for this handler and only execute those defined for this handler specifically
AsyncWebHandler &skipServerMiddlewares() {
return setSkipServerMiddlewares(true);
}
bool mustSkipServerMiddlewares() const {
return _skipServerMiddlewares;
}
bool filter(AsyncWebServerRequest *request) {
return _filter == NULL || _filter(request);
}
virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))) const {
return false;
}
virtual void handleRequest(__unused AsyncWebServerRequest *request) {}
virtual void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) {}
virtual void handleUpload(
__unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
__unused bool final
) {}
virtual void handleBody(__unused AsyncWebServerRequest *request, __unused uint8_t *data, __unused size_t len, __unused size_t index, __unused size_t total) {}
virtual bool isRequestHandlerTrivial() const { return true; }
virtual bool isRequestHandlerTrivial() const {
return true;
}
};
/*
@ -770,6 +1000,8 @@ class AsyncWebServerResponse {
size_t _writtenLength;
WebResponseState _state;
static bool headerMustBePresentOnce(const String &name);
public:
static const char *responseCodeToString(int code);
@ -777,17 +1009,30 @@ class AsyncWebServerResponse {
AsyncWebServerResponse();
virtual ~AsyncWebServerResponse() {}
void setCode(int code);
int code() const { return _code; }
int code() const {
return _code;
}
void setContentLength(size_t len);
void setContentType(const String& type) { setContentType(type.c_str()); }
void setContentType(const String &type) {
setContentType(type.c_str());
}
void setContentType(const char *type);
bool addHeader(const char *name, const char *value, bool replaceExisting = true);
bool addHeader(const String& name, const String& value, bool replaceExisting = true) { return addHeader(name.c_str(), value.c_str(), replaceExisting); }
bool addHeader(const char* name, long value, bool replaceExisting = true) { return addHeader(name, String(value), replaceExisting); }
bool addHeader(const String& name, long value, bool replaceExisting = true) { return addHeader(name.c_str(), value, replaceExisting); }
bool addHeader(const String &name, const String &value, bool replaceExisting = true) {
return addHeader(name.c_str(), value.c_str(), replaceExisting);
}
bool addHeader(const char *name, long value, bool replaceExisting = true) {
return addHeader(name, String(value), replaceExisting);
}
bool addHeader(const String &name, long value, bool replaceExisting = true) {
return addHeader(name.c_str(), value, replaceExisting);
}
bool removeHeader(const char *name);
bool removeHeader(const char *name, const char *value);
const AsyncWebHeader *getHeader(const char *name) const;
const std::list<AsyncWebHeader>& getHeaders() const { return _headers; }
const std::list<AsyncWebHeader> &getHeaders() const {
return _headers;
}
#ifndef ESP8266
[[deprecated("Use instead: _assembleHead(String& buffer, uint8_t version)")]]
@ -812,7 +1057,8 @@ class AsyncWebServerResponse {
* */
typedef std::function<void(AsyncWebServerRequest *request)> ArRequestHandlerFunction;
typedef std::function<void(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final)> ArUploadHandlerFunction;
typedef std::function<void(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final)>
ArUploadHandlerFunction;
typedef std::function<void(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction;
class AsyncWebServer : public AsyncMiddlewareChain {
@ -878,14 +1124,21 @@ class AsyncWebServer : public AsyncMiddlewareChain {
AsyncWebHandler &addHandler(AsyncWebHandler *handler);
bool removeHandler(AsyncWebHandler *handler);
AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest) { return on(uri, HTTP_ANY, onRequest); }
AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload = nullptr, ArBodyHandlerFunction onBody = nullptr);
AsyncCallbackWebHandler &on(const char *uri, ArRequestHandlerFunction onRequest) {
return on(uri, HTTP_ANY, onRequest);
}
AsyncCallbackWebHandler &on(
const char *uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload = nullptr,
ArBodyHandlerFunction onBody = nullptr
);
AsyncStaticWebHandler &serveStatic(const char *uri, fs::FS &fs, const char *path, const char *cache_control = NULL);
void onNotFound(ArRequestHandlerFunction fn); // called when handler is not assigned
void onFileUpload(ArUploadHandlerFunction fn); // handle file uploads
void onRequestBody(ArBodyHandlerFunction fn); // handle posts with plain body content (JSON often transmitted this way as a request)
// give access to the handler used to catch all requests, so that middleware can be added to it
AsyncWebHandler &catchAllHandler() const;
void reset(); // remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody
@ -907,8 +1160,12 @@ class DefaultHeaders {
_headers.emplace_back(name, value);
}
ConstIterator begin() const { return _headers.begin(); }
ConstIterator end() const { return _headers.end(); }
ConstIterator begin() const {
return _headers.begin();
}
ConstIterator end() const {
return _headers.end();
}
DefaultHeaders(DefaultHeaders const &) = delete;
DefaultHeaders &operator=(DefaultHeaders const &) = delete;

View file

@ -1,11 +1,16 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#include "WebAuthentication.h"
#include "ESPAsyncWebServer.h"
#include <ESPAsyncWebServer.h>
AsyncMiddlewareChain::~AsyncMiddlewareChain() {
for (AsyncMiddleware* m : _middlewares)
if (m->_freeOnRemoval)
for (AsyncMiddleware *m : _middlewares) {
if (m->_freeOnRemoval) {
delete m;
}
}
}
void AsyncMiddlewareChain::addMiddleware(ArMiddlewareCallback fn) {
AsyncMiddlewareFunction *m = new AsyncMiddlewareFunction(fn);
@ -14,38 +19,48 @@ void AsyncMiddlewareChain::addMiddleware(ArMiddlewareCallback fn) {
}
void AsyncMiddlewareChain::addMiddleware(AsyncMiddleware *middleware) {
if (middleware)
if (middleware) {
_middlewares.emplace_back(middleware);
}
}
void AsyncMiddlewareChain::addMiddlewares(std::vector<AsyncMiddleware *> middlewares) {
for (AsyncMiddleware* m : middlewares)
for (AsyncMiddleware *m : middlewares) {
addMiddleware(m);
}
}
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.
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->_freeOnRemoval)
if (m->_freeOnRemoval) {
delete m;
}
return true;
}
return false;
}),
_middlewares.end());
}
),
_middlewares.end()
);
return size != _middlewares.size();
}
void AsyncMiddlewareChain::_runChain(AsyncWebServerRequest *request, ArMiddlewareNext finalizer) {
if (!_middlewares.size())
if (!_middlewares.size()) {
return finalizer();
}
ArMiddlewareNext next;
std::list<AsyncMiddleware *>::iterator it = _middlewares.begin();
next = [this, &next, &it, request, finalizer]() {
if (it == _middlewares.end())
if (it == _middlewares.end()) {
return finalizer();
}
AsyncMiddleware *m = *it;
it++;
return m->run(request, next);
@ -72,38 +87,50 @@ void AsyncAuthenticationMiddleware::setPasswordHash(const char* hash) {
bool AsyncAuthenticationMiddleware::generateHash() {
// ensure we have all the necessary data
if (!_hasCreds)
if (!_hasCreds) {
return false;
}
// if we already have a hash, do nothing
if (_hash)
if (_hash) {
return false;
}
switch (_authMethod) {
case AsyncAuthType::AUTH_DIGEST:
_credentials = generateDigestHash(_username.c_str(), _credentials.c_str(), _realm.c_str());
if (_credentials.length()) {
_hash = true;
return true;
} else {
return false;
}
case AsyncAuthType::AUTH_BASIC:
_credentials = generateBasicHash(_username.c_str(), _credentials.c_str());
if (_credentials.length()) {
_hash = true;
return true;
default:
} else {
return false;
}
default: return false;
}
}
bool AsyncAuthenticationMiddleware::allowed(AsyncWebServerRequest *request) const {
if (_authMethod == AsyncAuthType::AUTH_NONE)
if (_authMethod == AsyncAuthType::AUTH_NONE) {
return true;
}
if (_authMethod == AsyncAuthType::AUTH_DENIED)
if (_authMethod == AsyncAuthType::AUTH_DENIED) {
return false;
}
if (!_hasCreds)
if (!_hasCreds) {
return true;
}
return request->authenticate(_username.c_str(), _credentials.c_str(), _realm.c_str(), _hash);
}
@ -113,26 +140,29 @@ void AsyncAuthenticationMiddleware::run(AsyncWebServerRequest* request, ArMiddle
}
void AsyncHeaderFreeMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
std::vector<const char*> reqHeaders;
request->getHeaderNames(reqHeaders);
for (const char* h : reqHeaders) {
std::list<const char *> toRemove;
for (auto &h : request->getHeaders()) {
bool keep = false;
for (const char *k : _toKeep) {
if (strcasecmp(h, k) == 0) {
if (strcasecmp(h.name().c_str(), k) == 0) {
keep = true;
break;
}
}
if (!keep) {
request->removeHeader(h);
toRemove.push_back(h.name().c_str());
}
}
for (const char *h : toRemove) {
request->removeHeader(h);
}
next();
}
void AsyncHeaderFilterMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
for (auto it = _toRemove.begin(); it != _toRemove.end(); ++it)
for (auto it = _toRemove.begin(); it != _toRemove.end(); ++it) {
request->removeHeader(*it);
}
next();
}
@ -229,8 +259,9 @@ void AsyncCorsMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext n
bool AsyncRateLimitMiddleware::isRequestAllowed(uint32_t &retryAfterSeconds) {
uint32_t now = millis();
while (!_requestTimes.empty() && _requestTimes.front() <= now - _windowSizeMillis)
while (!_requestTimes.empty() && _requestTimes.front() <= now - _windowSizeMillis) {
_requestTimes.pop_front();
}
_requestTimes.push_back(now);

View file

@ -1,26 +1,9 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// 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 <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>
#else
#include "md5.h"
@ -32,14 +15,16 @@ using namespace asyncsrv;
// Basic Auth hash = base64("username: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 generateBasicHash(username, password).equalsIgnoreCase(hash);
}
String generateBasicHash(const char *username, const char *password) {
if (username == NULL || password == NULL)
if (username == NULL || password == NULL) {
return emptyString;
}
size_t toencodeLen = strlen(username) + strlen(password) + 1;
@ -65,7 +50,7 @@ String generateBasicHash(const char* username, const char* password) {
}
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;
md5.begin();
md5.add(data, len);
@ -75,8 +60,9 @@ static bool getMD5(uint8_t* data, uint16_t len, char* output) { // 33 bytes or m
md5_context_t _ctx;
uint8_t *_buf = (uint8_t *)malloc(16);
if (_buf == NULL)
if (_buf == NULL) {
return false;
}
memset(_buf, 0x00, 16);
MD5Init(&_ctx);
@ -99,8 +85,12 @@ String genRandomMD5() {
uint32_t r = rand();
#endif
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;
}
String res = String(out);
free(out);
return res;
@ -108,8 +98,12 @@ String genRandomMD5() {
static String stringMD5(const String &in) {
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;
}
String res = String(out);
free(out);
return res;
@ -120,27 +114,47 @@ String generateDigestHash(const char* username, const char* password, const char
return emptyString;
}
char *out = (char *)malloc(33);
if (out == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
return emptyString;
}
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(':');
in.concat(realm);
in.concat(':');
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;
}
in = String(out);
free(out);
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) {
// os_printf("AUTH FAIL: missing requred fields\n");
// os_printf("AUTH FAIL: missing required fields\n");
return false;
}

View file

@ -1,23 +1,5 @@
/*
Asynchronous WebServer library for Espressif MCUs
Copyright (c) 2016 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
#ifndef WEB_AUTHENTICATION_H_
#define WEB_AUTHENTICATION_H_
@ -26,7 +8,10 @@
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
String generateDigestHash(const char *username, const char *password, const char *realm);

View file

@ -1,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// 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_
#define ASYNCWEBSERVERHANDLERIMPL_H_
@ -67,7 +50,7 @@ class AsyncStaticWebHandler : public AsyncWebHandler {
AsyncStaticWebHandler &setLastModified(const char *last_modified);
AsyncStaticWebHandler &setLastModified(struct tm *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 &setTemplateProcessor(AwsTemplateProcessor newCallback);
@ -86,16 +69,26 @@ class AsyncCallbackWebHandler : public AsyncWebHandler {
public:
AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {}
void setUri(const String &uri);
void setMethod(WebRequestMethodComposite method) { _method = method; }
void onRequest(ArRequestHandlerFunction fn) { _onRequest = fn; }
void onUpload(ArUploadHandlerFunction fn) { _onUpload = fn; }
void onBody(ArBodyHandlerFunction fn) { _onBody = fn; }
void setMethod(WebRequestMethodComposite method) {
_method = method;
}
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;
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 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_ */

View file

@ -1,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// 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 "WebHandlerImpl.h"
@ -42,10 +25,12 @@ AsyncWebHandler& AsyncWebHandler::setAuthentication(const char* username, const
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) {
// Ensure leading '/'
if (_uri.length() == 0 || _uri[0] != '/')
if (_uri.length() == 0 || _uri[0] != '/') {
_uri = String('/') + _uri;
if (_path.length() == 0 || _path[0] != '/')
}
if (_path.length() == 0 || _path[0] != '/') {
_path = String('/') + _path;
}
// If path ends with '/' we assume a hint that this is a directory to improve performance.
// However - if it does not end with '/' we, can't assume a file, path can still be a directory.
@ -53,11 +38,13 @@ AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char
// Remove the trailing '/' so we can handle default file
// Notice that root will be "" not "/"
if (_uri[_uri.length() - 1] == '/')
if (_uri[_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);
}
}
AsyncStaticWebHandler &AsyncStaticWebHandler::setTryGzipFirst(bool value) {
_tryGzipFirst = value;
@ -105,8 +92,9 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modifi
AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified() {
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 setLastModified(last_modified);
}
@ -124,16 +112,19 @@ bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest* request) const {
path = _path + path;
// 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;
}
// Can't handle if not default file
if (_default_file.length() == 0)
if (_default_file.length() == 0) {
return false;
}
// Try to add default file, ensure there is a trailing '/' ot the path.
if (path.length() == 0 || path[path.length() - 1] != '/')
// Try to add default file, ensure there is a trailing '/' to the path.
if (path.length() == 0 || path[path.length() - 1] != '/') {
path += String('/');
}
path += _default_file;
return const_cast<AsyncStaticWebHandler *>(this)->_searchFile(request, path);
@ -181,6 +172,14 @@ bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest* request, const St
// Extract the file name from the path and keep it in _tempObject
size_t pathLen = path.length();
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());
request->_tempObject = (void *)_tempPath;
}
@ -191,8 +190,9 @@ bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest* request, const St
uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const {
uint8_t w = value;
uint8_t n;
for (n = 0; w != 0; n++)
for (n = 0; w != 0; n++) {
w &= w - 1;
}
return n;
}
@ -212,7 +212,7 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
String etag;
if (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
constexpr size_t len = 1 + 8 * sizeof(time_t);
char buf[len];
@ -222,16 +222,21 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
etag = lw ^ request->_tempFile.size(); // etag combines file size and lastmod timestamp
#endif
} else {
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
etag = String(request->_tempFile.size());
#else
etag = request->_tempFile.size();
#endif
}
bool not_modified = false;
// 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);
else if (_last_modified.length())
} else if (_last_modified.length()) {
not_modified = request->header(T_IMS).equals(_last_modified);
}
AsyncWebServerResponse *response;
@ -242,15 +247,24 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
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());
if (_last_modified.length())
if (_last_modified.length()) {
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());
}
request->send(response);
}
AsyncStaticWebHandler &AsyncStaticWebHandler::setTemplateProcessor(AwsTemplateProcessor newCallback) {
@ -264,8 +278,9 @@ void AsyncCallbackWebHandler::setUri(const String& uri) {
}
bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest *request) const {
if (!_onRequest || !request->isHTTP() || !(_method & request->method()))
if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
return false;
}
#ifdef ASYNCWEBSERVER_REGEX
if (_isRegex) {
@ -284,31 +299,37 @@ bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest* request) const {
if (_uri.length() && _uri.startsWith("/*.")) {
String uriTemplate = String(_uri);
uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf("."));
if (!request->url().endsWith(uriTemplate))
if (!request->url().endsWith(uriTemplate)) {
return false;
}
} else if (_uri.length() && _uri.endsWith("*")) {
String uriTemplate = String(_uri);
uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1);
if (!request->url().startsWith(uriTemplate))
if (!request->url().startsWith(uriTemplate)) {
return false;
} 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 true;
}
void AsyncCallbackWebHandler::handleRequest(AsyncWebServerRequest *request) {
if (_onRequest)
if (_onRequest) {
_onRequest(request);
else
request->send(500);
} else {
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) {
if (_onUpload)
if (_onUpload) {
_onUpload(request, filename, index, data, len, final);
}
}
void AsyncCallbackWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
// ESP_LOGD("AsyncWebServer", "AsyncCallbackWebHandler::handleBody");
if (_onBody)
if (_onBody) {
_onBody(request, data, len, index, total);
}
}

View file

@ -1,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// 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 "WebAuthentication.h"
#include "WebResponseImpl.h"
@ -26,25 +9,84 @@
#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '='))
static void doNotDelete(AsyncWebServerRequest *) {}
using namespace asyncsrv;
enum { PARSE_REQ_START = 0,
enum {
PARSE_REQ_START = 0,
PARSE_REQ_HEADERS = 1,
PARSE_REQ_BODY = 2,
PARSE_REQ_END = 3,
PARSE_REQ_FAIL = 4 };
PARSE_REQ_FAIL = 4
};
AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
: _client(c), _server(s), _handler(NULL), _response(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY), _url(), _host(), _contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false), _isPlainPost(false), _expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), _itemSize(0), _itemName(), _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) {
c->onError([](void* r, AsyncClient* c, int8_t error) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this);
c->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this);
c->onDisconnect([](void* r, AsyncClient* c) { AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onDisconnect(); delete c; }, this);
c->onTimeout([](void* r, AsyncClient* c, uint32_t time) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this);
c->onData([](void* r, AsyncClient* c, void* buf, size_t len) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this);
c->onPoll([](void* r, AsyncClient* c) { (void)c; AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this);
: _client(c), _server(s), _handler(NULL), _response(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY), _url(), _host(),
_contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false), _isPlainPost(false),
_expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), _itemSize(0), _itemName(),
_itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) {
c->onError(
[](void *r, AsyncClient *c, int8_t error) {
(void)c;
// log_e("AsyncWebServerRequest::_onError");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onError(error);
},
this
);
c->onAck(
[](void *r, AsyncClient *c, size_t len, uint32_t time) {
(void)c;
// log_e("AsyncWebServerRequest::_onAck");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onAck(len, time);
},
this
);
c->onDisconnect(
[](void *r, AsyncClient *c) {
// log_e("AsyncWebServerRequest::_onDisconnect");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onDisconnect();
delete c;
},
this
);
c->onTimeout(
[](void *r, AsyncClient *c, uint32_t time) {
(void)c;
// log_e("AsyncWebServerRequest::_onTimeout");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onTimeout(time);
},
this
);
c->onData(
[](void *r, AsyncClient *c, void *buf, size_t len) {
(void)c;
// log_e("AsyncWebServerRequest::_onData");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onData(buf, len);
},
this
);
c->onPoll(
[](void *r, AsyncClient *c) {
(void)c;
// log_e("AsyncWebServerRequest::_onPoll");
AsyncWebServerRequest *req = (AsyncWebServerRequest *)r;
req->_onPoll();
},
this
);
}
AsyncWebServerRequest::~AsyncWebServerRequest() {
// log_e("AsyncWebServerRequest::~AsyncWebServerRequest");
_this.reset();
_headers.clear();
_pathParams.clear();
@ -74,7 +116,7 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
log_d("SSL/TLS handshake detected: resetting connection");
#endif
_parseState = PARSE_REQ_FAIL;
_client->abort();
abort();
return;
}
#endif
@ -89,7 +131,7 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
// Check for null characters in header
if (!str[i]) {
_parseState = PARSE_REQ_FAIL;
_client->abort();
abort();
return;
}
if (str[i] == '\n') {
@ -99,7 +141,14 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
if (i == len) { // No new line, just add the buffer in _temp
char ch = str[len - 1];
str[len - 1] = 0;
_temp.reserve(_temp.length() + len);
if (!_temp.reserve(_temp.length() + len)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
_parseState = PARSE_REQ_FAIL;
abort();
return;
}
_temp.concat(str);
_temp.concat(ch);
} else { // Found new line - extract it and parse
@ -118,6 +167,8 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
// A handler should be already attached at this point in _parseLine function.
// If handler does nothing (_onRequest is NULL), we don't need to really parse the body.
const bool needParse = _handler && !_handler->isRequestHandlerTrivial();
// Discard any bytes after content length; handlers may overrun their buffers
len = std::min(len, _contentLength - _parsedLength);
if (_isMultipart) {
if (needParse) {
size_t i;
@ -125,16 +176,16 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
_parseMultipartPostByte(((uint8_t *)buf)[i], i == len - 1);
_parsedLength++;
}
} else
} else {
_parsedLength += len;
}
} else {
if (_parsedLength == 0) {
if (_contentType.startsWith(T_app_xform_urlencoded)) {
_isPlainPost = true;
} else if (_contentType == T_text_plain && __is_param_char(((char *)buf)[0])) {
size_t i = 0;
while (i < len && __is_param_char(((char*)buf)[i++]))
;
while (i < len && __is_param_char(((char *)buf)[i++]));
if (i < len && ((char *)buf)[i - 1] == '=') {
_isPlainPost = true;
}
@ -142,8 +193,9 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
}
if (!_isPlainPost) {
// ESP_LOGD("AsyncWebServer", "_isPlainPost: %d, _handler: %p", _isPlainPost, _handler);
if (_handler)
if (_handler) {
_handler->handleBody(this, (uint8_t *)buf, len, _parsedLength, _contentLength);
}
_parsedLength += len;
} else if (needParse) {
size_t i;
@ -157,16 +209,8 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
}
if (_parsedLength == _contentLength) {
_parseState = PARSE_REQ_END;
_server->_runChain(this, [this]() { return _handler ? _handler->_runChain(this, [this]() { _handler->handleRequest(this); }) : send(501); });
if (!_sent) {
if (!_response)
send(501, T_text_plain, "Handler did not handle the request");
else if (!_response->_sourceValid())
send(500, T_text_plain, "Invalid data in handler");
_client->setRxTimeout(0);
_response->_respond(this);
_sent = true;
}
_runMiddlewareChain();
_send();
}
}
break;
@ -233,14 +277,18 @@ void AsyncWebServerRequest::_addGetParams(const String& params) {
size_t start = 0;
while (start < params.length()) {
int end = params.indexOf('&', start);
if (end < 0)
if (end < 0) {
end = params.length();
}
int equal = params.indexOf('=', start);
if (equal < 0 || equal > end)
if (equal < 0 || equal > end) {
equal = end;
String name(params.substring(start, equal));
String value(equal + 1 < end ? params.substring(equal + 1, end) : String());
_params.emplace_back(urlDecode(name), urlDecode(value));
}
String name = urlDecode(params.substring(start, equal));
String value = urlDecode(equal + 1 < end ? params.substring(equal + 1, end) : emptyString);
if (name.length()) {
_params.emplace_back(name, value);
}
start = end + 1;
}
}
@ -280,11 +328,13 @@ bool AsyncWebServerRequest::_parseReqHead() {
_url = urlDecode(u);
_addGetParams(g);
if (!_url.length())
if (!_url.length()) {
return false;
}
if (!_temp.startsWith(T_HTTP_1_0))
if (!_temp.startsWith(T_HTTP_1_0)) {
_version = 1;
}
_temp = emptyString;
return true;
@ -344,18 +394,19 @@ bool AsyncWebServerRequest::_parseReqHeader() {
}
_headers.emplace_back(name, value);
}
#ifndef TARGET_RP2040
_temp.clear();
#else
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
// Ancient PRI core does not have String::clear() method 8-()
_temp = emptyString;
#else
_temp.clear();
#endif
return true;
}
void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) {
if (data && (char)data != '&')
if (data && (char)data != '&') {
_temp += (char)data;
}
if (!data || (char)data == '&' || _parsedLength == _contentLength) {
String name(T_BODY);
String value(_temp);
@ -363,13 +414,16 @@ void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) {
name = _temp.substring(0, _temp.indexOf('='));
value = _temp.substring(_temp.indexOf('=') + 1);
}
_params.emplace_back(urlDecode(name), urlDecode(value), true);
name = urlDecode(name);
if (name.length()) {
_params.emplace_back(name, urlDecode(value), true);
}
#ifndef TARGET_RP2040
_temp.clear();
#else
#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
// Ancient PRI core does not have String::clear() method 8-()
_temp = emptyString;
#else
_temp.clear();
#endif
}
}
@ -379,8 +433,9 @@ void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last) {
if (last || _itemBufferIndex == RESPONSE_STREAM_BUFFER_SIZE) {
// check if authenticated before calling the upload
if (_handler)
if (_handler) {
_handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false);
}
_itemBufferIndex = 0;
}
}
@ -442,8 +497,9 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
_itemIsFile = false;
}
} else if (_multiParseState == PARSE_HEADERS) {
if ((char)data != '\r' && (char)data != '\n')
if ((char)data != '\r' && (char)data != '\n') {
_temp += (char)data;
}
if ((char)data == '\n') {
if (_temp.length()) {
if (_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase(T_Content_Type)) {
@ -479,11 +535,16 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
_itemStartIndex = _parsedLength;
_itemValue = emptyString;
if (_itemIsFile) {
if (_itemBuffer)
if (_itemBuffer) {
free(_itemBuffer);
}
_itemBuffer = (uint8_t *)malloc(RESPONSE_STREAM_BUFFER_SIZE);
if (_itemBuffer == NULL) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
_multiParseState = PARSE_ERROR;
abort();
return;
}
_itemBufferIndex = 0;
@ -526,8 +587,9 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
itemWriteByte('-');
itemWriteByte('-');
uint8_t i;
for (i = 0; i < _boundaryPosition; i++)
for (i = 0; i < _boundaryPosition; i++) {
itemWriteByte(_boundary.c_str()[i]);
}
_parseMultipartPostByte(data, last);
} else if (_boundaryPosition == _boundary.length() - 1) {
_multiParseState = DASH3_OR_RETURN2;
@ -535,8 +597,9 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
_params.emplace_back(_itemName, _itemValue, true);
} else {
if (_itemSize) {
if (_handler)
if (_handler) {
_handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true);
}
_itemBufferIndex = 0;
_params.emplace_back(_itemName, _itemFilename, true, true, _itemSize);
}
@ -563,8 +626,9 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
itemWriteByte('-');
itemWriteByte('-');
uint8_t i;
for (i = 0; i < _boundary.length(); i++)
for (i = 0; i < _boundary.length(); i++) {
itemWriteByte(_boundary.c_str()[i]);
}
_parseMultipartPostByte(data, last);
}
} else if (_multiParseState == EXPECT_FEED2) {
@ -578,8 +642,9 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
itemWriteByte('-');
itemWriteByte('-');
uint8_t i;
for (i = 0; i < _boundary.length(); i++)
for (i = 0; i < _boundary.length(); i++) {
itemWriteByte(_boundary.c_str()[i]);
}
itemWriteByte('\r');
_parseMultipartPostByte(data, last);
}
@ -590,13 +655,13 @@ void AsyncWebServerRequest::_parseLine() {
if (_parseState == PARSE_REQ_START) {
if (!_temp.length()) {
_parseState = PARSE_REQ_FAIL;
_client->abort();
abort();
} else {
if (_parseReqHead()) {
_parseState = PARSE_REQ_HEADERS;
} else {
_parseState = PARSE_REQ_FAIL;
_client->abort();
abort();
}
}
return;
@ -615,19 +680,71 @@ void AsyncWebServerRequest::_parseLine() {
_parseState = PARSE_REQ_BODY;
} else {
_parseState = PARSE_REQ_END;
_server->_runChain(this, [this]() { return _handler ? _handler->_runChain(this, [this]() { _handler->handleRequest(this); }) : send(501); });
if (!_sent) {
if (!_response)
_runMiddlewareChain();
_send();
}
} else {
_parseReqHeader();
}
}
}
void AsyncWebServerRequest::_runMiddlewareChain() {
if (_handler && _handler->mustSkipServerMiddlewares()) {
_handler->_runChain(this, [this]() {
_handler->handleRequest(this);
});
} else {
_server->_runChain(this, [this]() {
if (_handler) {
_handler->_runChain(this, [this]() {
_handler->handleRequest(this);
});
}
});
}
}
void AsyncWebServerRequest::_send() {
if (!_sent && !_paused) {
// log_d("AsyncWebServerRequest::_send()");
// user did not create a response ?
if (!_response) {
send(501, T_text_plain, "Handler did not handle the request");
else if (!_response->_sourceValid())
}
// response is not valid ?
if (!_response->_sourceValid()) {
send(500, T_text_plain, "Invalid data in handler");
}
// here, we either have a response give nfrom user or one of the two above
_client->setRxTimeout(0);
_response->_respond(this);
_sent = true;
}
}
} else
_parseReqHeader();
AsyncWebServerRequestPtr AsyncWebServerRequest::pause() {
if (_paused) {
return _this;
}
client()->setRxTimeout(0);
// this shared ptr will hold the request pointer until it gets destroyed following a disconnect.
// this is just used as a holder providing weak observers, so the deleter is a no-op.
_this = std::shared_ptr<AsyncWebServerRequest>(this, doNotDelete);
_paused = true;
return _this;
}
void AsyncWebServerRequest::abort() {
if (!_sent) {
_sent = true;
_paused = false;
_this.reset();
// log_e("AsyncWebServerRequest::abort");
_client->abort();
}
}
@ -651,7 +768,9 @@ bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper* data) const {
#endif
const AsyncWebHeader *AsyncWebServerRequest::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);
}
@ -672,23 +791,25 @@ const AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper
#endif
const AsyncWebHeader *AsyncWebServerRequest::getHeader(size_t num) const {
if (num >= _headers.size())
if (num >= _headers.size()) {
return nullptr;
}
return &(*std::next(_headers.cbegin(), num));
}
size_t AsyncWebServerRequest::getHeaderNames(std::vector<const char *> &names) const {
const size_t size = _headers.size();
names.reserve(size);
const size_t size = names.size();
for (const auto &h : _headers) {
names.push_back(h.name().c_str());
}
return size;
return names.size() - size;
}
bool AsyncWebServerRequest::removeHeader(const char *name) {
const size_t size = _headers.size();
_headers.remove_if([name](const AsyncWebHeader& header) { return header.name().equalsIgnoreCase(name); });
_headers.remove_if([name](const AsyncWebHeader &header) {
return header.name().equalsIgnoreCase(name);
});
return size != _headers.size();
}
@ -721,8 +842,9 @@ const AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelp
#endif
const AsyncWebParameter *AsyncWebServerRequest::getParam(size_t num) const {
if (num >= _params.size())
if (num >= _params.size()) {
return nullptr;
}
return &(*std::next(_params.cbegin(), num));
}
@ -748,24 +870,30 @@ double AsyncWebServerRequest::getAttribute(const char* name, double defaultValue
}
AsyncWebServerResponse *AsyncWebServerRequest::beginResponse(int code, const char *contentType, const char *content, AwsTemplateProcessor callback) {
if (callback)
if (callback) {
return new AsyncProgmemResponse(code, contentType, (const uint8_t *)content, strlen(content), callback);
}
return new AsyncBasicResponse(code, contentType, content);
}
AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) {
AsyncWebServerResponse *
AsyncWebServerRequest::beginResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback) {
return new AsyncProgmemResponse(code, contentType, content, len, callback);
}
AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(FS& fs, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) {
if (fs.exists(path) || (!download && fs.exists(path + T__gz)))
AsyncWebServerResponse *
AsyncWebServerRequest::beginResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback) {
if (fs.exists(path) || (!download && fs.exists(path + T__gz))) {
return new AsyncFileResponse(fs, path, contentType, download, callback);
}
return NULL;
}
AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(File content, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) {
if (content == true)
AsyncWebServerResponse *
AsyncWebServerRequest::beginResponse(File content, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback) {
if (content == true) {
return new AsyncFileResponse(content, path, contentType, download, callback);
}
return NULL;
}
@ -773,13 +901,16 @@ AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(Stream& stream, con
return new AsyncStreamResponse(stream, contentType, len, callback);
}
AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) {
AsyncWebServerResponse *
AsyncWebServerRequest::beginResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) {
return new AsyncCallbackResponse(contentType, len, callback, templateCallback);
}
AsyncWebServerResponse* AsyncWebServerRequest::beginChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) {
if (_version)
AsyncWebServerResponse *
AsyncWebServerRequest::beginChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) {
if (_version) {
return new AsyncChunkedResponse(contentType, callback, templateCallback);
}
return new AsyncCallbackResponse(contentType, 0, callback, templateCallback);
}
@ -792,11 +923,22 @@ AsyncWebServerResponse* AsyncWebServerRequest::beginResponse_P(int code, const S
}
void AsyncWebServerRequest::send(AsyncWebServerResponse *response) {
if (_sent)
// request is already sent on the wire ?
if (_sent) {
return;
if (_response)
}
// if we already had a response, delete it and replace it with the new one
if (_response) {
delete _response;
}
_response = response;
// if request was paused, we need to send the response now
if (_paused) {
_paused = false;
_send();
}
}
void AsyncWebServerRequest::redirect(const char *url, int code) {
@ -807,30 +949,34 @@ void AsyncWebServerRequest::redirect(const char* url, int code) {
bool AsyncWebServerRequest::authenticate(const char *username, const char *password, const char *realm, bool passwordIsHash) const {
if (_authorization.length()) {
if (_authMethod == AsyncAuthType::AUTH_DIGEST)
if (_authMethod == AsyncAuthType::AUTH_DIGEST) {
return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL);
else if (!passwordIsHash)
} else if (!passwordIsHash) {
return checkBasicAuthentication(_authorization.c_str(), username, password);
else
} else {
return _authorization.equals(password);
}
}
return false;
}
bool AsyncWebServerRequest::authenticate(const char *hash) const {
if (!_authorization.length() || hash == NULL)
if (!_authorization.length() || hash == NULL) {
return false;
}
if (_authMethod == AsyncAuthType::AUTH_DIGEST) {
String hStr = String(hash);
int separator = hStr.indexOf(':');
if (separator <= 0)
if (separator <= 0) {
return false;
}
String username = hStr.substring(0, separator);
hStr = hStr.substring(separator + 1);
separator = hStr.indexOf(':');
if (separator <= 0)
if (separator <= 0) {
return false;
}
String realm = hStr.substring(0, separator);
hStr = hStr.substring(separator + 1);
return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL);
@ -841,38 +987,57 @@ bool AsyncWebServerRequest::authenticate(const char* hash) const {
}
void AsyncWebServerRequest::requestAuthentication(AsyncAuthType method, const char *realm, const char *_authFailMsg) {
if (!realm)
if (!realm) {
realm = T_LOGIN_REQ;
}
AsyncWebServerResponse *r = _authFailMsg ? beginResponse(401, T_text_html, _authFailMsg) : beginResponse(401);
switch (method) {
case AsyncAuthType::AUTH_BASIC: {
case AsyncAuthType::AUTH_BASIC:
{
String header;
header.reserve(strlen(T_BASIC_REALM) + strlen(realm) + 1);
if (header.reserve(strlen(T_BASIC_REALM) + strlen(realm) + 1)) {
header.concat(T_BASIC_REALM);
header.concat(realm);
header.concat('"');
r->addHeader(T_WWW_AUTH, header.c_str());
} else {
#ifdef ESP32
log_e("Failed to allocate");
#endif
abort();
}
break;
}
case AsyncAuthType::AUTH_DIGEST: {
case AsyncAuthType::AUTH_DIGEST:
{
size_t len = strlen(T_DIGEST_) + strlen(T_realm__) + strlen(T_auth_nonce) + 32 + strlen(T__opaque) + 32 + 1;
String header;
header.reserve(len + strlen(realm));
if (header.reserve(len + strlen(realm))) {
const String nonce = genRandomMD5();
const String opaque = genRandomMD5();
if (nonce.length() && opaque.length()) {
header.concat(T_DIGEST_);
header.concat(T_realm__);
header.concat(realm);
header.concat(T_auth_nonce);
header.concat(genRandomMD5());
header.concat(nonce);
header.concat(T__opaque);
header.concat(genRandomMD5());
header.concat(opaque);
header.concat((char)0x22); // '"'
r->addHeader(T_WWW_AUTH, header.c_str());
} else {
#ifdef ESP32
log_e("Failed to allocate");
#endif
abort();
}
}
break;
}
default:
break;
default: break;
}
send(r);
@ -917,7 +1082,12 @@ const String& AsyncWebServerRequest::argName(size_t i) const {
}
const String &AsyncWebServerRequest::pathArg(size_t i) const {
return i < _pathParams.size() ? _pathParams[i] : emptyString;
if (i >= _pathParams.size()) {
return emptyString;
}
auto it = _pathParams.begin();
std::advance(it, i);
return *it;
}
const String &AsyncWebServerRequest::header(const char *name) const {
@ -946,7 +1116,13 @@ String AsyncWebServerRequest::urlDecode(const String& text) const {
unsigned int len = text.length();
unsigned int i = 0;
String decoded;
decoded.reserve(len); // Allocate the string internal buffer - never longer from source text
// Allocate the string internal buffer - never longer from source text
if (!decoded.reserve(len)) {
#ifdef ESP32
log_e("Failed to allocate");
#endif
return emptyString;
}
while (i < len) {
char decodedChar;
char encodedChar = text.charAt(i++);
@ -965,44 +1141,45 @@ String AsyncWebServerRequest::urlDecode(const String& text) const {
}
const char *AsyncWebServerRequest::methodToString() const {
if (_method == HTTP_ANY)
if (_method == HTTP_ANY) {
return T_ANY;
if (_method & HTTP_GET)
}
if (_method & HTTP_GET) {
return T_GET;
if (_method & HTTP_POST)
}
if (_method & HTTP_POST) {
return T_POST;
if (_method & HTTP_DELETE)
}
if (_method & HTTP_DELETE) {
return T_DELETE;
if (_method & HTTP_PUT)
}
if (_method & HTTP_PUT) {
return T_PUT;
if (_method & HTTP_PATCH)
}
if (_method & HTTP_PATCH) {
return T_PATCH;
if (_method & HTTP_HEAD)
}
if (_method & HTTP_HEAD) {
return T_HEAD;
if (_method & HTTP_OPTIONS)
}
if (_method & HTTP_OPTIONS) {
return T_OPTIONS;
}
return T_UNKNOWN;
}
const char *AsyncWebServerRequest::requestedConnTypeToString() const {
switch (_reqconntype) {
case RCT_NOT_USED:
return T_RCT_NOT_USED;
case RCT_DEFAULT:
return T_RCT_DEFAULT;
case RCT_HTTP:
return T_RCT_HTTP;
case RCT_WS:
return T_RCT_WS;
case RCT_EVENT:
return T_RCT_EVENT;
default:
return T_ERROR;
case RCT_NOT_USED: return T_RCT_NOT_USED;
case RCT_DEFAULT: return T_RCT_DEFAULT;
case RCT_HTTP: return T_RCT_HTTP;
case RCT_WS: return T_RCT_WS;
case RCT_EVENT: return T_RCT_EVENT;
default: return T_ERROR;
}
}
bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) const {
return ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) ||
((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype)) ||
((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype));
return ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) || ((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype))
|| ((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype));
}

View file

@ -1,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// 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_
#define ASYNCWEBSERVERRESPONSEIMPL_H_
@ -39,16 +22,19 @@ class AsyncBasicResponse : public AsyncWebServerResponse {
public:
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)
: AsyncBasicResponse(code, contentType.c_str(), content.c_str()) {}
void _respond(AsyncWebServerRequest *request) override final;
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override final;
bool _sourceValid() const override final { return true; }
bool _sourceValid() const override final {
return true;
}
};
class AsyncAbstractResponse : public AsyncWebServerResponse {
private:
#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};
// in-flight queue credits
size_t _in_flight_credit{2};
@ -70,8 +56,12 @@ class AsyncAbstractResponse : public AsyncWebServerResponse {
virtual ~AsyncAbstractResponse() {}
void _respond(AsyncWebServerRequest *request) override final;
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override final;
virtual bool _sourceValid() const { return false; }
virtual size_t _fillBuffer(uint8_t* buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; }
virtual bool _sourceValid() const {
return false;
}
virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) {
return 0;
}
};
#ifndef TEMPLATE_PLACEHOLDER
@ -90,11 +80,19 @@ class AsyncFileResponse : public AsyncAbstractResponse {
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 String& contentType, bool download = false, AwsTemplateProcessor callback = nullptr) : AsyncFileResponse(fs, path, contentType.c_str(), download, callback) {}
AsyncFileResponse(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr);
AsyncFileResponse(File content, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callack = nullptr) : AsyncFileResponse(content, path, contentType.c_str(), download, callack) {}
~AsyncFileResponse() { _content.close(); }
bool _sourceValid() const override final { return !!(_content); }
AsyncFileResponse(FS &fs, const String &path, const String &contentType, bool download = false, AwsTemplateProcessor callback = nullptr)
: AsyncFileResponse(fs, path, contentType.c_str(), download, callback) {}
AsyncFileResponse(
File content, const String &path, const char *contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr
);
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;
};
@ -104,8 +102,11 @@ class AsyncStreamResponse : public AsyncAbstractResponse {
public:
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) {}
bool _sourceValid() const override final { return !!(_content); }
AsyncStreamResponse(Stream &stream, const String &contentType, size_t len, AwsTemplateProcessor callback = nullptr)
: AsyncStreamResponse(stream, contentType.c_str(), len, callback) {}
bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
};
@ -116,8 +117,11 @@ class AsyncCallbackResponse : public AsyncAbstractResponse {
public:
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) {}
bool _sourceValid() const override final { return !!(_content); }
AsyncCallbackResponse(const String &contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr)
: AsyncCallbackResponse(contentType.c_str(), len, callback, templateCallback) {}
bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
};
@ -128,8 +132,11 @@ class AsyncChunkedResponse : public AsyncAbstractResponse {
public:
AsyncChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) : AsyncChunkedResponse(contentType.c_str(), callback, templateCallback) {}
bool _sourceValid() const override final { return !!(_content); }
AsyncChunkedResponse(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr)
: AsyncChunkedResponse(contentType.c_str(), callback, templateCallback) {}
bool _sourceValid() const override final {
return !!(_content);
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
};
@ -140,8 +147,11 @@ class AsyncProgmemResponse : public AsyncAbstractResponse {
public:
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) {}
bool _sourceValid() const override final { return true; }
AsyncProgmemResponse(int code, const String &contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr)
: AsyncProgmemResponse(code, contentType.c_str(), content, len, callback) {}
bool _sourceValid() const override final {
return true;
}
size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
};
@ -152,7 +162,9 @@ class AsyncResponseStream : public AsyncAbstractResponse, public Print {
public:
AsyncResponseStream(const char *contentType, size_t 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 {
return (_state < RESPONSE_END);
}
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);

View file

@ -1,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// 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 "WebResponseImpl.h"
@ -26,9 +9,11 @@ using namespace asyncsrv;
// Since ESP8266 does not link memchr by default, here's its implementation.
void *memchr(void *ptr, int ch, size_t count) {
unsigned char *p = static_cast<unsigned char *>(ptr);
while (count--)
if (*p++ == static_cast<unsigned char>(ch))
while (count--) {
if (*p++ == static_cast<unsigned char>(ch)) {
return --p;
}
}
return nullptr;
}
@ -39,118 +24,93 @@ void* memchr(void* ptr, int ch, size_t count) {
const char *AsyncWebServerResponse::responseCodeToString(int code) {
switch (code) {
case 100:
return T_HTTP_CODE_100;
case 101:
return T_HTTP_CODE_101;
case 200:
return T_HTTP_CODE_200;
case 201:
return T_HTTP_CODE_201;
case 202:
return T_HTTP_CODE_202;
case 203:
return T_HTTP_CODE_203;
case 204:
return T_HTTP_CODE_204;
case 205:
return T_HTTP_CODE_205;
case 206:
return T_HTTP_CODE_206;
case 300:
return T_HTTP_CODE_300;
case 301:
return T_HTTP_CODE_301;
case 302:
return T_HTTP_CODE_302;
case 303:
return T_HTTP_CODE_303;
case 304:
return T_HTTP_CODE_304;
case 305:
return T_HTTP_CODE_305;
case 307:
return T_HTTP_CODE_307;
case 400:
return T_HTTP_CODE_400;
case 401:
return T_HTTP_CODE_401;
case 402:
return T_HTTP_CODE_402;
case 403:
return T_HTTP_CODE_403;
case 404:
return T_HTTP_CODE_404;
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;
case 100: return T_HTTP_CODE_100;
case 101: return T_HTTP_CODE_101;
case 200: return T_HTTP_CODE_200;
case 201: return T_HTTP_CODE_201;
case 202: return T_HTTP_CODE_202;
case 203: return T_HTTP_CODE_203;
case 204: return T_HTTP_CODE_204;
case 205: return T_HTTP_CODE_205;
case 206: return T_HTTP_CODE_206;
case 300: return T_HTTP_CODE_300;
case 301: return T_HTTP_CODE_301;
case 302: return T_HTTP_CODE_302;
case 303: return T_HTTP_CODE_303;
case 304: return T_HTTP_CODE_304;
case 305: return T_HTTP_CODE_305;
case 307: return T_HTTP_CODE_307;
case 400: return T_HTTP_CODE_400;
case 401: return T_HTTP_CODE_401;
case 402: return T_HTTP_CODE_402;
case 403: return T_HTTP_CODE_403;
case 404: return T_HTTP_CODE_404;
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()
: _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),
_state(RESPONSE_SETUP) {
for (const auto &header : DefaultHeaders::Instance()) {
_headers.emplace_back(header);
}
}
void AsyncWebServerResponse::setCode(int code) {
if (_state == RESPONSE_SETUP)
if (_state == RESPONSE_SETUP) {
_code = code;
}
}
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;
}
}
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;
}
}
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)) {
_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);
return true;
}
@ -159,10 +119,21 @@ bool AsyncWebServerResponse::removeHeader(const char* name) {
}
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);
}
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) {
if (i->name().equalsIgnoreCase(name)) {
@ -171,9 +142,11 @@ bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool
// remove, break and add the new one
_headers.erase(i);
break;
} else {
} else if (headerMustBePresentOnce(i->name())) { // we can have only one header with that name
// do not update
return false;
} else {
break; // accept multiple headers with the same name
}
}
}
@ -185,21 +158,25 @@ bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool
void AsyncWebServerResponse::_assembleHead(String &buffer, uint8_t version) {
if (version) {
addHeader(T_Accept_Ranges, T_none, false);
if (_chunked)
if (_chunked) {
addHeader(T_Transfer_Encoding, T_chunked, false);
}
}
if (_sendContentLength)
if (_sendContentLength) {
addHeader(T_Content_Length, String(_contentLength), false);
}
if (_contentType.length())
if (_contentType.length()) {
addHeader(T_Content_Type, _contentType.c_str(), false);
}
// precompute buffer size to avoid reallocations by String class
size_t len = 0;
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;
}
// prepare buffer
buffer.reserve(len);
@ -233,10 +210,18 @@ void AsyncWebServerResponse::_assembleHead(String& buffer, uint8_t version) {
_headLength = buffer.length();
}
bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; }
bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; }
bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; }
bool AsyncWebServerResponse::_sourceValid() const { return false; }
bool AsyncWebServerResponse::_started() const {
return _state > RESPONSE_SETUP;
}
bool AsyncWebServerResponse::_finished() const {
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;
request->client()->close();
@ -257,9 +242,10 @@ AsyncBasicResponse::AsyncBasicResponse(int code, const char* contentType, const
_contentType = contentType;
if (_content.length()) {
_contentLength = _content.length();
if (!_contentType.length())
if (!_contentType.length()) {
_contentType = T_text_plain;
}
}
addHeader(T_Connection, T_close, false);
}
@ -355,8 +341,9 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
// return a credit for each chunk of acked data (polls does not give any credits)
if (len)
if (len) {
++_in_flight_credit;
}
// for chunked responses ignore acks if there are no _in_flight_credits left
if (_chunked && !_in_flight_credit) {
@ -399,8 +386,9 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
if (_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
if (len)
if (len) {
--_in_flight_credit;
}
return 0;
}
#endif
@ -420,7 +408,10 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
uint8_t *buf = (uint8_t *)malloc(outLen + headLen);
if (!buf) {
// os_printf("_ack malloc %d failed\n", outLen+headLen);
#ifdef ESP32
log_e("Failed to allocate");
#endif
request->abort();
return 0;
}
@ -481,10 +472,11 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u
} else if (_state == RESPONSE_WAIT_ACK) {
if (!_sendContentLength || _ackedLength >= _writtenLength) {
_state = RESPONSE_END;
if (!_chunked && !_sendContentLength)
if (!_chunked && !_sendContentLength) {
request->client()->close(true);
}
}
}
return 0;
}
@ -502,16 +494,19 @@ size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const s
}
size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t *data, size_t len) {
if (!_callback)
if (!_callback) {
return _fillBuffer(data, len);
}
const size_t originalLen = len;
len = _readDataFromCacheOrContent(data, len);
// Now we've read 'len' bytes, either from cache or from file
// Search for template placeholders
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]
uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
while ((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t *)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))
) { // data[0] ... data[len - 1]
uint8_t *pTemplateEnd =
(pTemplateStart < &data[len - 1]) ? (uint8_t *)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr;
// temporary buffer to hold parameter name
uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
String paramName;
@ -529,9 +524,11 @@ size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size
len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1;
++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);
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) {
pTemplateEnd = (uint8_t *)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
if (pTemplateEnd) {
@ -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);
++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;
} 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;
}
if (paramName.length()) {
// call callback and replace with result.
// Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter 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
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied);
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.
// Move the entire data after the placeholder
memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
}
// 3. replace placeholder with actual value
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)
@ -604,48 +604,50 @@ void AsyncFileResponse::_setContentTypeFromPath(const String& path) {
#endif
_contentType = getContentType(path);
#else
if (path.endsWith(T__html))
if (path.endsWith(T__html)) {
_contentType = T_text_html;
else if (path.endsWith(T__htm))
} else if (path.endsWith(T__htm)) {
_contentType = T_text_html;
else if (path.endsWith(T__css))
} else if (path.endsWith(T__css)) {
_contentType = T_text_css;
else if (path.endsWith(T__json))
} else if (path.endsWith(T__json)) {
_contentType = T_application_json;
else if (path.endsWith(T__js))
} else if (path.endsWith(T__js)) {
_contentType = T_application_javascript;
else if (path.endsWith(T__png))
} else if (path.endsWith(T__png)) {
_contentType = T_image_png;
else if (path.endsWith(T__gif))
} else if (path.endsWith(T__gif)) {
_contentType = T_image_gif;
else if (path.endsWith(T__jpg))
} else if (path.endsWith(T__jpg)) {
_contentType = T_image_jpeg;
else if (path.endsWith(T__ico))
} else if (path.endsWith(T__ico)) {
_contentType = T_image_x_icon;
else if (path.endsWith(T__svg))
} else if (path.endsWith(T__svg)) {
_contentType = T_image_svg_xml;
else if (path.endsWith(T__eot))
} else if (path.endsWith(T__eot)) {
_contentType = T_font_eot;
else if (path.endsWith(T__woff))
} else if (path.endsWith(T__woff)) {
_contentType = T_font_woff;
else if (path.endsWith(T__woff2))
} else if (path.endsWith(T__woff2)) {
_contentType = T_font_woff2;
else if (path.endsWith(T__ttf))
} else if (path.endsWith(T__ttf)) {
_contentType = T_font_ttf;
else if (path.endsWith(T__xml))
} else if (path.endsWith(T__xml)) {
_contentType = T_text_xml;
else if (path.endsWith(T__pdf))
} else if (path.endsWith(T__pdf)) {
_contentType = T_application_pdf;
else if (path.endsWith(T__zip))
} else if (path.endsWith(T__zip)) {
_contentType = T_application_zip;
else if (path.endsWith(T__gz))
} else if (path.endsWith(T__gz)) {
_contentType = T_application_x_gzip;
else
} else {
_contentType = T_text_plain;
}
#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;
_path = path;
@ -660,10 +662,11 @@ AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* con
_content = fs.open(_path, fs::FileOpenMode::read);
_contentLength = _content.size();
if (strlen(contentType) == 0)
if (strlen(contentType) == 0) {
_setContentTypeFromPath(path);
else
} else {
_contentType = contentType;
}
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26 + path.length() - filenameStart];
@ -679,7 +682,8 @@ AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* con
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;
_path = path;
@ -693,10 +697,11 @@ AsyncFileResponse::AsyncFileResponse(File content, const String& path, const cha
_content = content;
_contentLength = _content.size();
if (strlen(contentType) == 0)
if (strlen(contentType) == 0) {
_setContentTypeFromPath(path);
else
} else {
_contentType = contentType;
}
int filenameStart = path.lastIndexOf('/') + 1;
char buf[26 + path.length() - filenameStart];
@ -729,8 +734,9 @@ size_t AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) {
size_t available = _content->available();
size_t outLen = (available > len) ? len : available;
size_t i;
for (i = 0; i < outLen; i++)
for (i = 0; i < outLen; i++) {
data[i] = _content->read();
}
return outLen;
}
@ -738,12 +744,14 @@ size_t AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) {
* 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;
_content = callback;
_contentLength = len;
if (!len)
if (!len) {
_sendContentLength = false;
}
_contentType = contentType;
_filledLength = 0;
}
@ -760,7 +768,8 @@ size_t AsyncCallbackResponse::_fillBuffer(uint8_t* data, size_t len) {
* 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;
_content = callback;
_contentLength = 0;
@ -782,7 +791,8 @@ size_t AsyncChunkedResponse::_fillBuffer(uint8_t* data, size_t len) {
* 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;
_content = content;
_contentType = contentType;
@ -810,7 +820,11 @@ AsyncResponseStream::AsyncResponseStream(const char* contentType, size_t bufferS
_code = 200;
_contentLength = 0;
_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) {
@ -818,8 +832,9 @@ size_t AsyncResponseStream::_fillBuffer(uint8_t* buf, size_t maxLen) {
}
size_t AsyncResponseStream::write(const uint8_t *data, size_t len) {
if (_started())
if (_started()) {
return 0;
}
size_t written = _content.write(data, len);
_contentLength += written;
return written;

View file

@ -1,23 +1,6 @@
/*
Asynchronous WebServer library for Espressif MCUs
// SPDX-License-Identifier: LGPL-3.0-or-later
// 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 "WebHandlerImpl.h"
@ -45,14 +28,13 @@ const char* fs::FileOpenMode::write = "w";
const char *fs::FileOpenMode::append = "a";
#endif
AsyncWebServer::AsyncWebServer(uint16_t port)
: _server(port) {
AsyncWebServer::AsyncWebServer(uint16_t port) : _server(port) {
_catchAllHandler = new AsyncCallbackWebHandler();
if (_catchAllHandler == NULL)
return;
_server.onClient([](void* s, AsyncClient* c) {
if (c == NULL)
_server.onClient(
[](void *s, AsyncClient *c) {
if (c == NULL) {
return;
}
c->setRxTimeout(3);
AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer *)s, c);
if (r == NULL) {
@ -60,14 +42,15 @@ AsyncWebServer::AsyncWebServer(uint16_t port)
delete c;
}
},
this);
this
);
}
AsyncWebServer::~AsyncWebServer() {
reset();
end();
if (_catchAllHandler)
delete _catchAllHandler;
_catchAllHandler = nullptr; // Prevent potential use-after-free
}
AsyncWebRewrite &AsyncWebServer::addRewrite(std::shared_ptr<AsyncWebRewrite> rewrite) {
@ -138,6 +121,8 @@ void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest* request) {
}
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request) {
// 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)) {
request->_url = r->toUrl();
@ -157,7 +142,9 @@ void AsyncWebServer::_attachHandler(AsyncWebServerRequest* request) {
request->setHandler(_catchAllHandler);
}
AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody) {
AsyncCallbackWebHandler &AsyncWebServer::on(
const char *uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody
) {
AsyncCallbackWebHandler *handler = new AsyncCallbackWebHandler();
handler->setUri(uri);
handler->setMethod(method);
@ -186,13 +173,15 @@ void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn) {
_catchAllHandler->onBody(fn);
}
AsyncWebHandler &AsyncWebServer::catchAllHandler() const {
return *_catchAllHandler;
}
void AsyncWebServer::reset() {
_rewrites.clear();
_handlers.clear();
if (_catchAllHandler != NULL) {
_catchAllHandler->onRequest(NULL);
_catchAllHandler->onUpload(NULL);
_catchAllHandler->onBody(NULL);
}
}

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
namespace asyncsrv {
@ -25,6 +28,7 @@ namespace asyncsrv {
static constexpr const char *T_Content_Encoding = "content-encoding";
static constexpr const char *T_Content_Length = "content-length";
static constexpr const char *T_Content_Type = "content-type";
static constexpr const char *T_Content_Location = "content-location";
static constexpr const char *T_Cookie = "cookie";
static constexpr const char *T_CORS_ACAC = "access-control-allow-credentials";
static constexpr const char *T_CORS_ACAH = "access-control-allow-headers";
@ -33,6 +37,7 @@ namespace asyncsrv {
static constexpr const char *T_CORS_ACMA = "access-control-max-age";
static constexpr const char *T_CORS_O = "origin";
static constexpr const char *T_data_ = "data: ";
static constexpr const char *T_Date = "date";
static constexpr const char *T_DIGEST = "digest";
static constexpr const char *T_DIGEST_ = "digest ";
static constexpr const char *T_ETag = "etag";
@ -68,6 +73,7 @@ namespace asyncsrv {
static constexpr const char *T_nn = "\n\n";
static constexpr const char *T_rn = "\r\n";
static constexpr const char *T_rnrn = "\r\n\r\n";
static constexpr const char *T_Server = "server";
static constexpr const char *T_Transfer_Encoding = "transfer-encoding";
static constexpr const char *T_TRUE = "true";
static constexpr const char *T_UPGRADE = "upgrade";
@ -96,7 +102,7 @@ namespace asyncsrv {
static constexpr const char *T_RCT_EVENT = "RCT_EVENT";
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__eot = ".eot";
static constexpr const char *T__gif = ".gif";
@ -136,7 +142,7 @@ namespace asyncsrv {
static constexpr const char *T_text_plain = "text/plain";
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_101 = "Switching Protocols";
static constexpr const char *T_HTTP_CODE_200 = "OK";
@ -180,4 +186,8 @@ namespace asyncsrv {
static constexpr const char *T_HTTP_CODE_505 = "HTTP Version not supported";
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