summaryrefslogtreecommitdiff
path: root/libraries/ESP_Async_WebServer/src
diff options
context:
space:
mode:
authorschererleander <leander@schererleander.de>2026-01-20 08:34:54 +0100
committerschererleander <leander@schererleander.de>2026-01-20 08:34:54 +0100
commit85ea4e995a75abe061f6fc375ea0481084dddd43 (patch)
tree7eb5d57653ecd8f041aeac4e68d7d554c1168681 /libraries/ESP_Async_WebServer/src
initial commitHEADmain
Diffstat (limited to 'libraries/ESP_Async_WebServer/src')
-rw-r--r--libraries/ESP_Async_WebServer/src/AsyncEventSource.cpp507
-rw-r--r--libraries/ESP_Async_WebServer/src/AsyncEventSource.h320
-rw-r--r--libraries/ESP_Async_WebServer/src/AsyncJson.cpp167
-rw-r--r--libraries/ESP_Async_WebServer/src/AsyncJson.h119
-rw-r--r--libraries/ESP_Async_WebServer/src/AsyncMessagePack.cpp119
-rw-r--r--libraries/ESP_Async_WebServer/src/AsyncMessagePack.h126
-rw-r--r--libraries/ESP_Async_WebServer/src/AsyncWebHeader.cpp32
-rw-r--r--libraries/ESP_Async_WebServer/src/AsyncWebServerVersion.h40
-rw-r--r--libraries/ESP_Async_WebServer/src/AsyncWebSocket.cpp1364
-rw-r--r--libraries/ESP_Async_WebServer/src/AsyncWebSocket.h499
-rw-r--r--libraries/ESP_Async_WebServer/src/BackPort_SHA1Builder.cpp284
-rw-r--r--libraries/ESP_Async_WebServer/src/BackPort_SHA1Builder.h44
-rw-r--r--libraries/ESP_Async_WebServer/src/ChunkPrint.cpp18
-rw-r--r--libraries/ESP_Async_WebServer/src/ChunkPrint.h23
-rw-r--r--libraries/ESP_Async_WebServer/src/ESPAsyncWebServer.h1217
-rw-r--r--libraries/ESP_Async_WebServer/src/Middleware.cpp287
-rw-r--r--libraries/ESP_Async_WebServer/src/WebAuthentication.cpp247
-rw-r--r--libraries/ESP_Async_WebServer/src/WebAuthentication.h23
-rw-r--r--libraries/ESP_Async_WebServer/src/WebHandlerImpl.h93
-rw-r--r--libraries/ESP_Async_WebServer/src/WebHandlers.cpp326
-rw-r--r--libraries/ESP_Async_WebServer/src/WebRequest.cpp1185
-rw-r--r--libraries/ESP_Async_WebServer/src/WebResponseImpl.h180
-rw-r--r--libraries/ESP_Async_WebServer/src/WebResponses.cpp859
-rw-r--r--libraries/ESP_Async_WebServer/src/WebServer.cpp187
-rw-r--r--libraries/ESP_Async_WebServer/src/literals.h193
25 files changed, 8459 insertions, 0 deletions
diff --git a/libraries/ESP_Async_WebServer/src/AsyncEventSource.cpp b/libraries/ESP_Async_WebServer/src/AsyncEventSource.cpp
new file mode 100644
index 0000000..2ebfa2d
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/AsyncEventSource.cpp
@@ -0,0 +1,507 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#include "Arduino.h"
+#if defined(ESP32)
+#include <rom/ets_sys.h>
+#endif
+#include "AsyncEventSource.h"
+
+#define ASYNC_SSE_NEW_LINE_CHAR (char)0xa
+
+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) {
+ len += strlen(message);
+ }
+
+ if (event) {
+ len += strlen(event);
+ }
+
+ len += 42; // give it some overhead
+
+ if (!str.reserve(len)) {
+#ifdef ESP32
+ log_e("Failed to allocate");
+#endif
+ return emptyString;
+ }
+
+ if (reconnect) {
+ str += T_retry_;
+ str += reconnect;
+ str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
+ }
+
+ if (id) {
+ str += T_id__;
+ str += id;
+ str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
+ }
+
+ if (event != NULL) {
+ str += T_event_;
+ str += event;
+ str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
+ }
+
+ if (!message) {
+ return str;
+ }
+
+ size_t messageLen = strlen(message);
+ char *lineStart = (char *)message;
+ char *lineEnd;
+ do {
+ char *nextN = strchr(lineStart, '\n');
+ char *nextR = strchr(lineStart, '\r');
+ if (nextN == NULL && nextR == NULL) {
+ // a message is a single-line string
+ str += T_data_;
+ str += message;
+ str += T_nn;
+ return str;
+ }
+
+ // a message is a multi-line string
+ char *nextLine = NULL;
+ if (nextN != NULL && nextR != NULL) { // windows line-ending \r\n
+ if (nextR + 1 == nextN) {
+ // normal \r\n sequence
+ lineEnd = nextR;
+ nextLine = nextN + 1;
+ } else {
+ // some abnormal \n \r mixed sequence
+ lineEnd = std::min(nextR, nextN);
+ nextLine = lineEnd + 1;
+ }
+ } else if (nextN != NULL) { // Unix/Mac OS X LF
+ lineEnd = nextN;
+ nextLine = nextN + 1;
+ } else { // some ancient garbage
+ lineEnd = nextR;
+ nextLine = nextR + 1;
+ }
+
+ str += T_data_;
+ str.concat(lineStart, lineEnd - lineStart);
+ str += ASYNC_SSE_NEW_LINE_CHAR; // \n
+
+ lineStart = nextLine;
+ } while (lineStart < ((char *)message + messageLen));
+
+ // append another \n to terminate message
+ str += ASYNC_SSE_NEW_LINE_CHAR; // '\n'
+
+ return str;
+}
+
+// Message
+
+size_t AsyncEventSourceMessage::ack(size_t len, __attribute__((unused)) uint32_t time) {
+ // If the whole message is now acked...
+ if (_acked + len > _data->length()) {
+ // Return the number of extra bytes acked (they will be carried on to the next message)
+ const size_t extra = _acked + len - _data->length();
+ _acked = _data->length();
+ return extra;
+ }
+ // Return that no extra bytes left.
+ _acked += len;
+ return 0;
+}
+
+size_t AsyncEventSourceMessage::write(AsyncClient *client) {
+ if (!client) {
+ return 0;
+ }
+
+ if (_sent >= _data->length() || !client->canSend()) {
+ return 0;
+ }
+
+ size_t len = std::min(_data->length() - _sent, client->space());
+ /*
+ add() would call lwip's tcp_write() under the AsyncTCP hood with apiflags argument.
+ By default apiflags=ASYNC_WRITE_FLAG_COPY
+ we could have used apiflags with this flag unset to pass data by reference and avoid copy to socket buffer,
+ but looks like it does not work for Arduino's lwip in ESP32/IDF
+ it is enforced in https://github.com/espressif/esp-lwip/blob/0606eed9d8b98a797514fdf6eabb4daf1c8c8cd9/src/core/tcp_out.c#L422C5-L422C30
+ if LWIP_NETIF_TX_SINGLE_PBUF is set, and it is set indeed in IDF
+ https://github.com/espressif/esp-idf/blob/a0f798cfc4bbd624aab52b2c194d219e242d80c1/components/lwip/port/include/lwipopts.h#L744
+
+ So let's just keep it enforced ASYNC_WRITE_FLAG_COPY and keep in mind that there is no zero-copy
+ */
+ size_t written = client->add(_data->c_str() + _sent, len, ASYNC_WRITE_FLAG_COPY); // ASYNC_WRITE_FLAG_MORE
+ _sent += written;
+ return written;
+}
+
+size_t AsyncEventSourceMessage::send(AsyncClient *client) {
+ size_t sent = write(client);
+ return sent && client->send() ? sent : 0;
+}
+
+// Client
+
+AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) : _client(request->client()), _server(server) {
+
+ 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->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
+ );
+
+ _server->_addClient(this);
+ delete request;
+
+ _client->setNoDelay(true);
+}
+
+AsyncEventSourceClient::~AsyncEventSourceClient() {
+#ifdef ESP32
+ std::lock_guard<std::recursive_mutex> lock(_lockmq);
+#endif
+ _messageQueue.clear();
+ close();
+}
+
+bool AsyncEventSourceClient::_queueMessage(const char *message, size_t len) {
+ if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
+#ifdef ESP8266
+ ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
+#elif defined(ESP32)
+ log_e("Event message queue overflow: discard message");
+#endif
+ return false;
+ }
+
+#ifdef ESP32
+ // length() is not thread-safe, thus acquiring the lock before this call..
+ std::lock_guard<std::recursive_mutex> lock(_lockmq);
+#endif
+
+ _messageQueue.emplace_back(message, len);
+
+ /*
+ throttle queue run
+ if Q is filled for >25% then network/CPU is congested, since there is no zero-copy mode for socket buff
+ forcing Q run will only eat more heap ram and blow the buffer, let's just keep data in our own queue
+ the queue will be processed at least on each onAck()/onPoll() call from AsyncTCP
+ */
+ if (_messageQueue.size() < SSE_MAX_QUEUED_MESSAGES >> 2 && _client->canSend()) {
+ _runQueue();
+ }
+
+ return true;
+}
+
+bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) {
+ if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
+#ifdef ESP8266
+ ets_printf(String(F("ERROR: Too many messages queued\n")).c_str());
+#elif defined(ESP32)
+ log_e("Event message queue overflow: discard message");
+#endif
+ return false;
+ }
+
+#ifdef ESP32
+ // length() is not thread-safe, thus acquiring the lock before this call..
+ std::lock_guard<std::recursive_mutex> lock(_lockmq);
+#endif
+
+ _messageQueue.emplace_back(std::move(msg));
+
+ /*
+ throttle queue run
+ if Q is filled for >25% then network/CPU is congested, since there is no zero-copy mode for socket buff
+ forcing Q run will only eat more heap ram and blow the buffer, let's just keep data in our own queue
+ the queue will be processed at least on each onAck()/onPoll() call from AsyncTCP
+ */
+ if (_messageQueue.size() < SSE_MAX_QUEUED_MESSAGES >> 2 && _client->canSend()) {
+ _runQueue();
+ }
+ return true;
+}
+
+void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))) {
+#ifdef ESP32
+ // Same here, acquiring the lock early
+ std::lock_guard<std::recursive_mutex> lock(_lockmq);
+#endif
+
+ // adjust in-flight len
+ if (len < _inflight) {
+ _inflight -= len;
+ } else {
+ _inflight = 0;
+ }
+
+ // acknowledge as much messages's data as we got confirmed len from a AsyncTCP
+ while (len && _messageQueue.size()) {
+ len = _messageQueue.front().ack(len);
+ if (_messageQueue.front().finished()) {
+ // now we could release full ack'ed messages, we were keeping it unless send confirmed from AsyncTCP
+ _messageQueue.pop_front();
+ }
+ }
+
+ // try to send another batch of data
+ if (_messageQueue.size()) {
+ _runQueue();
+ }
+}
+
+void AsyncEventSourceClient::_onPoll() {
+ if (_messageQueue.size()) {
+#ifdef ESP32
+ // Same here, acquiring the lock early
+ std::lock_guard<std::recursive_mutex> lock(_lockmq);
+#endif
+ _runQueue();
+ }
+}
+
+void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))) {
+ if (_client) {
+ _client->close(true);
+ }
+}
+
+void AsyncEventSourceClient::_onDisconnect() {
+ if (!_client) {
+ return;
+ }
+ _client = nullptr;
+ _server->_handleDisconnect(this);
+}
+
+void AsyncEventSourceClient::close() {
+ if (_client) {
+ _client->close();
+ }
+}
+
+bool AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
+ if (!connected()) {
+ return false;
+ }
+ return _queueMessage(std::make_shared<String>(generateEventMessage(message, event, id, reconnect)));
+}
+
+void AsyncEventSourceClient::_runQueue() {
+ 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;
+ for (auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i) {
+ if (!i->sent()) {
+ const size_t bytes_written = i->write(_client);
+ total_bytes_written += bytes_written;
+ _inflight += bytes_written;
+ if (bytes_written == 0 || _inflight > _max_inflight) {
+ // Serial.print("_");
+ break;
+ }
+ }
+ }
+
+ // flush socket
+ if (total_bytes_written) {
+ _client->send();
+ }
+}
+
+void AsyncEventSourceClient::set_max_inflight_bytes(size_t value) {
+ if (value >= SSE_MIN_INFLIGH && value <= SSE_MAX_INFLIGH) {
+ _max_inflight = value;
+ }
+}
+
+/* AsyncEventSource */
+
+void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) {
+ AsyncAuthorizationMiddleware *m = new AsyncAuthorizationMiddleware(401, cb);
+ m->_freeOnRemoval = true;
+ addMiddleware(m);
+}
+
+void AsyncEventSource::_addClient(AsyncEventSourceClient *client) {
+ if (!client) {
+ return;
+ }
+#ifdef ESP32
+ std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
+#endif
+ _clients.emplace_back(client);
+ if (_connectcb) {
+ _connectcb(client);
+ }
+
+ _adjust_inflight_window();
+}
+
+void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient *client) {
+ if (_disconnectcb) {
+ _disconnectcb(client);
+ }
+#ifdef ESP32
+ std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
+#endif
+ for (auto i = _clients.begin(); i != _clients.end(); ++i) {
+ if (i->get() == client) {
+ _clients.erase(i);
+ break;
+ }
+ }
+ _adjust_inflight_window();
+}
+
+void AsyncEventSource::close() {
+ // While the whole loop is not done, the linked list is locked and so the
+ // iterator should remain valid even when AsyncEventSource::_handleDisconnect()
+ // is called very early
+#ifdef ESP32
+ std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
+#endif
+ for (const auto &c : _clients) {
+ if (c->connected()) {
+ /**
+ * @brief: Fix self-deadlock by using recursive_mutex instead.
+ * Due to c->close() shall call the callback function _onDisconnect()
+ * The calling flow _onDisconnect() --> _handleDisconnect() --> deadlock
+ */
+ c->close();
+ }
+ }
+}
+
+// pmb fix
+size_t AsyncEventSource::avgPacketsWaiting() const {
+ size_t aql = 0;
+ uint32_t nConnectedClients = 0;
+#ifdef ESP32
+ std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
+#endif
+ if (!_clients.size()) {
+ return 0;
+ }
+
+ for (const auto &c : _clients) {
+ if (c->connected()) {
+ aql += c->packetsWaiting();
+ ++nConnectedClients;
+ }
+ }
+ return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up
+}
+
+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::recursive_mutex> lock(_client_queue_lock);
+#endif
+ size_t hits = 0;
+ size_t miss = 0;
+ for (const auto &c : _clients) {
+ if (c->write(shared_msg)) {
+ ++hits;
+ } else {
+ ++miss;
+ }
+ }
+ return hits == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
+}
+
+size_t AsyncEventSource::count() const {
+#ifdef ESP32
+ std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
+#endif
+ size_t n_clients{0};
+ for (const auto &i : _clients) {
+ if (i->connected()) {
+ ++n_clients;
+ }
+ }
+
+ return n_clients;
+}
+
+bool AsyncEventSource::canHandle(AsyncWebServerRequest *request) const {
+ return request->isSSE() && request->url().equals(_url);
+}
+
+void AsyncEventSource::handleRequest(AsyncWebServerRequest *request) {
+ request->send(new AsyncEventSourceResponse(this));
+}
+
+void AsyncEventSource::_adjust_inflight_window() {
+ if (_clients.size()) {
+ size_t inflight = SSE_MAX_INFLIGH / _clients.size();
+ for (const auto &c : _clients) {
+ c->set_max_inflight_bytes(inflight);
+ }
+ // Serial.printf("adjusted inflight to: %u\n", inflight);
+ }
+}
+
+/* Response */
+
+AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server) {
+ _server = server;
+ _code = 200;
+ _contentType = T_text_event_stream;
+ _sendContentLength = false;
+ addHeader(T_Cache_Control, T_no_cache);
+ addHeader(T_Connection, T_keep_alive);
+}
+
+void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request) {
+ String out;
+ _assembleHead(out, request->version());
+ request->client()->write(out.c_str(), _headLength);
+ _state = RESPONSE_WAIT_ACK;
+}
+
+size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))) {
+ if (len) {
+ new AsyncEventSourceClient(request, _server);
+ }
+ return 0;
+}
diff --git a/libraries/ESP_Async_WebServer/src/AsyncEventSource.h b/libraries/ESP_Async_WebServer/src/AsyncEventSource.h
new file mode 100644
index 0000000..96f0a89
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/AsyncEventSource.h
@@ -0,0 +1,320 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#ifndef ASYNCEVENTSOURCE_H_
+#define ASYNCEVENTSOURCE_H_
+
+#include <Arduino.h>
+
+#ifdef ESP32
+#include <AsyncTCP.h>
+#include <mutex>
+#ifndef SSE_MAX_QUEUED_MESSAGES
+#define SSE_MAX_QUEUED_MESSAGES 32
+#endif
+#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
+#define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q
+#elif defined(ESP8266)
+#include <ESPAsyncTCP.h>
+#ifndef SSE_MAX_QUEUED_MESSAGES
+#define SSE_MAX_QUEUED_MESSAGES 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) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
+#include <RPAsyncTCP.h>
+#ifndef SSE_MAX_QUEUED_MESSAGES
+#define SSE_MAX_QUEUED_MESSAGES 32
+#endif
+#define SSE_MIN_INFLIGH 2 * 1460 // allow 2 MSS packets
+#define SSE_MAX_INFLIGH 16 * 1024 // but no more than 16k, no need to blow it, since same data is kept in local Q
+#endif
+
+#include <ESPAsyncWebServer.h>
+
+#ifdef ESP8266
+#include <Hash.h>
+#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
+#include <../src/Hash.h>
+#endif
+#endif
+
+class AsyncEventSource;
+class AsyncEventSourceResponse;
+class AsyncEventSourceClient;
+using ArEventHandlerFunction = std::function<void(AsyncEventSourceClient *client)>;
+using ArAuthorizeConnectHandler = ArAuthorizeFunction;
+// shared message object container
+using AsyncEvent_SharedData_t = std::shared_ptr<String>;
+
+/**
+ * @brief Async Event Message container with shared message content data
+ *
+ */
+class AsyncEventSourceMessage {
+
+private:
+ const AsyncEvent_SharedData_t _data;
+ size_t _sent{0}; // num of bytes already sent
+ size_t _acked{0}; // num of bytes acked
+
+public:
+ AsyncEventSourceMessage(AsyncEvent_SharedData_t data) : _data(data){};
+#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);
+ };
+#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 acknowledge
+ * @param time
+ * @return size_t number of extra bytes carried over
+ */
+ size_t ack(size_t len, uint32_t time = 0);
+
+ /**
+ * @brief write message data to client's buffer
+ * @note this method does NOT call client's send
+ *
+ * @param client
+ * @return size_t number of bytes written
+ */
+ size_t write(AsyncClient *client);
+
+ /**
+ * @brief writes message data to client's buffer and calls client's send method
+ *
+ * @param client
+ * @return size_t returns num of bytes the clien was able to send()
+ */
+ size_t send(AsyncClient *client);
+
+ // returns true if full message's length were acked
+ bool finished() {
+ return _acked == _data->length();
+ }
+
+ /**
+ * @brief returns true if all data has been sent already
+ *
+ */
+ bool sent() {
+ return _sent == _data->length();
+ }
+};
+
+/**
+ * @brief class holds a sse messages queue for a particular client's connection
+ *
+ */
+class AsyncEventSourceClient {
+private:
+ AsyncClient *_client;
+ AsyncEventSource *_server;
+ uint32_t _lastId{0};
+ size_t _inflight{0}; // num of unacknowledged bytes that has been written to socket buffer
+ size_t _max_inflight{SSE_MAX_INFLIGH}; // max num of unacknowledged bytes that could be written to socket buffer
+ std::list<AsyncEventSourceMessage> _messageQueue;
+#ifdef ESP32
+ mutable std::recursive_mutex _lockmq;
+#endif
+ bool _queueMessage(const char *message, size_t len);
+ bool _queueMessage(AsyncEvent_SharedData_t &&msg);
+ void _runQueue();
+
+public:
+ AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
+ ~AsyncEventSourceClient();
+
+ /**
+ * @brief Send an SSE message to client
+ * it will craft an SSE message and place it to client's message queue
+ *
+ * @param message body string, could be single or multi-line string sepprated by \n, \r, \r\n
+ * @param event body string, a sinle line string
+ * @param id sequence id
+ * @param reconnect client's reconnect timeout
+ * @return true if message was placed in a queue
+ * @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);
+ }
+
+ /**
+ * @brief place supplied preformatted SSE message to the message queue
+ * @note message must a properly formatted SSE string according to https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
+ *
+ * @param message data
+ * @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));
+ };
+
+ [[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);
+ };
+
+ // 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();
+ };
+
+ /**
+ * @brief Sets max amount of bytes that could be written to client's socket while awaiting delivery acknowledge
+ * used to throttle message delivery length to tradeoff memory consumption
+ * @note actual amount of data written could possible be a bit larger but no more than available socket buff space
+ *
+ * @param value
+ */
+ void set_max_inflight_bytes(size_t value);
+
+ /**
+ * @brief Get current max inflight bytes value
+ *
+ * @return size_t
+ */
+ 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);
+ void _onPoll();
+ void _onTimeout(uint32_t time);
+ void _onDisconnect();
+};
+
+/**
+ * @brief a class that maintains all connected HTTP clients subscribed to SSE delivery
+ * dispatches supplied messages to the client's queues
+ *
+ */
+class AsyncEventSource : public AsyncWebHandler {
+private:
+ String _url;
+ std::list<std::unique_ptr<AsyncEventSourceClient>> _clients;
+#ifdef ESP32
+ // Same as for individual messages, protect mutations of _clients list
+ // since simultaneous access from different tasks is possible
+ mutable std::recursive_mutex _client_queue_lock;
+#endif
+ ArEventHandlerFunction _connectcb = nullptr;
+ ArEventHandlerFunction _disconnectcb = nullptr;
+
+ // this method manipulates in-fligh data size for connected client depending on number of active connections
+ void _adjust_inflight_window();
+
+public:
+ typedef enum {
+ DISCARDED = 0,
+ ENQUEUED = 1,
+ PARTIALLY_ENQUEUED = 2,
+ } SendStatus;
+
+ AsyncEventSource(const char *url) : _url(url){};
+ AsyncEventSource(const String &url) : _url(url){};
+ ~AsyncEventSource() {
+ close();
+ };
+
+ const char *url() const {
+ return _url.c_str();
+ }
+ // close all connected clients
+ void close();
+
+ /**
+ * @brief set on-connect callback for the client
+ * used to deliver messages to client on first connect
+ *
+ * @param cb
+ */
+ void onConnect(ArEventHandlerFunction cb) {
+ _connectcb = cb;
+ }
+
+ /**
+ * @brief Send an SSE message to client
+ * it will craft an SSE message and place it to all connected client's message queues
+ *
+ * @param message body string, could be single or multi-line string sepprated by \n, \r, \r\n
+ * @param event body string, a sinle line string
+ * @param id sequence id
+ * @param reconnect client's reconnect timeout
+ * @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);
+ }
+
+ // 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 authorizeConnect(ArAuthorizeConnectHandler cb);
+
+ // returns number of connected clients
+ size_t count() const;
+
+ // returns average number of messages pending in all client's queues
+ size_t avgPacketsWaiting() const;
+
+ // system callbacks (do not call from user code!)
+ void _addClient(AsyncEventSourceClient *client);
+ void _handleDisconnect(AsyncEventSourceClient *client);
+ bool canHandle(AsyncWebServerRequest *request) const override final;
+ void handleRequest(AsyncWebServerRequest *request) override final;
+};
+
+class AsyncEventSourceResponse : public AsyncWebServerResponse {
+private:
+ AsyncEventSource *_server;
+
+public:
+ AsyncEventSourceResponse(AsyncEventSource *server);
+ void _respond(AsyncWebServerRequest *request);
+ size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
+ bool _sourceValid() const {
+ return true;
+ }
+};
+
+#endif /* ASYNCEVENTSOURCE_H_ */
diff --git a/libraries/ESP_Async_WebServer/src/AsyncJson.cpp b/libraries/ESP_Async_WebServer/src/AsyncJson.cpp
new file mode 100644
index 0000000..b8d014b
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/AsyncJson.cpp
@@ -0,0 +1,167 @@
+// 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
+
+#if ARDUINOJSON_VERSION_MAJOR == 5
+AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
+ _code = 200;
+ _contentType = asyncsrv::T_application_json;
+ if (isArray) {
+ _root = _jsonBuffer.createArray();
+ } 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) {
+ _root = _jsonBuffer.createNestedArray();
+ } else {
+ _root = _jsonBuffer.createNestedObject();
+ }
+}
+#else
+AsyncJsonResponse::AsyncJsonResponse(bool isArray) : _isValid{false} {
+ _code = 200;
+ _contentType = asyncsrv::T_application_json;
+ if (isArray) {
+ _root = _jsonBuffer.add<JsonArray>();
+ } else {
+ _root = _jsonBuffer.add<JsonObject>();
+ }
+}
+#endif
+
+size_t AsyncJsonResponse::setLength() {
+#if ARDUINOJSON_VERSION_MAJOR == 5
+ _contentLength = _root.measureLength();
+#else
+ _contentLength = measureJson(_root);
+#endif
+ if (_contentLength) {
+ _isValid = true;
+ }
+ return _contentLength;
+}
+
+size_t AsyncJsonResponse::_fillBuffer(uint8_t *data, size_t len) {
+ ChunkPrint dest(data, _sentLength, len);
+#if ARDUINOJSON_VERSION_MAJOR == 5
+ _root.printTo(dest);
+#else
+ serializeJson(_root, dest);
+#endif
+ return len;
+}
+
+#if ARDUINOJSON_VERSION_MAJOR == 6
+PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray, size_t maxJsonBufferSize) : AsyncJsonResponse{isArray, maxJsonBufferSize} {}
+#else
+PrettyAsyncJsonResponse::PrettyAsyncJsonResponse(bool isArray) : AsyncJsonResponse{isArray} {}
+#endif
+
+size_t PrettyAsyncJsonResponse::setLength() {
+#if ARDUINOJSON_VERSION_MAJOR == 5
+ _contentLength = _root.measurePrettyLength();
+#else
+ _contentLength = measureJsonPretty(_root);
+#endif
+ if (_contentLength) {
+ _isValid = true;
+ }
+ return _contentLength;
+}
+
+size_t PrettyAsyncJsonResponse::_fillBuffer(uint8_t *data, size_t len) {
+ ChunkPrint dest(data, _sentLength, len);
+#if ARDUINOJSON_VERSION_MAJOR == 5
+ _root.prettyPrintTo(dest);
+#else
+ serializeJsonPretty(_root, dest);
+#endif
+ return len;
+}
+
+#if ARDUINOJSON_VERSION_MAJOR == 6
+AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize)
+ : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
+#else
+AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest)
+ : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
+#endif
+
+bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) const {
+ if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
+ return false;
+ }
+
+ if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
+ return false;
+ }
+
+ if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_json)) {
+ return false;
+ }
+
+ return true;
+}
+
+void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) {
+ if (_onRequest) {
+ if (request->method() == HTTP_GET) {
+ JsonVariant json;
+ _onRequest(request, json);
+ return;
+ } else if (request->_tempObject != NULL) {
+
+#if ARDUINOJSON_VERSION_MAJOR == 5
+ DynamicJsonBuffer jsonBuffer;
+ JsonVariant json = jsonBuffer.parse((uint8_t *)(request->_tempObject));
+ if (json.success()) {
+#elif ARDUINOJSON_VERSION_MAJOR == 6
+ DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
+ DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
+ if (!error) {
+ JsonVariant json = jsonBuffer.as<JsonVariant>();
+#else
+ JsonDocument jsonBuffer;
+ DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
+ if (!error) {
+ JsonVariant json = jsonBuffer.as<JsonVariant>();
+#endif
+
+ _onRequest(request, json);
+ return;
+ }
+ }
+ request->send(_contentLength > _maxContentLength ? 413 : 400);
+ } else {
+ request->send(500);
+ }
+}
+
+void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
+ if (_onRequest) {
+ _contentLength = total;
+ if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
+ request->_tempObject = malloc(total);
+ 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);
+ }
+ }
+}
+
+#endif // ASYNC_JSON_SUPPORT
diff --git a/libraries/ESP_Async_WebServer/src/AsyncJson.h b/libraries/ESP_Async_WebServer/src/AsyncJson.h
new file mode 100644
index 0000000..b5777d6
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/AsyncJson.h
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#ifndef ASYNC_JSON_H_
+#define ASYNC_JSON_H_
+
+#if __has_include("ArduinoJson.h")
+#include <ArduinoJson.h>
+#if ARDUINOJSON_VERSION_MAJOR >= 5
+#define ASYNC_JSON_SUPPORT 1
+#else
+#define ASYNC_JSON_SUPPORT 0
+#endif // ARDUINOJSON_VERSION_MAJOR >= 5
+#endif // __has_include("ArduinoJson.h")
+
+#if ASYNC_JSON_SUPPORT == 1
+#include <ESPAsyncWebServer.h>
+
+#include "ChunkPrint.h"
+
+#if ARDUINOJSON_VERSION_MAJOR == 6
+#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
+#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
+#endif
+#endif
+
+class AsyncJsonResponse : public AsyncAbstractResponse {
+protected:
+#if ARDUINOJSON_VERSION_MAJOR == 5
+ DynamicJsonBuffer _jsonBuffer;
+#elif ARDUINOJSON_VERSION_MAJOR == 6
+ DynamicJsonDocument _jsonBuffer;
+#else
+ JsonDocument _jsonBuffer;
+#endif
+
+ JsonVariant _root;
+ bool _isValid;
+
+public:
+#if ARDUINOJSON_VERSION_MAJOR == 6
+ AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
+#else
+ AsyncJsonResponse(bool isArray = false);
+#endif
+ JsonVariant &getRoot() {
+ return _root;
+ }
+ bool _sourceValid() const {
+ return _isValid;
+ }
+ size_t setLength();
+ 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();
+ }
+#endif
+};
+
+class PrettyAsyncJsonResponse : public AsyncJsonResponse {
+public:
+#if ARDUINOJSON_VERSION_MAJOR == 6
+ PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
+#else
+ PrettyAsyncJsonResponse(bool isArray = false);
+#endif
+ size_t setLength();
+ size_t _fillBuffer(uint8_t *data, size_t len);
+};
+
+typedef std::function<void(AsyncWebServerRequest *request, JsonVariant &json)> ArJsonRequestHandlerFunction;
+
+class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
+protected:
+ String _uri;
+ WebRequestMethodComposite _method;
+ ArJsonRequestHandlerFunction _onRequest;
+ size_t _contentLength;
+#if ARDUINOJSON_VERSION_MAJOR == 6
+ size_t maxJsonBufferSize;
+#endif
+ size_t _maxContentLength;
+
+public:
+#if ARDUINOJSON_VERSION_MAJOR == 6
+ AsyncCallbackJsonWebHandler(const String &uri, ArJsonRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
+#else
+ 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;
+ }
+
+ 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 handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final;
+ bool isRequestHandlerTrivial() const override final {
+ return !_onRequest;
+ }
+};
+
+#endif // ASYNC_JSON_SUPPORT == 1
+
+#endif // ASYNC_JSON_H_
diff --git a/libraries/ESP_Async_WebServer/src/AsyncMessagePack.cpp b/libraries/ESP_Async_WebServer/src/AsyncMessagePack.cpp
new file mode 100644
index 0000000..0c6faa1
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/AsyncMessagePack.cpp
@@ -0,0 +1,119 @@
+// 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
+
+#if ARDUINOJSON_VERSION_MAJOR == 6
+AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray, size_t maxJsonBufferSize) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
+ _code = 200;
+ _contentType = asyncsrv::T_application_msgpack;
+ if (isArray) {
+ _root = _jsonBuffer.createNestedArray();
+ } else {
+ _root = _jsonBuffer.createNestedObject();
+ }
+}
+#else
+AsyncMessagePackResponse::AsyncMessagePackResponse(bool isArray) : _isValid{false} {
+ _code = 200;
+ _contentType = asyncsrv::T_application_msgpack;
+ if (isArray) {
+ _root = _jsonBuffer.add<JsonArray>();
+ } else {
+ _root = _jsonBuffer.add<JsonObject>();
+ }
+}
+#endif
+
+size_t AsyncMessagePackResponse::setLength() {
+ _contentLength = measureMsgPack(_root);
+ if (_contentLength) {
+ _isValid = true;
+ }
+ return _contentLength;
+}
+
+size_t AsyncMessagePackResponse::_fillBuffer(uint8_t *data, size_t len) {
+ ChunkPrint dest(data, _sentLength, len);
+ serializeMsgPack(_root, dest);
+ return len;
+}
+
+#if ARDUINOJSON_VERSION_MAJOR == 6
+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)
+ : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {}
+#endif
+
+bool AsyncCallbackMessagePackWebHandler::canHandle(AsyncWebServerRequest *request) const {
+ if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
+ return false;
+ }
+
+ if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
+ return false;
+ }
+
+ if (request->method() != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack)) {
+ return false;
+ }
+
+ return true;
+}
+
+void AsyncCallbackMessagePackWebHandler::handleRequest(AsyncWebServerRequest *request) {
+ if (_onRequest) {
+ if (request->method() == HTTP_GET) {
+ JsonVariant json;
+ _onRequest(request, json);
+ return;
+ } else if (request->_tempObject != NULL) {
+
+#if ARDUINOJSON_VERSION_MAJOR == 6
+ DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
+ DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t *)(request->_tempObject));
+ if (!error) {
+ JsonVariant json = jsonBuffer.as<JsonVariant>();
+#else
+ JsonDocument jsonBuffer;
+ DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t *)(request->_tempObject));
+ if (!error) {
+ JsonVariant json = jsonBuffer.as<JsonVariant>();
+#endif
+
+ _onRequest(request, json);
+ return;
+ }
+ }
+ request->send(_contentLength > _maxContentLength ? 413 : 400);
+ } else {
+ request->send(500);
+ }
+}
+
+void AsyncCallbackMessagePackWebHandler::handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
+ if (_onRequest) {
+ _contentLength = total;
+ if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
+ request->_tempObject = malloc(total);
+ 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);
+ }
+ }
+}
+
+#endif // ASYNC_MSG_PACK_SUPPORT
diff --git a/libraries/ESP_Async_WebServer/src/AsyncMessagePack.h b/libraries/ESP_Async_WebServer/src/AsyncMessagePack.h
new file mode 100644
index 0000000..7488b5c
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/AsyncMessagePack.h
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#pragma once
+
+/*
+ server.on("/msg_pack", HTTP_ANY, [](AsyncWebServerRequest * request) {
+ AsyncMessagePackResponse * response = new AsyncMessagePackResponse();
+ JsonObject& root = response->getRoot();
+ root["key1"] = "key number one";
+ JsonObject& nested = root.createNestedObject("nested");
+ nested["key1"] = "key number one";
+ response->setLength();
+ request->send(response);
+ });
+
+ --------------------
+
+ AsyncCallbackMessagePackWebHandler* handler = new AsyncCallbackMessagePackWebHandler("/msg_pack/endpoint");
+ handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) {
+ JsonObject jsonObj = json.as<JsonObject>();
+ // ...
+ });
+ server.addHandler(handler);
+*/
+
+#if __has_include("ArduinoJson.h")
+#include <ArduinoJson.h>
+#if ARDUINOJSON_VERSION_MAJOR >= 6
+#define ASYNC_MSG_PACK_SUPPORT 1
+#else
+#define ASYNC_MSG_PACK_SUPPORT 0
+#endif // ARDUINOJSON_VERSION_MAJOR >= 6
+#endif // __has_include("ArduinoJson.h")
+
+#if ASYNC_MSG_PACK_SUPPORT == 1
+#include <ESPAsyncWebServer.h>
+
+#include "ChunkPrint.h"
+
+#if ARDUINOJSON_VERSION_MAJOR == 6
+#ifndef DYNAMIC_JSON_DOCUMENT_SIZE
+#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
+#endif
+#endif
+
+class AsyncMessagePackResponse : public AsyncAbstractResponse {
+protected:
+#if ARDUINOJSON_VERSION_MAJOR == 6
+ DynamicJsonDocument _jsonBuffer;
+#else
+ JsonDocument _jsonBuffer;
+#endif
+
+ JsonVariant _root;
+ bool _isValid;
+
+public:
+#if ARDUINOJSON_VERSION_MAJOR == 6
+ AsyncMessagePackResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
+#else
+ AsyncMessagePackResponse(bool isArray = false);
+#endif
+ JsonVariant &getRoot() {
+ return _root;
+ }
+ bool _sourceValid() const {
+ return _isValid;
+ }
+ size_t setLength();
+ 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();
+ }
+#endif
+};
+
+typedef std::function<void(AsyncWebServerRequest *request, JsonVariant &json)> ArMessagePackRequestHandlerFunction;
+
+class AsyncCallbackMessagePackWebHandler : public AsyncWebHandler {
+protected:
+ String _uri;
+ WebRequestMethodComposite _method;
+ ArMessagePackRequestHandlerFunction _onRequest;
+ size_t _contentLength;
+#if ARDUINOJSON_VERSION_MAJOR == 6
+ size_t maxJsonBufferSize;
+#endif
+ size_t _maxContentLength;
+
+public:
+#if ARDUINOJSON_VERSION_MAJOR == 6
+ 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;
+ }
+
+ 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 handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final;
+ bool isRequestHandlerTrivial() const override final {
+ return !_onRequest;
+ }
+};
+
+#endif // ASYNC_MSG_PACK_SUPPORT == 1
diff --git a/libraries/ESP_Async_WebServer/src/AsyncWebHeader.cpp b/libraries/ESP_Async_WebServer/src/AsyncWebHeader.cpp
new file mode 100644
index 0000000..6d82f74
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/AsyncWebHeader.cpp
@@ -0,0 +1,32 @@
+// 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) {
+ return;
+ }
+ int index = data.indexOf(':');
+ if (index < 0) {
+ return;
+ }
+ _name = data.substring(0, index);
+ _value = data.substring(index + 2);
+}
+
+String AsyncWebHeader::toString() const {
+ String str;
+ if (str.reserve(_name.length() + _value.length() + 2)) {
+ str.concat(_name);
+ str.concat((char)0x3a);
+ str.concat((char)0x20);
+ str.concat(_value);
+ str.concat(asyncsrv::T_rn);
+ } else {
+#ifdef ESP32
+ log_e("Failed to allocate");
+#endif
+ }
+ return str;
+}
diff --git a/libraries/ESP_Async_WebServer/src/AsyncWebServerVersion.h b/libraries/ESP_Async_WebServer/src/AsyncWebServerVersion.h
new file mode 100644
index 0000000..0bfa5a0
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/AsyncWebServerVersion.h
@@ -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 7
+
+/**
+ * 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
diff --git a/libraries/ESP_Async_WebServer/src/AsyncWebSocket.cpp b/libraries/ESP_Async_WebServer/src/AsyncWebSocket.cpp
new file mode 100644
index 0000000..f86d616
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/AsyncWebSocket.cpp
@@ -0,0 +1,1364 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#include "AsyncWebSocket.h"
+#include "Arduino.h"
+
+#include <cstring>
+
+#include <libb64/cencode.h>
+
+#if defined(ESP32)
+#if ESP_IDF_VERSION_MAJOR < 5
+#include "BackPort_SHA1Builder.h"
+#else
+#include <SHA1Builder.h>
+#endif
+#include <rom/ets_sys.h>
+#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()) {
+ return 0;
+ }
+ size_t space = client->space();
+ if (space < 9) {
+ return 0;
+ }
+ return space - 8;
+}
+
+size_t webSocketSendFrame(AsyncClient *client, bool final, uint8_t opcode, bool mask, uint8_t *data, size_t len) {
+ if (!client || !client->canSend()) {
+ // Serial.println("SF 1");
+ return 0;
+ }
+ size_t space = client->space();
+ if (space < 2) {
+ // Serial.println("SF 2");
+ return 0;
+ }
+ uint8_t mbuf[4] = {0, 0, 0, 0};
+ uint8_t headLen = 2;
+ if (len && mask) {
+ headLen += 4;
+ mbuf[0] = rand() % 0xFF;
+ mbuf[1] = rand() % 0xFF;
+ mbuf[2] = rand() % 0xFF;
+ mbuf[3] = rand() % 0xFF;
+ }
+ if (len > 125) {
+ headLen += 2;
+ }
+ if (space < headLen) {
+ // Serial.println("SF 2");
+ return 0;
+ }
+ space -= headLen;
+
+ if (len > space) {
+ len = space;
+ }
+
+ uint8_t *buf = (uint8_t *)malloc(headLen);
+ if (buf == NULL) {
+#ifdef ESP32
+ log_e("Failed to allocate");
+#endif
+ client->abort();
+ return 0;
+ }
+
+ buf[0] = opcode & 0x0F;
+ if (final) {
+ buf[0] |= 0x80;
+ }
+ if (len < 126) {
+ buf[1] = len & 0x7F;
+ } else {
+ buf[1] = 126;
+ buf[2] = (uint8_t)((len >> 8) & 0xFF);
+ buf[3] = (uint8_t)(len & 0xFF);
+ }
+ if (len && mask) {
+ buf[1] |= 0x80;
+ memcpy(buf + (headLen - 4), mbuf, 4);
+ }
+ if (client->add((const char *)buf, headLen) != headLen) {
+ // os_printf("error adding %lu header bytes\n", headLen);
+ free(buf);
+ // Serial.println("SF 4");
+ return 0;
+ }
+ free(buf);
+
+ if (len) {
+ if (len && mask) {
+ size_t 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");
+ return 0;
+ }
+ }
+ if (!client->send()) {
+ // os_printf("error sending frame: %lu\n", headLen+len);
+ // Serial.println("SF 6");
+ return 0;
+ }
+ // Serial.println("SF");
+ return len;
+}
+
+/*
+ * AsyncWebSocketMessageBuffer
+ */
+
+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 {
+ std::memcpy(_buffer->data(), data, 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) {
+ return true;
+ }
+ _buffer->reserve(size);
+ return _buffer->capacity() >= size;
+}
+
+/*
+ * Control Frame
+ */
+
+class AsyncWebSocketControl {
+private:
+ uint8_t _opcode;
+ uint8_t *_data;
+ size_t _len;
+ bool _mask;
+ bool _finished;
+
+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) {
+ _len = 0;
+ }
+ if (_len) {
+ if (_len > 125) {
+ _len = 125;
+ }
+
+ _data = (uint8_t *)malloc(_len);
+
+ if (_data == NULL) {
+#ifdef ESP32
+ log_e("Failed to allocate");
+#endif
+ _len = 0;
+ } else {
+ memcpy(_data, data, len);
+ }
+ } else {
+ _data = NULL;
+ }
+ }
+
+ ~AsyncWebSocketControl() {
+ if (_data != NULL) {
+ free(_data);
+ }
+ }
+
+ 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);
+ }
+};
+
+/*
+ * 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} {}
+
+void AsyncWebSocketMessage::ack(size_t len, uint32_t time) {
+ (void)time;
+ _acked += len;
+ if (_sent >= _WSbuffer->size() && _acked >= _ack) {
+ _status = WS_MSG_SENT;
+ }
+ // ets_printf("A: %u\n", len);
+}
+
+size_t AsyncWebSocketMessage::send(AsyncClient *client) {
+ if (!client) {
+ return 0;
+ }
+
+ if (_status != WS_MSG_SENDING) {
+ return 0;
+ }
+ if (_acked < _ack) {
+ return 0;
+ }
+ if (_sent == _WSbuffer->size()) {
+ if (_acked == _ack) {
+ _status = WS_MSG_SENT;
+ }
+ return 0;
+ }
+ if (_sent > _WSbuffer->size()) {
+ _status = WS_MSG_ERROR;
+ // ets_printf("E: %u > %u\n", _sent, _WSbuffer->length());
+ return 0;
+ }
+
+ size_t toSend = _WSbuffer->size() - _sent;
+ size_t window = webSocketSendFrameWindow(client);
+
+ if (window < toSend) {
+ toSend = window;
+ }
+
+ _sent += toSend;
+ _ack += toSend + ((toSend < 126) ? 2 : 4) + (_mask * 4);
+
+ // ets_printf("W: %u %u\n", _sent - toSend, toSend);
+
+ bool final = (_sent == _WSbuffer->size());
+ uint8_t *dPtr = (uint8_t *)(_WSbuffer->data() + (_sent - toSend));
+ uint8_t opCode = (toSend && _sent == toSend) ? _opcode : (uint8_t)WS_CONTINUATION;
+
+ size_t sent = webSocketSendFrame(client, final, opCode, _mask, dPtr, toSend);
+ _status = WS_MSG_SENDING;
+ if (toSend && sent != toSend) {
+ // ets_printf("E: %u != %u\n", toSend, sent);
+ _sent -= (toSend - sent);
+ _ack -= (toSend - sent);
+ }
+ // ets_printf("S: %u %u\n", _sent, sent);
+ return sent;
+}
+
+/*
+ * Async WebSocket Client
+ */
+const char *AWSC_PING_PAYLOAD = "ESPAsyncWebServer-PING";
+const size_t AWSC_PING_PAYLOAD_LEN = 22;
+
+AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server) : _tempObject(NULL) {
+ _client = request->client();
+ _server = server;
+ _clientId = _server->_getNextId();
+ _status = WS_CONNECTED;
+ _pstate = 0;
+ _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
+ );
+ delete request;
+ memset(&_pinfo, 0, sizeof(_pinfo));
+}
+
+AsyncWebSocketClient::~AsyncWebSocketClient() {
+ {
+#ifdef ESP32
+ std::lock_guard<std::recursive_mutex> lock(_lock);
+#endif
+ _messageQueue.clear();
+ _controlQueue.clear();
+ }
+ _server->_handleEvent(this, WS_EVT_DISCONNECT, NULL, NULL, 0);
+}
+
+void AsyncWebSocketClient::_clearQueue() {
+ while (!_messageQueue.empty() && _messageQueue.front().finished()) {
+ _messageQueue.pop_front();
+ }
+}
+
+void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
+ _lastMessageTime = millis();
+
+#ifdef ESP32
+ std::unique_lock<std::recursive_mutex> lock(_lock);
+#endif
+
+ if (!_controlQueue.empty()) {
+ auto &head = _controlQueue.front();
+ if (head.finished()) {
+ len -= head.len();
+ if (_status == WS_DISCONNECTING && head.opcode() == WS_DISCONNECT) {
+ _controlQueue.pop_front();
+ _status = WS_DISCONNECTED;
+ if (_client) {
+#ifdef ESP32
+ /*
+ Unlocking has to be called before return execution otherwise std::unique_lock ::~unique_lock() will get an exception pthread_mutex_unlock.
+ Due to _client->close(true) shall call the callback function _onDisconnect()
+ The calling flow _onDisconnect() --> _handleDisconnect() --> ~AsyncWebSocketClient()
+ */
+ lock.unlock();
+#endif
+ _client->close(true);
+ }
+ return;
+ }
+ _controlQueue.pop_front();
+ }
+ }
+
+ if (len && !_messageQueue.empty()) {
+ _messageQueue.front().ack(len, time);
+ }
+
+ _clearQueue();
+
+ _runQueue();
+}
+
+void AsyncWebSocketClient::_onPoll() {
+ if (!_client) {
+ return;
+ }
+
+#ifdef ESP32
+ std::unique_lock<std::recursive_mutex> lock(_lock);
+#endif
+ if (_client && _client->canSend() && (!_controlQueue.empty() || !_messageQueue.empty())) {
+ _runQueue();
+ } else if (_keepAlivePeriod > 0 && (millis() - _lastMessageTime) >= _keepAlivePeriod && (_controlQueue.empty() && _messageQueue.empty())) {
+#ifdef ESP32
+ lock.unlock();
+#endif
+ ping((uint8_t *)AWSC_PING_PAYLOAD, AWSC_PING_PAYLOAD_LEN);
+ }
+}
+
+void AsyncWebSocketClient::_runQueue() {
+ // all calls to this method MUST be protected by a mutex lock!
+ if (!_client) {
+ return;
+ }
+
+ _clearQueue();
+
+ 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);
+ }
+}
+
+bool AsyncWebSocketClient::queueIsFull() const {
+#ifdef ESP32
+ std::lock_guard<std::recursive_mutex> lock(_lock);
+#endif
+ return (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED);
+}
+
+size_t AsyncWebSocketClient::queueLen() const {
+#ifdef ESP32
+ std::lock_guard<std::recursive_mutex> lock(_lock);
+#endif
+ return _messageQueue.size();
+}
+
+bool AsyncWebSocketClient::canSend() const {
+#ifdef ESP32
+ std::lock_guard<std::recursive_mutex> lock(_lock);
+#endif
+ return _messageQueue.size() < WS_MAX_QUEUED_MESSAGES;
+}
+
+bool AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t *data, size_t len, bool mask) {
+ if (!_client) {
+ return false;
+ }
+
+#ifdef ESP32
+ std::lock_guard<std::recursive_mutex> lock(_lock);
+#endif
+
+ _controlQueue.emplace_back(opcode, data, len, mask);
+
+ 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) {
+ return false;
+ }
+
+#ifdef ESP32
+ std::unique_lock<std::recursive_mutex> lock(_lock);
+#endif
+
+ if (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) {
+ if (closeWhenFull) {
+ _status = WS_DISCONNECTED;
+
+ if (_client) {
+#ifdef ESP32
+ /*
+ Unlocking has to be called before return execution otherwise std::unique_lock ::~unique_lock() will get an exception pthread_mutex_unlock.
+ Due to _client->close(true) shall call the callback function _onDisconnect()
+ The calling flow _onDisconnect() --> _handleDisconnect() --> ~AsyncWebSocketClient()
+ */
+ lock.unlock();
+#endif
+ _client->close(true);
+ }
+
+#ifdef ESP8266
+ ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: closing connection\n");
+#elif defined(ESP32)
+ log_e("Too many messages queued: closing connection");
+#endif
+
+ } else {
+#ifdef ESP8266
+ ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: discarding new message\n");
+#elif defined(ESP32)
+ log_e("Too many messages queued: discarding new message");
+#endif
+ }
+
+ return false;
+ }
+
+ _messageQueue.emplace_back(buffer, opcode, mask);
+
+ if (_client && _client->canSend()) {
+ _runQueue();
+ }
+
+ return true;
+}
+
+void AsyncWebSocketClient::close(uint16_t code, const char *message) {
+ if (_status != WS_CONNECTED) {
+ return;
+ }
+
+ _status = WS_DISCONNECTING;
+
+ if (code) {
+ uint8_t packetLen = 2;
+ if (message != NULL) {
+ size_t mlen = strlen(message);
+ if (mlen > 123) {
+ mlen = 123;
+ }
+ packetLen += mlen;
+ }
+ char *buf = (char *)malloc(packetLen);
+ if (buf != NULL) {
+ buf[0] = (uint8_t)(code >> 8);
+ buf[1] = (uint8_t)(code & 0xFF);
+ if (message != NULL) {
+ memcpy(buf + 2, message, packetLen - 2);
+ }
+ _queueControl(WS_DISCONNECT, (uint8_t *)buf, packetLen);
+ free(buf);
+ return;
+ } else {
+#ifdef ESP32
+ log_e("Failed to allocate");
+ _client->abort();
+#endif
+ }
+ }
+ _queueControl(WS_DISCONNECT);
+}
+
+bool AsyncWebSocketClient::ping(const uint8_t *data, size_t len) {
+ return _status == WS_CONNECTED && _queueControl(WS_PING, data, len);
+}
+
+void AsyncWebSocketClient::_onError(int8_t) {
+ // Serial.println("onErr");
+}
+
+void AsyncWebSocketClient::_onTimeout(uint32_t time) {
+ if (!_client) {
+ return;
+ }
+ // Serial.println("onTime");
+ (void)time;
+ _client->close(true);
+}
+
+void AsyncWebSocketClient::_onDisconnect() {
+ // Serial.println("onDis");
+ _client = nullptr;
+ _server->_handleDisconnect(this);
+}
+
+void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
+ _lastMessageTime = millis();
+ uint8_t *data = (uint8_t *)pbuf;
+ while (plen > 0) {
+ if (!_pstate) {
+ const uint8_t *fdata = data;
+
+ _pinfo.index = 0;
+ _pinfo.final = (fdata[0] & 0x80) != 0;
+ _pinfo.opcode = fdata[0] & 0x0F;
+ _pinfo.masked = (fdata[1] & 0x80) != 0;
+ _pinfo.len = fdata[1] & 0x7F;
+
+ // log_d("WS[%" PRIu32 "]: _onData: %" PRIu32, _clientId, plen);
+ // log_d("WS[%" PRIu32 "]: _status = %" PRIu32, _clientId, _status);
+ // log_d("WS[%" PRIu32 "]: _pinfo: index: %" PRIu64 ", final: %" PRIu8 ", opcode: %" PRIu8 ", masked: %" PRIu8 ", len: %" PRIu64, _clientId, _pinfo.index, _pinfo.final, _pinfo.opcode, _pinfo.masked, _pinfo.len);
+
+ data += 2;
+ plen -= 2;
+
+ if (_pinfo.len == 126 && plen >= 2) {
+ _pinfo.len = fdata[3] | (uint16_t)(fdata[2]) << 8;
+ data += 2;
+ 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;
+ 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.
+ memcpy(_pinfo.mask, data, 4);
+ data += 4;
+ plen -= 4;
+ }
+ }
+
+ const size_t datalen = std::min((size_t)(_pinfo.len - _pinfo.index), plen);
+ const auto datalast = data[datalen];
+
+ if (_pinfo.masked) {
+ for (size_t i = 0; i < datalen; i++) {
+ data[i] ^= _pinfo.mask[(_pinfo.index + i) % 4];
+ }
+ }
+
+ if ((datalen + _pinfo.index) < _pinfo.len) {
+ _pstate = 1;
+
+ if (_pinfo.index == 0) {
+ if (_pinfo.opcode) {
+ _pinfo.message_opcode = _pinfo.opcode;
+ _pinfo.num = 0;
+ }
+ }
+ if (datalen > 0) {
+ _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen);
+ }
+
+ _pinfo.index += datalen;
+ } else if ((datalen + _pinfo.index) == _pinfo.len) {
+ _pstate = 0;
+ if (_pinfo.opcode == WS_DISCONNECT) {
+ if (datalen) {
+ uint16_t reasonCode = (uint16_t)(data[0] << 8) + data[1];
+ char *reasonString = (char *)(data + 2);
+ if (reasonCode > 1001) {
+ _server->_handleEvent(this, WS_EVT_ERROR, (void *)&reasonCode, (uint8_t *)reasonString, strlen(reasonString));
+ }
+ }
+ if (_status == WS_DISCONNECTING) {
+ _status = WS_DISCONNECTED;
+ if (_client) {
+ _client->close(true);
+ }
+ } else {
+ _status = WS_DISCONNECTING;
+ 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) {
+ _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) {
+ _pinfo.num = 0;
+ } 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?
+ break;
+ }
+
+ // restore byte as _handleEvent may have added a null terminator i.e., data[len] = 0;
+ if (datalen) {
+ data[datalen] = datalast;
+ }
+
+ data += datalen;
+ plen -= datalen;
+ }
+}
+
+size_t AsyncWebSocketClient::printf(const char *format, ...) {
+ va_list arg;
+ va_start(arg, format);
+ size_t len = vsnprintf(nullptr, 0, format, arg);
+ va_end(arg);
+
+ if (len == 0) {
+ return 0;
+ }
+
+ char *buffer = new char[len + 1];
+
+ if (!buffer) {
+ return 0;
+ }
+
+ va_start(arg, format);
+ len = vsnprintf(buffer, len + 1, format, arg);
+ va_end(arg);
+
+ bool enqueued = text(buffer, len);
+ delete[] buffer;
+ return enqueued ? len : 0;
+}
+
+#ifdef ESP8266
+size_t AsyncWebSocketClient::printf_P(PGM_P formatP, ...) {
+ va_list arg;
+ va_start(arg, formatP);
+ size_t len = vsnprintf_P(nullptr, 0, formatP, arg);
+ va_end(arg);
+
+ if (len == 0) {
+ return 0;
+ }
+
+ char *buffer = new char[len + 1];
+
+ if (!buffer) {
+ return 0;
+ }
+
+ va_start(arg, formatP);
+ len = vsnprintf_P(buffer, len + 1, formatP, arg);
+ va_end(arg);
+
+ bool enqueued = text(buffer, len);
+ delete[] buffer;
+ return enqueued ? len : 0;
+}
+#endif
+
+namespace {
+AsyncWebSocketSharedBuffer makeSharedBuffer(const uint8_t *message, size_t len) {
+ auto buffer = std::make_shared<std::vector<uint8_t>>(len);
+ std::memcpy(buffer->data(), message, len);
+ return buffer;
+}
+} // namespace
+
+bool AsyncWebSocketClient::text(AsyncWebSocketMessageBuffer *buffer) {
+ bool enqueued = false;
+ if (buffer) {
+ enqueued = text(std::move(buffer->_buffer));
+ delete buffer;
+ }
+ return enqueued;
+}
+
+bool AsyncWebSocketClient::text(AsyncWebSocketSharedBuffer buffer) {
+ return _queueMessage(buffer);
+}
+
+bool AsyncWebSocketClient::text(const uint8_t *message, size_t len) {
+ return text(makeSharedBuffer(message, len));
+}
+
+bool AsyncWebSocketClient::text(const char *message, size_t len) {
+ return text((const uint8_t *)message, len);
+}
+
+bool AsyncWebSocketClient::text(const char *message) {
+ return text(message, strlen(message));
+}
+
+bool AsyncWebSocketClient::text(const String &message) {
+ return text(message.c_str(), message.length());
+}
+
+#ifdef ESP8266
+bool AsyncWebSocketClient::text(const __FlashStringHelper *data) {
+ PGM_P p = reinterpret_cast<PGM_P>(data);
+
+ size_t n = 0;
+ while (1) {
+ if (pgm_read_byte(p + n) == 0) {
+ break;
+ }
+ n += 1;
+ }
+
+ char *message = (char *)malloc(n + 1);
+ bool enqueued = false;
+ if (message) {
+ memcpy_P(message, p, n);
+ message[n] = 0;
+ enqueued = text(message, n);
+ free(message);
+ }
+ return enqueued;
+}
+#endif // ESP8266
+
+bool AsyncWebSocketClient::binary(AsyncWebSocketMessageBuffer *buffer) {
+ bool enqueued = false;
+ if (buffer) {
+ enqueued = binary(std::move(buffer->_buffer));
+ delete buffer;
+ }
+ return enqueued;
+}
+
+bool AsyncWebSocketClient::binary(AsyncWebSocketSharedBuffer buffer) {
+ return _queueMessage(buffer, WS_BINARY);
+}
+
+bool AsyncWebSocketClient::binary(const uint8_t *message, size_t len) {
+ return binary(makeSharedBuffer(message, len));
+}
+
+bool AsyncWebSocketClient::binary(const char *message, size_t len) {
+ return binary((const uint8_t *)message, len);
+}
+
+bool AsyncWebSocketClient::binary(const char *message) {
+ return binary(message, strlen(message));
+}
+
+bool AsyncWebSocketClient::binary(const String &message) {
+ return binary(message.c_str(), message.length());
+}
+
+#ifdef ESP8266
+bool AsyncWebSocketClient::binary(const __FlashStringHelper *data, size_t len) {
+ PGM_P p = reinterpret_cast<PGM_P>(data);
+ char *message = (char *)malloc(len);
+ bool enqueued = false;
+ if (message) {
+ memcpy_P(message, p, len);
+ enqueued = binary(message, len);
+ free(message);
+ }
+ return enqueued;
+}
+#endif
+
+IPAddress AsyncWebSocketClient::remoteIP() const {
+ if (!_client) {
+ return IPAddress((uint32_t)0U);
+ }
+
+ return _client->remoteIP();
+}
+
+uint16_t AsyncWebSocketClient::remotePort() const {
+ if (!_client) {
+ return 0;
+ }
+
+ return _client->remotePort();
+}
+
+/*
+ * Async Web Socket - Each separate socket location
+ */
+
+void AsyncWebSocket::_handleEvent(AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
+ if (_eventHandler != NULL) {
+ _eventHandler(this, client, type, arg, data, len);
+ }
+}
+
+AsyncWebSocketClient *AsyncWebSocket::_newClient(AsyncWebServerRequest *request) {
+ _clients.emplace_back(request, this);
+ _handleEvent(&_clients.back(), WS_EVT_CONNECT, request, NULL, 0);
+ return &_clients.back();
+}
+
+void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient *client) {
+ const auto client_id = client->id();
+ const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [client_id](const AsyncWebSocketClient &c) {
+ return c.id() == client_id;
+ });
+ if (iter != std::end(_clients)) {
+ _clients.erase(iter);
+ }
+}
+
+bool AsyncWebSocket::availableForWriteAll() {
+ return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) {
+ return c.queueIsFull();
+ });
+}
+
+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)) {
+ 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;
+ });
+}
+
+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)) {
+ return nullptr;
+ }
+
+ return &(*iter);
+}
+
+void AsyncWebSocket::close(uint32_t id, uint16_t code, const char *message) {
+ 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) {
+ c.close(code, message);
+ }
+ }
+}
+
+void AsyncWebSocket::cleanupClients(uint16_t maxClients) {
+ if (count() > maxClients) {
+ _clients.front().close();
+ }
+
+ for (auto i = _clients.begin(); i != _clients.end(); ++i) {
+ if (i->shouldBeDeleted()) {
+ _clients.erase(i);
+ break;
+ }
+ }
+}
+
+bool AsyncWebSocket::ping(uint32_t id, const uint8_t *data, size_t len) {
+ AsyncWebSocketClient *c = client(id);
+ return c && c->ping(data, 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)) {
+ hit++;
+ } else {
+ miss++;
+ }
+ }
+ return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
+}
+
+bool AsyncWebSocket::text(uint32_t id, const uint8_t *message, size_t len) {
+ AsyncWebSocketClient *c = client(id);
+ return c && c->text(makeSharedBuffer(message, len));
+}
+bool AsyncWebSocket::text(uint32_t id, const char *message, size_t len) {
+ return text(id, (const uint8_t *)message, len);
+}
+bool AsyncWebSocket::text(uint32_t id, const char *message) {
+ return text(id, message, strlen(message));
+}
+bool AsyncWebSocket::text(uint32_t id, const String &message) {
+ return text(id, message.c_str(), message.length());
+}
+
+#ifdef ESP8266
+bool AsyncWebSocket::text(uint32_t id, const __FlashStringHelper *data) {
+ PGM_P p = reinterpret_cast<PGM_P>(data);
+
+ size_t n = 0;
+ while (true) {
+ if (pgm_read_byte(p + n) == 0) {
+ break;
+ }
+ n += 1;
+ }
+
+ char *message = (char *)malloc(n + 1);
+ bool enqueued = false;
+ if (message) {
+ memcpy_P(message, p, n);
+ message[n] = 0;
+ enqueued = text(id, message, n);
+ free(message);
+ }
+ return enqueued;
+}
+#endif // ESP8266
+
+bool AsyncWebSocket::text(uint32_t id, AsyncWebSocketMessageBuffer *buffer) {
+ bool enqueued = false;
+ if (buffer) {
+ enqueued = text(id, std::move(buffer->_buffer));
+ delete buffer;
+ }
+ return enqueued;
+}
+bool AsyncWebSocket::text(uint32_t id, AsyncWebSocketSharedBuffer buffer) {
+ AsyncWebSocketClient *c = client(id);
+ return c && c->text(buffer);
+}
+
+AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const uint8_t *message, size_t len) {
+ return textAll(makeSharedBuffer(message, len));
+}
+AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const char *message, size_t len) {
+ return textAll((const uint8_t *)message, len);
+}
+AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const char *message) {
+ return textAll(message, strlen(message));
+}
+AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const String &message) {
+ return textAll(message.c_str(), message.length());
+}
+#ifdef ESP8266
+AsyncWebSocket::SendStatus AsyncWebSocket::textAll(const __FlashStringHelper *data) {
+ PGM_P p = reinterpret_cast<PGM_P>(data);
+
+ size_t n = 0;
+ while (1) {
+ if (pgm_read_byte(p + n) == 0) {
+ break;
+ }
+ n += 1;
+ }
+
+ char *message = (char *)malloc(n + 1);
+ AsyncWebSocket::SendStatus status = DISCARDED;
+ if (message) {
+ memcpy_P(message, p, n);
+ message[n] = 0;
+ status = textAll(message, n);
+ free(message);
+ }
+ return status;
+}
+#endif // ESP8266
+AsyncWebSocket::SendStatus AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer *buffer) {
+ AsyncWebSocket::SendStatus status = DISCARDED;
+ if (buffer) {
+ status = textAll(std::move(buffer->_buffer));
+ delete buffer;
+ }
+ return status;
+}
+
+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)) {
+ hit++;
+ } else {
+ miss++;
+ }
+ }
+ return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
+}
+
+bool AsyncWebSocket::binary(uint32_t id, const uint8_t *message, size_t len) {
+ AsyncWebSocketClient *c = client(id);
+ return c && c->binary(makeSharedBuffer(message, len));
+}
+bool AsyncWebSocket::binary(uint32_t id, const char *message, size_t len) {
+ return binary(id, (const uint8_t *)message, len);
+}
+bool AsyncWebSocket::binary(uint32_t id, const char *message) {
+ return binary(id, message, strlen(message));
+}
+bool AsyncWebSocket::binary(uint32_t id, const String &message) {
+ return binary(id, message.c_str(), message.length());
+}
+
+#ifdef ESP8266
+bool AsyncWebSocket::binary(uint32_t id, const __FlashStringHelper *data, size_t len) {
+ PGM_P p = reinterpret_cast<PGM_P>(data);
+ char *message = (char *)malloc(len);
+ bool enqueued = false;
+ if (message) {
+ memcpy_P(message, p, len);
+ enqueued = binary(id, message, len);
+ free(message);
+ }
+ return enqueued;
+}
+#endif // ESP8266
+
+bool AsyncWebSocket::binary(uint32_t id, AsyncWebSocketMessageBuffer *buffer) {
+ bool enqueued = false;
+ if (buffer) {
+ enqueued = binary(id, std::move(buffer->_buffer));
+ delete buffer;
+ }
+ return enqueued;
+}
+bool AsyncWebSocket::binary(uint32_t id, AsyncWebSocketSharedBuffer buffer) {
+ AsyncWebSocketClient *c = client(id);
+ return c && c->binary(buffer);
+}
+
+AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(const uint8_t *message, size_t len) {
+ return binaryAll(makeSharedBuffer(message, len));
+}
+AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(const char *message, size_t len) {
+ return binaryAll((const uint8_t *)message, len);
+}
+AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(const char *message) {
+ return binaryAll(message, strlen(message));
+}
+AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(const String &message) {
+ return binaryAll(message.c_str(), message.length());
+}
+
+#ifdef ESP8266
+AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(const __FlashStringHelper *data, size_t len) {
+ PGM_P p = reinterpret_cast<PGM_P>(data);
+ char *message = (char *)malloc(len);
+ AsyncWebSocket::SendStatus status = DISCARDED;
+ if (message) {
+ memcpy_P(message, p, len);
+ status = binaryAll(message, len);
+ free(message);
+ }
+ return status;
+}
+#endif // ESP8266
+
+AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer *buffer) {
+ AsyncWebSocket::SendStatus status = DISCARDED;
+ if (buffer) {
+ status = binaryAll(std::move(buffer->_buffer));
+ delete buffer;
+ }
+ return status;
+}
+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)) {
+ hit++;
+ } else {
+ miss++;
+ }
+ }
+ return hit == 0 ? DISCARDED : (miss == 0 ? ENQUEUED : PARTIALLY_ENQUEUED);
+}
+
+size_t AsyncWebSocket::printf(uint32_t id, const char *format, ...) {
+ AsyncWebSocketClient *c = client(id);
+ if (c) {
+ va_list arg;
+ va_start(arg, format);
+ size_t len = c->printf(format, arg);
+ va_end(arg);
+ return len;
+ }
+ return 0;
+}
+
+size_t AsyncWebSocket::printfAll(const char *format, ...) {
+ va_list arg;
+ va_start(arg, format);
+ size_t len = vsnprintf(nullptr, 0, format, arg);
+ va_end(arg);
+
+ if (len == 0) {
+ return 0;
+ }
+
+ char *buffer = new char[len + 1];
+
+ if (!buffer) {
+ return 0;
+ }
+
+ va_start(arg, format);
+ len = vsnprintf(buffer, len + 1, format, arg);
+ va_end(arg);
+
+ AsyncWebSocket::SendStatus status = textAll(buffer, len);
+ delete[] buffer;
+ return status == DISCARDED ? 0 : len;
+}
+
+#ifdef ESP8266
+size_t AsyncWebSocket::printf_P(uint32_t id, PGM_P formatP, ...) {
+ AsyncWebSocketClient *c = client(id);
+ if (c != NULL) {
+ va_list arg;
+ va_start(arg, formatP);
+ size_t len = c->printf_P(formatP, arg);
+ va_end(arg);
+ return len;
+ }
+ return 0;
+}
+
+size_t AsyncWebSocket::printfAll_P(PGM_P formatP, ...) {
+ va_list arg;
+ va_start(arg, formatP);
+ size_t len = vsnprintf_P(nullptr, 0, formatP, arg);
+ va_end(arg);
+
+ if (len == 0) {
+ return 0;
+ }
+
+ char *buffer = new char[len + 1];
+
+ if (!buffer) {
+ return 0;
+ }
+
+ va_start(arg, formatP);
+ len = vsnprintf_P(buffer, len + 1, formatP, arg);
+ va_end(arg);
+
+ AsyncWebSocket::SendStatus status = textAll(buffer, len);
+ delete[] buffer;
+ return status == DISCARDED ? 0 : len;
+}
+#endif
+
+const char __WS_STR_CONNECTION[] PROGMEM = {"Connection"};
+const char __WS_STR_UPGRADE[] PROGMEM = {"Upgrade"};
+const char __WS_STR_ORIGIN[] PROGMEM = {"Origin"};
+const char __WS_STR_COOKIE[] PROGMEM = {"Cookie"};
+const char __WS_STR_VERSION[] PROGMEM = {"Sec-WebSocket-Version"};
+const char __WS_STR_KEY[] PROGMEM = {"Sec-WebSocket-Key"};
+const char __WS_STR_PROTOCOL[] PROGMEM = {"Sec-WebSocket-Protocol"};
+const char __WS_STR_ACCEPT[] PROGMEM = {"Sec-WebSocket-Accept"};
+const char __WS_STR_UUID[] PROGMEM = {"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"};
+
+#define WS_STR_UUID_LEN 36
+
+#define WS_STR_CONNECTION FPSTR(__WS_STR_CONNECTION)
+#define WS_STR_UPGRADE FPSTR(__WS_STR_UPGRADE)
+#define WS_STR_ORIGIN FPSTR(__WS_STR_ORIGIN)
+#define WS_STR_COOKIE FPSTR(__WS_STR_COOKIE)
+#define WS_STR_VERSION FPSTR(__WS_STR_VERSION)
+#define WS_STR_KEY FPSTR(__WS_STR_KEY)
+#define WS_STR_PROTOCOL FPSTR(__WS_STR_PROTOCOL)
+#define WS_STR_ACCEPT FPSTR(__WS_STR_ACCEPT)
+#define WS_STR_UUID FPSTR(__WS_STR_UUID)
+
+bool AsyncWebSocket::canHandle(AsyncWebServerRequest *request) const {
+ return _enabled && request->isWebSocketUpgrade() && request->url().equals(_url);
+}
+
+void AsyncWebSocket::handleRequest(AsyncWebServerRequest *request) {
+ if (!request->hasHeader(WS_STR_VERSION) || !request->hasHeader(WS_STR_KEY)) {
+ request->send(400);
+ return;
+ }
+ if (_handshakeHandler != nullptr) {
+ if (!_handshakeHandler(request)) {
+ request->send(401);
+ return;
+ }
+ }
+ const AsyncWebHeader *version = request->getHeader(WS_STR_VERSION);
+ if (version->value().toInt() != 13) {
+ AsyncWebServerResponse *response = request->beginResponse(400);
+ response->addHeader(WS_STR_VERSION, T_13);
+ request->send(response);
+ return;
+ }
+ 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
+ response->addHeader(WS_STR_PROTOCOL, protocol->value());
+ }
+ request->send(response);
+}
+
+AsyncWebSocketMessageBuffer *AsyncWebSocket::makeBuffer(size_t size) {
+ return new AsyncWebSocketMessageBuffer(size);
+}
+
+AsyncWebSocketMessageBuffer *AsyncWebSocket::makeBuffer(const uint8_t *data, size_t size) {
+ return new AsyncWebSocketMessageBuffer(data, size);
+}
+
+/*
+ * Response to Web Socket request - sends the authorization and detaches the TCP Client from the web server
+ * Authentication code from https://github.com/Links2004/arduinoWebSockets/blob/master/src/WebSockets.cpp#L480
+ */
+
+AsyncWebSocketResponse::AsyncWebSocketResponse(const String &key, AsyncWebSocket *server) {
+ _server = server;
+ _code = 101;
+ _sendContentLength = false;
+
+ uint8_t hash[20];
+ char buffer[33];
+
+#if defined(ESP8266) || defined(TARGET_RP2040) || defined(PICO_RP2040) || defined(PICO_RP2350) || defined(TARGET_RP2350)
+ sha1(key + WS_STR_UUID, hash);
+#else
+ String k;
+ 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;
+ sha1.begin();
+ sha1.add((const uint8_t *)k.c_str(), k.length());
+ sha1.calculate();
+ sha1.getBytes(hash);
+#endif
+ base64_encodestate _state;
+ base64_init_encodestate(&_state);
+ int len = base64_encode_block((const char *)hash, 20, buffer, &_state);
+ len = base64_encode_blockend((buffer + len), &_state);
+ addHeader(WS_STR_CONNECTION, WS_STR_UPGRADE);
+ addHeader(WS_STR_UPGRADE, T_WS);
+ addHeader(WS_STR_ACCEPT, buffer);
+}
+
+void AsyncWebSocketResponse::_respond(AsyncWebServerRequest *request) {
+ if (_state == RESPONSE_FAILED) {
+ request->client()->close(true);
+ return;
+ }
+ String out;
+ _assembleHead(out, request->version());
+ request->client()->write(out.c_str(), _headLength);
+ _state = RESPONSE_WAIT_ACK;
+}
+
+size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
+ (void)time;
+
+ if (len) {
+ _server->_newClient(request);
+ }
+
+ return 0;
+}
diff --git a/libraries/ESP_Async_WebServer/src/AsyncWebSocket.h b/libraries/ESP_Async_WebServer/src/AsyncWebSocket.h
new file mode 100644
index 0000000..122aca9
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/AsyncWebSocket.h
@@ -0,0 +1,499 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#ifndef ASYNCWEBSOCKET_H_
+#define ASYNCWEBSOCKET_H_
+
+#include <Arduino.h>
+#ifdef ESP32
+#include <AsyncTCP.h>
+#include <mutex>
+#ifndef WS_MAX_QUEUED_MESSAGES
+#define WS_MAX_QUEUED_MESSAGES 32
+#endif
+#elif defined(ESP8266)
+#include <ESPAsyncTCP.h>
+#ifndef WS_MAX_QUEUED_MESSAGES
+#define WS_MAX_QUEUED_MESSAGES 8
+#endif
+#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 <memory>
+
+#ifdef ESP8266
+#include <Hash.h>
+#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library
+#include <../src/Hash.h>
+#endif
+#endif
+
+#ifndef DEFAULT_MAX_WS_CLIENTS
+#ifdef ESP32
+#define DEFAULT_MAX_WS_CLIENTS 8
+#else
+#define DEFAULT_MAX_WS_CLIENTS 4
+#endif
+#endif
+
+using AsyncWebSocketSharedBuffer = std::shared_ptr<std::vector<uint8_t>>;
+
+class AsyncWebSocket;
+class AsyncWebSocketResponse;
+class AsyncWebSocketClient;
+class AsyncWebSocketControl;
+
+typedef struct {
+ /** Message type as defined by enum AwsFrameType.
+ * Note: Applications will only see WS_TEXT and WS_BINARY.
+ * All other types are handled by the library. */
+ uint8_t message_opcode;
+ /** Frame number of a fragmented message. */
+ uint32_t num;
+ /** Is this the last frame in a fragmented message ?*/
+ uint8_t final;
+ /** Is this frame masked? */
+ uint8_t masked;
+ /** Message type as defined by enum AwsFrameType.
+ * This value is the same as message_opcode for non-fragmented
+ * messages, but may also be WS_CONTINUATION in a fragmented message. */
+ uint8_t opcode;
+ /** Length of the current frame.
+ * This equals the total length of the message if num == 0 && final == true */
+ uint64_t len;
+ /** Mask key */
+ uint8_t mask[4];
+ /** Offset of the data inside the current frame. */
+ uint64_t index;
+} AwsFrameInfo;
+
+typedef enum {
+ WS_DISCONNECTED,
+ WS_CONNECTED,
+ 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_MSG_SENT,
+ 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;
+
+class AsyncWebSocketMessageBuffer {
+ friend AsyncWebSocket;
+ friend AsyncWebSocketClient;
+
+private:
+ AsyncWebSocketSharedBuffer _buffer;
+
+public:
+ AsyncWebSocketMessageBuffer() {}
+ explicit AsyncWebSocketMessageBuffer(size_t size);
+ 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();
+ }
+};
+
+class AsyncWebSocketMessage {
+private:
+ AsyncWebSocketSharedBuffer _WSbuffer;
+ uint8_t _opcode{WS_TEXT};
+ bool _mask{false};
+ AwsMessageStatus _status{WS_MSG_ERROR};
+ size_t _sent{};
+ size_t _ack{};
+ size_t _acked{};
+
+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;
+ }
+
+ void ack(size_t len, uint32_t time);
+ size_t send(AsyncClient *client);
+};
+
+class AsyncWebSocketClient {
+private:
+ AsyncClient *_client;
+ AsyncWebSocket *_server;
+ uint32_t _clientId;
+ AwsClientStatus _status;
+#ifdef ESP32
+ mutable std::recursive_mutex _lock;
+#endif
+ std::deque<AsyncWebSocketControl> _controlQueue;
+ std::deque<AsyncWebSocketMessage> _messageQueue;
+ bool closeWhenFull = true;
+
+ uint8_t _pstate;
+ AwsFrameInfo _pinfo;
+
+ uint32_t _lastMessageTime;
+ uint32_t _keepAlivePeriod;
+
+ bool _queueControl(uint8_t opcode, const uint8_t *data = NULL, size_t len = 0, bool mask = false);
+ bool _queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false);
+ void _runQueue();
+ void _clearQueue();
+
+public:
+ void *_tempObject;
+
+ AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server);
+ ~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;
+ }
+
+ // - 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.
+ // The big issue with this behavior is that is can cause the UI to automatically re-create a new WS connection, which can be filled again,
+ // and so on, causing a resource exhaustion.
+ //
+ // - If "false", the incoming message will be discarded if the queue is full.
+ // This is the default behavior in the original ESPAsyncWebServer library from me-no-dev.
+ // This behavior allows the best performance at the expense of unreliable message delivery in case the queue is full (some messages may be lost).
+ //
+ // - In any case, when the queue is full, a message is logged.
+ // - IT is recommended to use the methods queueIsFull(), availableForWriteAll(), availableForWrite(clientId) to check if the queue is full before sending a message.
+ //
+ // Usage:
+ // - can be set in the onEvent listener when connecting (event type is: WS_EVT_CONNECT)
+ //
+ // 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;
+ }
+
+ IPAddress remoteIP() const;
+ uint16_t remotePort() const;
+
+ bool shouldBeDeleted() const {
+ return !_client;
+ }
+
+ // control frames
+ void close(uint16_t code = 0, const char *message = NULL);
+ bool ping(const uint8_t *data = NULL, size_t len = 0);
+
+ // set auto-ping period in seconds. disabled if zero (default)
+ void keepAlivePeriod(uint16_t seconds) {
+ _keepAlivePeriod = seconds * 1000;
+ }
+ uint16_t keepAlivePeriod() {
+ return (uint16_t)(_keepAlivePeriod / 1000);
+ }
+
+ // data packets
+ void message(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false) {
+ _queueMessage(buffer, opcode, mask);
+ }
+ bool queueIsFull() const;
+ size_t queueLen() const;
+
+ size_t printf(const char *format, ...) __attribute__((format(printf, 2, 3)));
+
+ bool text(AsyncWebSocketSharedBuffer buffer);
+ bool text(const uint8_t *message, size_t len);
+ bool text(const char *message, size_t len);
+ bool text(const char *message);
+ bool text(const String &message);
+ bool text(AsyncWebSocketMessageBuffer *buffer);
+
+ bool binary(AsyncWebSocketSharedBuffer buffer);
+ bool binary(const uint8_t *message, size_t len);
+ bool binary(const char *message, size_t len);
+ bool binary(const char *message);
+ bool binary(const String &message);
+ bool binary(AsyncWebSocketMessageBuffer *buffer);
+
+ bool canSend() const;
+
+ // system callbacks (do not call)
+ void _onAck(size_t len, uint32_t time);
+ void _onError(int8_t);
+ void _onPoll();
+ void _onTimeout(uint32_t time);
+ void _onDisconnect();
+ void _onData(void *pbuf, size_t plen);
+
+#ifdef ESP8266
+ size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
+ bool text(const __FlashStringHelper *message);
+ bool binary(const __FlashStringHelper *message, size_t len);
+#endif
+};
+
+using AwsHandshakeHandler = std::function<bool(AsyncWebServerRequest *request)>;
+using AwsEventHandler = std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len)>;
+
+// WebServer Handler implementation that plays the role of a socket server
+class AsyncWebSocket : public AsyncWebHandler {
+private:
+ String _url;
+ std::list<AsyncWebSocketClient> _clients;
+ uint32_t _cNextId;
+ AwsEventHandler _eventHandler;
+ AwsHandshakeHandler _handshakeHandler;
+ bool _enabled;
+#ifdef ESP32
+ mutable std::mutex _lock;
+#endif
+
+public:
+ typedef enum {
+ DISCARDED = 0,
+ ENQUEUED = 1,
+ PARTIALLY_ENQUEUED = 2,
+ } SendStatus;
+
+ explicit AsyncWebSocket(const char *url, AwsEventHandler handler = nullptr) : _url(url), _cNextId(1), _eventHandler(handler), _enabled(true) {}
+ AsyncWebSocket(const String &url, AwsEventHandler handler = nullptr) : _url(url), _cNextId(1), _eventHandler(handler), _enabled(true) {}
+ ~AsyncWebSocket(){};
+ const char *url() const {
+ return _url.c_str();
+ }
+ 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;
+ }
+
+ void close(uint32_t id, uint16_t code = 0, const char *message = NULL);
+ void closeAll(uint16_t code = 0, const char *message = NULL);
+ void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS);
+
+ bool ping(uint32_t id, const uint8_t *data = NULL, size_t len = 0);
+ SendStatus pingAll(const uint8_t *data = NULL, size_t len = 0); // done
+
+ bool text(uint32_t id, const uint8_t *message, size_t len);
+ bool text(uint32_t id, const char *message, size_t len);
+ bool text(uint32_t id, const char *message);
+ bool text(uint32_t id, const String &message);
+ bool text(uint32_t id, AsyncWebSocketMessageBuffer *buffer);
+ bool text(uint32_t id, AsyncWebSocketSharedBuffer buffer);
+
+ SendStatus textAll(const uint8_t *message, size_t len);
+ SendStatus textAll(const char *message, size_t len);
+ SendStatus textAll(const char *message);
+ SendStatus textAll(const String &message);
+ SendStatus textAll(AsyncWebSocketMessageBuffer *buffer);
+ SendStatus textAll(AsyncWebSocketSharedBuffer buffer);
+
+ bool binary(uint32_t id, const uint8_t *message, size_t len);
+ bool binary(uint32_t id, const char *message, size_t len);
+ bool binary(uint32_t id, const char *message);
+ bool binary(uint32_t id, const String &message);
+ bool binary(uint32_t id, AsyncWebSocketMessageBuffer *buffer);
+ bool binary(uint32_t id, AsyncWebSocketSharedBuffer buffer);
+
+ SendStatus binaryAll(const uint8_t *message, size_t len);
+ SendStatus binaryAll(const char *message, size_t len);
+ SendStatus binaryAll(const char *message);
+ SendStatus binaryAll(const String &message);
+ SendStatus binaryAll(AsyncWebSocketMessageBuffer *buffer);
+ SendStatus binaryAll(AsyncWebSocketSharedBuffer buffer);
+
+ size_t printf(uint32_t id, const char *format, ...) __attribute__((format(printf, 3, 4)));
+ size_t printfAll(const char *format, ...) __attribute__((format(printf, 2, 3)));
+
+#ifdef ESP8266
+ bool text(uint32_t id, const __FlashStringHelper *message);
+ SendStatus textAll(const __FlashStringHelper *message);
+ bool binary(uint32_t id, const __FlashStringHelper *message, size_t len);
+ SendStatus binaryAll(const __FlashStringHelper *message, size_t len);
+ size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__((format(printf, 3, 4)));
+ size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3)));
+#endif
+
+ void onEvent(AwsEventHandler handler) {
+ _eventHandler = handler;
+ }
+ void handleHandshake(AwsHandshakeHandler handler) {
+ _handshakeHandler = handler;
+ }
+
+ // system callbacks (do not call)
+ uint32_t _getNextId() {
+ return _cNextId++;
+ }
+ AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request);
+ void _handleDisconnect(AsyncWebSocketClient *client);
+ void _handleEvent(AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len);
+ bool canHandle(AsyncWebServerRequest *request) const override final;
+ void handleRequest(AsyncWebServerRequest *request) override final;
+
+ // messagebuffer functions/objects.
+ AsyncWebSocketMessageBuffer *makeBuffer(size_t size = 0);
+ AsyncWebSocketMessageBuffer *makeBuffer(const uint8_t *data, size_t size);
+
+ std::list<AsyncWebSocketClient> &getClients() {
+ return _clients;
+ }
+};
+
+// WebServer response to authenticate the socket and detach the tcp client from the web server request
+class AsyncWebSocketResponse : public AsyncWebServerResponse {
+private:
+ String _content;
+ AsyncWebSocket *_server;
+
+public:
+ 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;
+ }
+};
+
+class AsyncWebSocketMessageHandler {
+public:
+ AwsEventHandler eventHandler() const {
+ return _handler;
+ }
+
+ void onConnect(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client)> onConnect) {
+ _onConnect = onConnect;
+ }
+
+ void onDisconnect(std::function<void(AsyncWebSocket *server, uint32_t clientId)> onDisconnect) {
+ _onDisconnect = onDisconnect;
+ }
+
+ /**
+ * Error callback
+ * @param reason null-terminated string
+ * @param len length of the string
+ */
+ void onError(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, uint16_t errorCode, const char *reason, size_t len)> onError) {
+ _onError = onError;
+ }
+
+ /**
+ * Complete message callback
+ * @param data pointer to the data (binary or null-terminated string). This handler expects the user to know which data type he uses.
+ */
+ void onMessage(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const uint8_t *data, size_t len)> onMessage) {
+ _onMessage = onMessage;
+ }
+
+ /**
+ * Fragmented message callback
+ * @param data pointer to the data (binary or null-terminated string), will be null-terminated. This handler expects the user to know which data type he uses.
+ */
+ // clang-format off
+ void onFragment(std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const AwsFrameInfo *frameInfo, const uint8_t *data, size_t len)> onFragment) {
+ _onFragment = onFragment;
+ }
+ // clang-format on
+
+private:
+ // clang-format off
+ std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client)> _onConnect;
+ std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, uint16_t errorCode, const char *reason, size_t len)> _onError;
+ std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const uint8_t *data, size_t len)> _onMessage;
+ std::function<void(AsyncWebSocket *server, AsyncWebSocketClient *client, const AwsFrameInfo *frameInfo, const uint8_t *data, size_t len)> _onFragment;
+ std::function<void(AsyncWebSocket *server, uint32_t clientId)> _onDisconnect;
+ // clang-format on
+
+ // this handler is meant to only support 1-frame messages (== unfragmented messages)
+ AwsEventHandler _handler = [this](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
+ if (type == WS_EVT_CONNECT) {
+ if (_onConnect) {
+ _onConnect(server, client);
+ }
+ } else if (type == WS_EVT_DISCONNECT) {
+ if (_onDisconnect) {
+ _onDisconnect(server, client->id());
+ }
+ } else if (type == WS_EVT_ERROR) {
+ if (_onError) {
+ _onError(server, client, *((uint16_t *)arg), (const char *)data, len);
+ }
+ } else if (type == WS_EVT_DATA) {
+ AwsFrameInfo *info = (AwsFrameInfo *)arg;
+ if (info->opcode == WS_TEXT) {
+ data[len] = 0;
+ }
+ if (info->final && info->index == 0 && info->len == len) {
+ if (_onMessage) {
+ _onMessage(server, client, data, len);
+ }
+ } else {
+ if (_onFragment) {
+ _onFragment(server, client, info, data, len);
+ }
+ }
+ }
+ };
+};
+
+#endif /* ASYNCWEBSOCKET_H_ */
diff --git a/libraries/ESP_Async_WebServer/src/BackPort_SHA1Builder.cpp b/libraries/ESP_Async_WebServer/src/BackPort_SHA1Builder.cpp
new file mode 100644
index 0000000..06a73a5
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/BackPort_SHA1Builder.cpp
@@ -0,0 +1,284 @@
+/*
+ * FIPS-180-1 compliant SHA-1 implementation
+ *
+ * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file is part of mbed TLS (https://tls.mbed.org)
+ * Modified for esp32 by Lucas Saavedra Vaz on 11 Jan 2024
+ */
+
+#include <Arduino.h>
+#if ESP_IDF_VERSION_MAJOR < 5
+
+#include "BackPort_SHA1Builder.h"
+
+// 32-bit integer manipulation macros (big endian)
+
+#ifndef GET_UINT32_BE
+#define GET_UINT32_BE(n, b, i) \
+ { (n) = ((uint32_t)(b)[(i)] << 24) | ((uint32_t)(b)[(i) + 1] << 16) | ((uint32_t)(b)[(i) + 2] << 8) | ((uint32_t)(b)[(i) + 3]); }
+#endif
+
+#ifndef PUT_UINT32_BE
+#define PUT_UINT32_BE(n, b, i) \
+ { \
+ (b)[(i)] = (uint8_t)((n) >> 24); \
+ (b)[(i) + 1] = (uint8_t)((n) >> 16); \
+ (b)[(i) + 2] = (uint8_t)((n) >> 8); \
+ (b)[(i) + 3] = (uint8_t)((n)); \
+ }
+#endif
+
+// Constants
+
+static const uint8_t sha1_padding[64] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+// Private methods
+
+void SHA1Builder::process(const uint8_t *data) {
+ uint32_t temp, W[16], A, B, C, D, E;
+
+ GET_UINT32_BE(W[0], data, 0);
+ GET_UINT32_BE(W[1], data, 4);
+ GET_UINT32_BE(W[2], data, 8);
+ GET_UINT32_BE(W[3], data, 12);
+ GET_UINT32_BE(W[4], data, 16);
+ GET_UINT32_BE(W[5], data, 20);
+ GET_UINT32_BE(W[6], data, 24);
+ GET_UINT32_BE(W[7], data, 28);
+ GET_UINT32_BE(W[8], data, 32);
+ GET_UINT32_BE(W[9], data, 36);
+ GET_UINT32_BE(W[10], data, 40);
+ GET_UINT32_BE(W[11], data, 44);
+ GET_UINT32_BE(W[12], data, 48);
+ GET_UINT32_BE(W[13], data, 52);
+ GET_UINT32_BE(W[14], data, 56);
+ GET_UINT32_BE(W[15], data, 60);
+
+#define sha1_S(x, n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n)))
+
+#define sha1_R(t) (temp = W[(t - 3) & 0x0F] ^ W[(t - 8) & 0x0F] ^ W[(t - 14) & 0x0F] ^ W[t & 0x0F], (W[t & 0x0F] = sha1_S(temp, 1)))
+
+#define sha1_P(a, b, c, d, e, x) \
+ { \
+ e += sha1_S(a, 5) + sha1_F(b, c, d) + sha1_K + x; \
+ b = sha1_S(b, 30); \
+ }
+
+ A = state[0];
+ B = state[1];
+ C = state[2];
+ D = state[3];
+ E = state[4];
+
+#define sha1_F(x, y, z) (z ^ (x & (y ^ z)))
+#define sha1_K 0x5A827999
+
+ sha1_P(A, B, C, D, E, W[0]);
+ sha1_P(E, A, B, C, D, W[1]);
+ sha1_P(D, E, A, B, C, W[2]);
+ sha1_P(C, D, E, A, B, W[3]);
+ sha1_P(B, C, D, E, A, W[4]);
+ sha1_P(A, B, C, D, E, W[5]);
+ sha1_P(E, A, B, C, D, W[6]);
+ sha1_P(D, E, A, B, C, W[7]);
+ sha1_P(C, D, E, A, B, W[8]);
+ sha1_P(B, C, D, E, A, W[9]);
+ sha1_P(A, B, C, D, E, W[10]);
+ sha1_P(E, A, B, C, D, W[11]);
+ sha1_P(D, E, A, B, C, W[12]);
+ sha1_P(C, D, E, A, B, W[13]);
+ sha1_P(B, C, D, E, A, W[14]);
+ sha1_P(A, B, C, D, E, W[15]);
+ sha1_P(E, A, B, C, D, sha1_R(16));
+ sha1_P(D, E, A, B, C, sha1_R(17));
+ sha1_P(C, D, E, A, B, sha1_R(18));
+ sha1_P(B, C, D, E, A, sha1_R(19));
+
+#undef sha1_K
+#undef sha1_F
+
+#define sha1_F(x, y, z) (x ^ y ^ z)
+#define sha1_K 0x6ED9EBA1
+
+ sha1_P(A, B, C, D, E, sha1_R(20));
+ sha1_P(E, A, B, C, D, sha1_R(21));
+ sha1_P(D, E, A, B, C, sha1_R(22));
+ sha1_P(C, D, E, A, B, sha1_R(23));
+ sha1_P(B, C, D, E, A, sha1_R(24));
+ sha1_P(A, B, C, D, E, sha1_R(25));
+ sha1_P(E, A, B, C, D, sha1_R(26));
+ sha1_P(D, E, A, B, C, sha1_R(27));
+ sha1_P(C, D, E, A, B, sha1_R(28));
+ sha1_P(B, C, D, E, A, sha1_R(29));
+ sha1_P(A, B, C, D, E, sha1_R(30));
+ sha1_P(E, A, B, C, D, sha1_R(31));
+ sha1_P(D, E, A, B, C, sha1_R(32));
+ sha1_P(C, D, E, A, B, sha1_R(33));
+ sha1_P(B, C, D, E, A, sha1_R(34));
+ sha1_P(A, B, C, D, E, sha1_R(35));
+ sha1_P(E, A, B, C, D, sha1_R(36));
+ sha1_P(D, E, A, B, C, sha1_R(37));
+ sha1_P(C, D, E, A, B, sha1_R(38));
+ sha1_P(B, C, D, E, A, sha1_R(39));
+
+#undef sha1_K
+#undef sha1_F
+
+#define sha1_F(x, y, z) ((x & y) | (z & (x | y)))
+#define sha1_K 0x8F1BBCDC
+
+ sha1_P(A, B, C, D, E, sha1_R(40));
+ sha1_P(E, A, B, C, D, sha1_R(41));
+ sha1_P(D, E, A, B, C, sha1_R(42));
+ sha1_P(C, D, E, A, B, sha1_R(43));
+ sha1_P(B, C, D, E, A, sha1_R(44));
+ sha1_P(A, B, C, D, E, sha1_R(45));
+ sha1_P(E, A, B, C, D, sha1_R(46));
+ sha1_P(D, E, A, B, C, sha1_R(47));
+ sha1_P(C, D, E, A, B, sha1_R(48));
+ sha1_P(B, C, D, E, A, sha1_R(49));
+ sha1_P(A, B, C, D, E, sha1_R(50));
+ sha1_P(E, A, B, C, D, sha1_R(51));
+ sha1_P(D, E, A, B, C, sha1_R(52));
+ sha1_P(C, D, E, A, B, sha1_R(53));
+ sha1_P(B, C, D, E, A, sha1_R(54));
+ sha1_P(A, B, C, D, E, sha1_R(55));
+ sha1_P(E, A, B, C, D, sha1_R(56));
+ sha1_P(D, E, A, B, C, sha1_R(57));
+ sha1_P(C, D, E, A, B, sha1_R(58));
+ sha1_P(B, C, D, E, A, sha1_R(59));
+
+#undef sha1_K
+#undef sha1_F
+
+#define sha1_F(x, y, z) (x ^ y ^ z)
+#define sha1_K 0xCA62C1D6
+
+ sha1_P(A, B, C, D, E, sha1_R(60));
+ sha1_P(E, A, B, C, D, sha1_R(61));
+ sha1_P(D, E, A, B, C, sha1_R(62));
+ sha1_P(C, D, E, A, B, sha1_R(63));
+ sha1_P(B, C, D, E, A, sha1_R(64));
+ sha1_P(A, B, C, D, E, sha1_R(65));
+ sha1_P(E, A, B, C, D, sha1_R(66));
+ sha1_P(D, E, A, B, C, sha1_R(67));
+ sha1_P(C, D, E, A, B, sha1_R(68));
+ sha1_P(B, C, D, E, A, sha1_R(69));
+ sha1_P(A, B, C, D, E, sha1_R(70));
+ sha1_P(E, A, B, C, D, sha1_R(71));
+ sha1_P(D, E, A, B, C, sha1_R(72));
+ sha1_P(C, D, E, A, B, sha1_R(73));
+ sha1_P(B, C, D, E, A, sha1_R(74));
+ sha1_P(A, B, C, D, E, sha1_R(75));
+ sha1_P(E, A, B, C, D, sha1_R(76));
+ sha1_P(D, E, A, B, C, sha1_R(77));
+ sha1_P(C, D, E, A, B, sha1_R(78));
+ sha1_P(B, C, D, E, A, sha1_R(79));
+
+#undef sha1_K
+#undef sha1_F
+
+ state[0] += A;
+ state[1] += B;
+ state[2] += C;
+ state[3] += D;
+ state[4] += E;
+}
+
+// Public methods
+
+void SHA1Builder::begin() {
+ total[0] = 0;
+ total[1] = 0;
+
+ state[0] = 0x67452301;
+ state[1] = 0xEFCDAB89;
+ state[2] = 0x98BADCFE;
+ state[3] = 0x10325476;
+ state[4] = 0xC3D2E1F0;
+
+ memset(buffer, 0x00, sizeof(buffer));
+ memset(hash, 0x00, sizeof(hash));
+}
+
+void SHA1Builder::add(const uint8_t *data, size_t len) {
+ size_t fill;
+ uint32_t left;
+
+ if (len == 0) {
+ return;
+ }
+
+ left = total[0] & 0x3F;
+ fill = 64 - left;
+
+ total[0] += (uint32_t)len;
+ total[0] &= 0xFFFFFFFF;
+
+ if (total[0] < (uint32_t)len) {
+ total[1]++;
+ }
+
+ if (left && len >= fill) {
+ memcpy((void *)(buffer + left), data, fill);
+ process(buffer);
+ data += fill;
+ len -= fill;
+ left = 0;
+ }
+
+ while (len >= 64) {
+ process(data);
+ data += 64;
+ len -= 64;
+ }
+
+ if (len > 0) {
+ memcpy((void *)(buffer + left), data, len);
+ }
+}
+
+void SHA1Builder::calculate(void) {
+ uint32_t last, padn;
+ uint32_t high, low;
+ uint8_t msglen[8];
+
+ high = (total[0] >> 29) | (total[1] << 3);
+ low = (total[0] << 3);
+
+ PUT_UINT32_BE(high, msglen, 0);
+ PUT_UINT32_BE(low, msglen, 4);
+
+ last = total[0] & 0x3F;
+ padn = (last < 56) ? (56 - last) : (120 - last);
+
+ add((uint8_t *)sha1_padding, padn);
+ add(msglen, 8);
+
+ PUT_UINT32_BE(state[0], hash, 0);
+ PUT_UINT32_BE(state[1], hash, 4);
+ PUT_UINT32_BE(state[2], hash, 8);
+ PUT_UINT32_BE(state[3], hash, 12);
+ PUT_UINT32_BE(state[4], hash, 16);
+}
+
+void SHA1Builder::getBytes(uint8_t *output) {
+ memcpy(output, hash, SHA1_HASH_SIZE);
+}
+
+#endif // ESP_IDF_VERSION_MAJOR < 5
diff --git a/libraries/ESP_Async_WebServer/src/BackPort_SHA1Builder.h b/libraries/ESP_Async_WebServer/src/BackPort_SHA1Builder.h
new file mode 100644
index 0000000..e7eafbe
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/BackPort_SHA1Builder.h
@@ -0,0 +1,44 @@
+// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <Arduino.h>
+#if ESP_IDF_VERSION_MAJOR < 5
+
+#ifndef SHA1Builder_h
+#define SHA1Builder_h
+
+#include <Stream.h>
+#include <WString.h>
+
+#define SHA1_HASH_SIZE 20
+
+class SHA1Builder {
+private:
+ uint32_t total[2]; /* number of bytes processed */
+ uint32_t state[5]; /* intermediate digest state */
+ unsigned char buffer[64]; /* data block being processed */
+ uint8_t hash[SHA1_HASH_SIZE]; /* SHA-1 result */
+
+ void process(const uint8_t *data);
+
+public:
+ void begin();
+ void add(const uint8_t *data, size_t len);
+ void calculate();
+ void getBytes(uint8_t *output);
+};
+
+#endif // SHA1Builder_h
+
+#endif // ESP_IDF_VERSION_MAJOR < 5
diff --git a/libraries/ESP_Async_WebServer/src/ChunkPrint.cpp b/libraries/ESP_Async_WebServer/src/ChunkPrint.cpp
new file mode 100644
index 0000000..4617d34
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/ChunkPrint.cpp
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#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) {
+ _to_skip--;
+ return 1;
+ } else if (_to_write > 0) {
+ _to_write--;
+ _destination[_pos++] = c;
+ return 1;
+ }
+ return 0;
+}
diff --git a/libraries/ESP_Async_WebServer/src/ChunkPrint.h b/libraries/ESP_Async_WebServer/src/ChunkPrint.h
new file mode 100644
index 0000000..04938b3
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/ChunkPrint.h
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#ifndef CHUNKPRINT_H
+#define CHUNKPRINT_H
+
+#include <Print.h>
+
+class ChunkPrint : public Print {
+private:
+ uint8_t *_destination;
+ size_t _to_skip;
+ size_t _to_write;
+ size_t _pos;
+
+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);
+ }
+};
+#endif
diff --git a/libraries/ESP_Async_WebServer/src/ESPAsyncWebServer.h b/libraries/ESP_Async_WebServer/src/ESPAsyncWebServer.h
new file mode 100644
index 0000000..24233cd
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/ESPAsyncWebServer.h
@@ -0,0 +1,1217 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#ifndef _ESPAsyncWebServer_H_
+#define _ESPAsyncWebServer_H_
+
+#include <Arduino.h>
+#include <FS.h>
+#include <lwip/tcpbase.h>
+
+#include <algorithm>
+#include <deque>
+#include <functional>
+#include <list>
+#include <unordered_map>
+#include <vector>
+
+#ifdef ESP32
+#include <AsyncTCP.h>
+#include <WiFi.h>
+#elif defined(ESP8266)
+#include <ESP8266WiFi.h>
+#include <ESPAsyncTCP.h>
+#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
+#include <RPAsyncTCP.h>
+#include <HTTP_Method.h>
+#include <WiFi.h>
+#include <http_parser.h>
+#else
+#error Platform not supported
+#endif
+
+#include "literals.h"
+
+#include "AsyncWebServerVersion.h"
+#define ASYNCWEBSERVER_FORK_ESP32Async
+
+#ifdef ASYNCWEBSERVER_REGEX
+#define ASYNCWEBSERVER_REGEX_ATTRIBUTE
+#else
+#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined")))
+#endif
+
+// See https://github.com/ESP32Async/ESPAsyncWebServer/commit/3d3456e9e81502a477f6498c44d0691499dda8f9#diff-646b25b11691c11dce25529e3abce843f0ba4bd07ab75ec9eee7e72b06dbf13fR388-R392
+// This setting slowdown chunk serving but avoids crashing or deadlocks in the case where slow chunk responses are created, like file serving form SD Card
+#ifndef ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
+#define ASYNCWEBSERVER_USE_CHUNK_INFLIGHT 1
+#endif
+
+class AsyncWebServer;
+class AsyncWebServerRequest;
+class AsyncWebServerResponse;
+class AsyncWebHeader;
+class AsyncWebParameter;
+class AsyncWebRewrite;
+class AsyncWebHandler;
+class AsyncStaticWebHandler;
+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,
+ HTTP_DELETE = 0b00000100,
+ HTTP_PUT = 0b00001000,
+ HTTP_PATCH = 0b00010000,
+ HTTP_HEAD = 0b00100000,
+ HTTP_OPTIONS = 0b01000000,
+ HTTP_ANY = 0b01111111,
+} WebRequestMethod;
+#endif
+#endif
+
+#ifndef HAVE_FS_FILE_OPEN_MODE
+namespace fs {
+class FileOpenMode {
+public:
+ static const char *read;
+ static const char *write;
+ static const char *append;
+};
+}; // namespace fs
+#else
+#include "FileOpenMode.h"
+#endif
+
+// if this value is returned when asked for data, packet will not be sent and you will be asked for data again
+#define RESPONSE_TRY_AGAIN 0xFFFFFFFF
+#define RESPONSE_STREAM_BUFFER_SIZE 1460
+
+typedef uint8_t WebRequestMethodComposite;
+typedef std::function<void(void)> ArDisconnectHandler;
+
+/*
+ * PARAMETER :: Chainable object to hold GET/POST and FILE parameters
+ * */
+
+class AsyncWebParameter {
+private:
+ String _name;
+ String _value;
+ size_t _size;
+ bool _isForm;
+ 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;
+ }
+};
+
+/*
+ * HEADER :: Chainable object to hold the headers
+ * */
+
+class AsyncWebHeader {
+private:
+ String _name;
+ String _value;
+
+public:
+ AsyncWebHeader(const AsyncWebHeader &) = default;
+ AsyncWebHeader(const char *name, const char *value) : _name(name), _value(value) {}
+ AsyncWebHeader(const String &name, const String &value) : _name(name), _value(value) {}
+ AsyncWebHeader(const String &data);
+
+ AsyncWebHeader &operator=(const AsyncWebHeader &) = default;
+
+ const String &name() const {
+ return _name;
+ }
+ const String &value() const {
+ return _value;
+ }
+ String toString() const;
+};
+
+/*
+ * REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect
+ * */
+
+typedef enum {
+ RCT_NOT_USED = -1,
+ RCT_DEFAULT = 0,
+ RCT_HTTP,
+ RCT_WS,
+ RCT_EVENT,
+ RCT_MAX
+} RequestedConnectionType;
+
+// this enum is similar to Arduino WebServer's AsyncAuthType and PsychicHttp
+typedef enum {
+ AUTH_NONE = 0, // always allow
+ AUTH_BASIC = 1,
+ AUTH_DIGEST = 2,
+ AUTH_BEARER = 3,
+ AUTH_OTHER = 4,
+ AUTH_DENIED = 255, // always returns 401
+} AsyncAuthType;
+
+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;
+ friend class AsyncWebServer;
+ friend class AsyncCallbackWebHandler;
+
+private:
+ AsyncClient *_client;
+ AsyncWebServer *_server;
+ AsyncWebHandler *_handler;
+ AsyncWebServerResponse *_response;
+ ArDisconnectHandler _onDisconnectfn;
+
+ 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;
+
+ uint8_t _version;
+ WebRequestMethodComposite _method;
+ String _url;
+ String _host;
+ String _contentType;
+ String _boundary;
+ String _authorization;
+ RequestedConnectionType _reqconntype;
+ AsyncAuthType _authMethod = AsyncAuthType::AUTH_NONE;
+ bool _isMultipart;
+ bool _isPlainPost;
+ bool _expectingContinue;
+ size_t _contentLength;
+ size_t _parsedLength;
+
+ std::list<AsyncWebHeader> _headers;
+ std::list<AsyncWebParameter> _params;
+ std::list<String> _pathParams;
+
+ std::unordered_map<const char *, String, std::hash<const char *>, std::equal_to<const char *>> _attributes;
+
+ uint8_t _multiParseState;
+ uint8_t _boundaryPosition;
+ size_t _itemStartIndex;
+ size_t _itemSize;
+ String _itemName;
+ String _itemFilename;
+ String _itemType;
+ String _itemValue;
+ uint8_t *_itemBuffer;
+ size_t _itemBufferIndex;
+ bool _itemIsFile;
+
+ void _onPoll();
+ void _onAck(size_t len, uint32_t time);
+ void _onError(int8_t error);
+ void _onTimeout(uint32_t time);
+ void _onDisconnect();
+ void _onData(void *buf, size_t len);
+
+ void _addPathParam(const char *param);
+
+ bool _parseReqHead();
+ bool _parseReqHeader();
+ void _parseLine();
+ void _parsePlainPostChar(uint8_t data);
+ void _parseMultipartPostByte(uint8_t data, bool last);
+ void _addGetParams(const String &params);
+
+ void _handleUploadStart();
+ void _handleUploadByte(uint8_t data, bool last);
+ void _handleUploadEnd();
+
+ void _send();
+ void _runMiddlewareChain();
+
+public:
+ File _tempFile;
+ void *_tempObject;
+
+ 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;
+ }
+
+ 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);
+ }
+ void onDisconnect(ArDisconnectHandler fn);
+
+ // hash is the string representation of:
+ // base64(user:pass) for basic or
+ // 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(AsyncAuthType method, const char *realm = nullptr, const char *_authFailMsg = nullptr);
+
+ // 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.")]]
+#endif
+ void addInterestingHeader(__unused const char *name) {
+ }
+#ifndef ESP8266
+ [[deprecated("All headers are now collected. Use removeHeader(name) or AsyncHeaderFreeMiddleware if you really need to free some headers.")]]
+#endif
+ void addInterestingHeader(__unused const String &name) {
+ }
+
+ /**
+ * @brief issue HTTP redirect response with Location header
+ *
+ * @param url - url to redirect to
+ * @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 send(AsyncWebServerResponse *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, 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 {
+ 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(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 {
+ 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(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 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)")]]
+#endif
+ void send_P(int code, const String &contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr) {
+ send(code, contentType, content, len, callback);
+ }
+#ifndef ESP8266
+ [[deprecated("Replaced by send(int code, const String& contentType, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr)")]]
+ void send_P(int code, const String &contentType, PGM_P content, AwsTemplateProcessor callback = nullptr) {
+ send(code, contentType, content, callback);
+ }
+#else
+ void send_P(int code, const String &contentType, PGM_P content, AwsTemplateProcessor callback = nullptr) {
+ send(beginResponse_P(code, contentType, content, callback));
+ }
+#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, 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(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(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(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 *beginChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr);
+ AsyncWebServerResponse *beginChunkedResponse(const String &contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) {
+ return beginChunkedResponse(contentType.c_str(), callback, templateCallback);
+ }
+
+ AsyncResponseStream *beginResponseStream(const char *contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE);
+ AsyncResponseStream *beginResponseStream(const String &contentType, size_t bufferSize = RESPONSE_STREAM_BUFFER_SIZE) {
+ 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)")]]
+#endif
+ AsyncWebServerResponse *beginResponse_P(int code, const String &contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback = nullptr) {
+ 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)"
+ )]]
+#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
+ *
+ * @param name
+ * @param post
+ * @param file
+ * @return const AsyncWebParameter*
+ */
+ 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);
+ };
+#ifdef ESP8266
+ const AsyncWebParameter *getParam(const __FlashStringHelper *data, bool post, bool file) const;
+#endif
+
+ /**
+ * @brief Get request parameter by number
+ * i.e., n-th parameter
+ * @param num
+ * @return const AsyncWebParameter*
+ */
+ const AsyncWebParameter *getParam(size_t num) const;
+ const AsyncWebParameter *getParam(int num) const {
+ return num < 0 ? nullptr : getParam((size_t)num);
+ }
+
+ size_t args() const {
+ return params();
+ } // 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());
+ };
+#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 &arg(int i) const {
+ return i < 0 ? emptyString : arg((size_t)i);
+ };
+ const String &argName(size_t i) const; // get request argument name by number
+ const String &argName(int i) const {
+ return i < 0 ? emptyString : argName((size_t)i);
+ };
+ bool hasArg(const char *name) const; // check if argument exists
+ bool hasArg(const String &name) const {
+ return hasArg(name.c_str());
+ };
+#ifdef ESP8266
+ bool hasArg(const __FlashStringHelper *data) const; // check if F(argument) exists
+#endif
+
+ const String &ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const;
+ const String &ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(int i) const {
+ return i < 0 ? emptyString : pathArg((size_t)i);
+ }
+
+ // get request header value by name
+ const String &header(const char *name) const;
+ 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)
+#endif
+
+ const String &header(size_t i) const; // get request header value by number
+ const String &header(int i) const {
+ return i < 0 ? emptyString : header((size_t)i);
+ };
+ const String &headerName(size_t i) const; // get request header name by number
+ const String &headerName(int i) const {
+ return i < 0 ? emptyString : headerName((size_t)i);
+ };
+
+ size_t headers() const; // get header count
+
+ // check if header exists
+ bool hasHeader(const char *name) const;
+ 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());
+ };
+#ifdef ESP8266
+ const AsyncWebHeader *getHeader(const __FlashStringHelper *data) const;
+#endif
+
+ const AsyncWebHeader *getHeader(size_t num) const;
+ const AsyncWebHeader *getHeader(int num) const {
+ return num < 0 ? nullptr : getHeader((size_t)num);
+ };
+
+ const std::list<AsyncWebHeader> &getHeaders() const {
+ return _headers;
+ }
+
+ size_t getHeaderNames(std::vector<const char *> &names) const;
+
+ // Remove a header from the request.
+ // 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();
+ }
+
+ 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);
+ };
+#ifdef ESP8266
+ 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);
+ }
+
+ 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;
+ long getAttribute(const char *name, long defaultValue) const;
+ float getAttribute(const char *name, float defaultValue) const;
+ double getAttribute(const char *name, double defaultValue) const;
+
+ String urlDecode(const String &text) const;
+};
+
+/*
+ * FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server)
+ * */
+
+using ArRequestFilterFunction = std::function<bool(AsyncWebServerRequest *request)>;
+
+bool ON_STA_FILTER(AsyncWebServerRequest *request);
+
+bool ON_AP_FILTER(AsyncWebServerRequest *request);
+
+/*
+ * MIDDLEWARE :: Request interceptor, assigned to a AsyncWebHandler (or the server), which can be used:
+ * 1. to run some code before the final handler is executed (e.g. check authentication)
+ * 2. decide whether to proceed or not with the next handler
+ * */
+
+using ArMiddlewareNext = std::function<void(void)>;
+using ArMiddlewareCallback = std::function<void(AsyncWebServerRequest *request, ArMiddlewareNext next)>;
+
+// Middleware is a base class for all middleware
+class AsyncMiddleware {
+public:
+ virtual ~AsyncMiddleware() {}
+ virtual void run(__unused AsyncWebServerRequest *request, __unused ArMiddlewareNext next) {
+ return next();
+ };
+
+private:
+ friend class AsyncWebHandler;
+ friend class AsyncEventSource;
+ friend class AsyncMiddlewareChain;
+ bool _freeOnRemoval = false;
+};
+
+// Create a custom middleware by providing an anonymous callback function
+class AsyncMiddlewareFunction : public AsyncMiddleware {
+public:
+ AsyncMiddlewareFunction(ArMiddlewareCallback fn) : _fn(fn) {}
+ void run(AsyncWebServerRequest *request, ArMiddlewareNext next) override {
+ return _fn(request, next);
+ };
+
+private:
+ ArMiddlewareCallback _fn;
+};
+
+// For internal use only: super class to add/remove middleware to server or handlers
+class AsyncMiddlewareChain {
+public:
+ ~AsyncMiddlewareChain();
+
+ void addMiddleware(ArMiddlewareCallback fn);
+ void addMiddleware(AsyncMiddleware *middleware);
+ void addMiddlewares(std::vector<AsyncMiddleware *> middlewares);
+ bool removeMiddleware(AsyncMiddleware *middleware);
+
+ // For internal use only
+ void _runChain(AsyncWebServerRequest *request, ArMiddlewareNext finalizer);
+
+protected:
+ std::list<AsyncMiddleware *> _middlewares;
+};
+
+// AsyncAuthenticationMiddleware is a middleware that checks if the request is authenticated
+class AsyncAuthenticationMiddleware : public AsyncMiddleware {
+public:
+ void setUsername(const char *username);
+ void setPassword(const char *password);
+ void setPasswordHash(const char *hash);
+
+ 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
+ // AUTH_BASIC: basic authentication
+ // AUTH_DIGEST: digest authentication
+ // AUTH_BEARER: bearer token authentication
+ // 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;
+ }
+
+ // 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.
+ // returns true if the hash was successfully generated and replaced
+ bool generateHash();
+
+ // returns true if the username and password (or hash) are set
+ bool hasCredentials() const {
+ return _hasCreds;
+ }
+
+ bool allowed(AsyncWebServerRequest *request) const;
+
+ void run(AsyncWebServerRequest *request, ArMiddlewareNext next);
+
+private:
+ String _username;
+ String _credentials;
+ bool _hash = false;
+
+ String _realm = asyncsrv::T_LOGIN_REQ;
+ AsyncAuthType _authMethod = AsyncAuthType::AUTH_NONE;
+ String _authFailMsg;
+ bool _hasCreds = false;
+};
+
+using ArAuthorizeFunction = std::function<bool(AsyncWebServerRequest *request)>;
+// AsyncAuthorizationMiddleware is a middleware that checks if the request is authorized
+class AsyncAuthorizationMiddleware : public AsyncMiddleware {
+public:
+ 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();
+ }
+
+private:
+ int _code;
+ ArAuthorizeFunction _authz;
+};
+
+// 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.remove(name);
+ }
+
+ void run(AsyncWebServerRequest *request, ArMiddlewareNext next);
+
+private:
+ 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.remove(name);
+ }
+
+ void run(AsyncWebServerRequest *request, ArMiddlewareNext next);
+
+private:
+ 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 run(AsyncWebServerRequest *request, ArMiddlewareNext next);
+
+private:
+ Print *_out = nullptr;
+ bool _enabled = true;
+};
+
+// 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 addCORSHeaders(AsyncWebServerResponse *response);
+
+ void run(AsyncWebServerRequest *request, ArMiddlewareNext next);
+
+private:
+ String _origin = "*";
+ String _methods = "*";
+ String _headers = "*";
+ bool _credentials = true;
+ uint32_t _maxAge = 86400;
+};
+
+// Rate limit Middleware
+class AsyncRateLimitMiddleware : public AsyncMiddleware {
+public:
+ void setMaxRequests(size_t maxRequests) {
+ _maxRequests = maxRequests;
+ }
+ void setWindowSize(uint32_t seconds) {
+ _windowSizeMillis = seconds * 1000;
+ }
+
+ bool isRequestAllowed(uint32_t &retryAfterSeconds);
+
+ void run(AsyncWebServerRequest *request, ArMiddlewareNext next);
+
+private:
+ size_t _maxRequests = 0;
+ uint32_t _windowSizeMillis = 0;
+ std::list<uint32_t> _requestTimes;
+};
+
+/*
+ * REWRITE :: One instance can be handle any Request (done by the Server)
+ * */
+
+class AsyncWebRewrite {
+protected:
+ String _from;
+ String _toUrl;
+ String _params;
+ ArRequestFilterFunction _filter{nullptr};
+
+public:
+ AsyncWebRewrite(const char *from, const char *to) : _from(from), _toUrl(to) {
+ int index = _toUrl.indexOf('?');
+ if (index > 0) {
+ _params = _toUrl.substring(index + 1);
+ _toUrl = _toUrl.substring(0, index);
+ }
+ }
+ virtual ~AsyncWebRewrite() {}
+ AsyncWebRewrite &setFilter(ArRequestFilterFunction fn) {
+ _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);
+ }
+};
+
+/*
+ * HANDLER :: One instance can be attached to any Request (done by the Server)
+ * */
+
+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);
+ };
+ 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 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;
+ }
+};
+
+/*
+ * RESPONSE :: One instance is created for each Request (attached by the Handler)
+ * */
+
+typedef enum {
+ RESPONSE_SETUP,
+ RESPONSE_HEADERS,
+ RESPONSE_CONTENT,
+ RESPONSE_WAIT_ACK,
+ RESPONSE_END,
+ RESPONSE_FAILED
+} WebResponseState;
+
+class AsyncWebServerResponse {
+protected:
+ int _code;
+ std::list<AsyncWebHeader> _headers;
+ String _contentType;
+ size_t _contentLength;
+ bool _sendContentLength;
+ bool _chunked;
+ size_t _headLength;
+ size_t _sentLength;
+ size_t _ackedLength;
+ size_t _writtenLength;
+ WebResponseState _state;
+
+ static bool headerMustBePresentOnce(const String &name);
+
+public:
+ static const char *responseCodeToString(int code);
+
+public:
+ AsyncWebServerResponse();
+ virtual ~AsyncWebServerResponse() {}
+ void setCode(int code);
+ int code() const {
+ return _code;
+ }
+ void setContentLength(size_t len);
+ 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 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;
+ }
+
+#ifndef ESP8266
+ [[deprecated("Use instead: _assembleHead(String& buffer, uint8_t version)")]]
+#endif
+ String _assembleHead(uint8_t version) {
+ String buffer;
+ _assembleHead(buffer, version);
+ return buffer;
+ }
+ void _assembleHead(String &buffer, uint8_t version);
+
+ virtual bool _started() const;
+ virtual bool _finished() const;
+ virtual bool _failed() const;
+ virtual bool _sourceValid() const;
+ virtual void _respond(AsyncWebServerRequest *request);
+ virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time);
+};
+
+/*
+ * SERVER :: One instance
+ * */
+
+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, uint8_t *data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction;
+
+class AsyncWebServer : public AsyncMiddlewareChain {
+protected:
+ AsyncServer _server;
+ std::list<std::shared_ptr<AsyncWebRewrite>> _rewrites;
+ std::list<std::unique_ptr<AsyncWebHandler>> _handlers;
+ AsyncCallbackWebHandler *_catchAllHandler;
+
+public:
+ AsyncWebServer(uint16_t port);
+ ~AsyncWebServer();
+
+ void begin();
+ void end();
+
+ tcp_state state() const {
+#ifdef ESP8266
+ // ESPAsyncTCP and RPAsyncTCP methods are not corrected declared with const for immutable ones.
+ return static_cast<tcp_state>(const_cast<AsyncWebServer *>(this)->_server.status());
+#else
+ return static_cast<tcp_state>(_server.status());
+#endif
+ }
+
+#if ASYNC_TCP_SSL_ENABLED
+ void onSslFileRequest(AcSSlFileHandler cb, void *arg);
+ void beginSecure(const char *cert, const char *private_key_file, const char *password);
+#endif
+
+ AsyncWebRewrite &addRewrite(AsyncWebRewrite *rewrite);
+
+ /**
+ * @brief (compat) Add url rewrite rule by pointer
+ * a deep copy of the pointer object will be created,
+ * it is up to user to manage further lifetime of the object in argument
+ *
+ * @param rewrite pointer to rewrite object to copy setting from
+ * @return AsyncWebRewrite& reference to a newly created rewrite rule
+ */
+ AsyncWebRewrite &addRewrite(std::shared_ptr<AsyncWebRewrite> rewrite);
+
+ /**
+ * @brief add url rewrite rule
+ *
+ * @param from
+ * @param to
+ * @return AsyncWebRewrite&
+ */
+ AsyncWebRewrite &rewrite(const char *from, const char *to);
+
+ /**
+ * @brief (compat) remove rewrite rule via referenced object
+ * this will NOT deallocate pointed object itself, internal rule with same from/to urls will be removed if any
+ * it's a compat method, better use `removeRewrite(const char* from, const char* to)`
+ * @param rewrite
+ * @return true
+ * @return false
+ */
+ bool removeRewrite(AsyncWebRewrite *rewrite);
+
+ /**
+ * @brief remove rewrite rule
+ *
+ * @param from
+ * @param to
+ * @return true
+ * @return false
+ */
+ bool removeRewrite(const char *from, const char *to);
+
+ 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
+ );
+
+ 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
+
+ void _handleDisconnect(AsyncWebServerRequest *request);
+ void _attachHandler(AsyncWebServerRequest *request);
+ void _rewriteRequest(AsyncWebServerRequest *request);
+};
+
+class DefaultHeaders {
+ using headers_t = std::list<AsyncWebHeader>;
+ headers_t _headers;
+
+public:
+ DefaultHeaders() = default;
+
+ using ConstIterator = headers_t::const_iterator;
+
+ void addHeader(const String &name, const String &value) {
+ _headers.emplace_back(name, value);
+ }
+
+ ConstIterator begin() const {
+ return _headers.begin();
+ }
+ ConstIterator end() const {
+ return _headers.end();
+ }
+
+ DefaultHeaders(DefaultHeaders const &) = delete;
+ DefaultHeaders &operator=(DefaultHeaders const &) = delete;
+
+ static DefaultHeaders &Instance() {
+ static DefaultHeaders instance;
+ return instance;
+ }
+};
+
+#include "AsyncEventSource.h"
+#include "AsyncWebSocket.h"
+#include "WebHandlerImpl.h"
+#include "WebResponseImpl.h"
+
+#endif /* _AsyncWebServer_H_ */
diff --git a/libraries/ESP_Async_WebServer/src/Middleware.cpp b/libraries/ESP_Async_WebServer/src/Middleware.cpp
new file mode 100644
index 0000000..890303d
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/Middleware.cpp
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#include "WebAuthentication.h"
+#include <ESPAsyncWebServer.h>
+
+AsyncMiddlewareChain::~AsyncMiddlewareChain() {
+ for (AsyncMiddleware *m : _middlewares) {
+ if (m->_freeOnRemoval) {
+ delete m;
+ }
+ }
+}
+
+void AsyncMiddlewareChain::addMiddleware(ArMiddlewareCallback fn) {
+ AsyncMiddlewareFunction *m = new AsyncMiddlewareFunction(fn);
+ m->_freeOnRemoval = true;
+ _middlewares.emplace_back(m);
+}
+
+void AsyncMiddlewareChain::addMiddleware(AsyncMiddleware *middleware) {
+ if (middleware) {
+ _middlewares.emplace_back(middleware);
+ }
+}
+
+void AsyncMiddlewareChain::addMiddlewares(std::vector<AsyncMiddleware *> 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) {
+ if (m == middleware) {
+ if (m->_freeOnRemoval) {
+ delete m;
+ }
+ return true;
+ }
+ return false;
+ }
+ ),
+ _middlewares.end()
+ );
+ return size != _middlewares.size();
+}
+
+void AsyncMiddlewareChain::_runChain(AsyncWebServerRequest *request, ArMiddlewareNext finalizer) {
+ if (!_middlewares.size()) {
+ return finalizer();
+ }
+ ArMiddlewareNext next;
+ std::list<AsyncMiddleware *>::iterator it = _middlewares.begin();
+ next = [this, &next, &it, request, finalizer]() {
+ if (it == _middlewares.end()) {
+ return finalizer();
+ }
+ AsyncMiddleware *m = *it;
+ it++;
+ return m->run(request, next);
+ };
+ return next();
+}
+
+void AsyncAuthenticationMiddleware::setUsername(const char *username) {
+ _username = username;
+ _hasCreds = _username.length() && _credentials.length();
+}
+
+void AsyncAuthenticationMiddleware::setPassword(const char *password) {
+ _credentials = password;
+ _hash = false;
+ _hasCreds = _username.length() && _credentials.length();
+}
+
+void AsyncAuthenticationMiddleware::setPasswordHash(const char *hash) {
+ _credentials = hash;
+ _hash = _credentials.length();
+ _hasCreds = _username.length() && _credentials.length();
+}
+
+bool AsyncAuthenticationMiddleware::generateHash() {
+ // ensure we have all the necessary data
+ if (!_hasCreds) {
+ return false;
+ }
+
+ // if we already have a hash, do nothing
+ 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;
+ } else {
+ return false;
+ }
+
+ default: return false;
+ }
+}
+
+bool AsyncAuthenticationMiddleware::allowed(AsyncWebServerRequest *request) const {
+ if (_authMethod == AsyncAuthType::AUTH_NONE) {
+ return true;
+ }
+
+ if (_authMethod == AsyncAuthType::AUTH_DENIED) {
+ return false;
+ }
+
+ if (!_hasCreds) {
+ return true;
+ }
+
+ return request->authenticate(_username.c_str(), _credentials.c_str(), _realm.c_str(), _hash);
+}
+
+void AsyncAuthenticationMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
+ return allowed(request) ? next() : request->requestAuthentication(_authMethod, _realm.c_str(), _authFailMsg.c_str());
+}
+
+void AsyncHeaderFreeMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
+ std::list<const char *> toRemove;
+ for (auto &h : request->getHeaders()) {
+ bool keep = false;
+ for (const char *k : _toKeep) {
+ if (strcasecmp(h.name().c_str(), k) == 0) {
+ keep = true;
+ break;
+ }
+ }
+ if (!keep) {
+ 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) {
+ request->removeHeader(*it);
+ }
+ next();
+}
+
+void AsyncLoggingMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
+ if (!isEnabled()) {
+ next();
+ return;
+ }
+ _out->print(F("* Connection from "));
+ _out->print(request->client()->remoteIP().toString());
+ _out->print(':');
+ _out->println(request->client()->remotePort());
+ _out->print('>');
+ _out->print(' ');
+ _out->print(request->methodToString());
+ _out->print(' ');
+ _out->print(request->url().c_str());
+ _out->print(F(" HTTP/1."));
+ _out->println(request->version());
+ for (auto &h : request->getHeaders()) {
+ if (h.value().length()) {
+ _out->print('>');
+ _out->print(' ');
+ _out->print(h.name());
+ _out->print(':');
+ _out->print(' ');
+ _out->println(h.value());
+ }
+ }
+ _out->println(F(">"));
+ uint32_t elapsed = millis();
+ next();
+ elapsed = millis() - elapsed;
+ AsyncWebServerResponse *response = request->getResponse();
+ if (response) {
+ _out->print(F("* Processed in "));
+ _out->print(elapsed);
+ _out->println(F(" ms"));
+ _out->print('<');
+ _out->print(F(" HTTP/1."));
+ _out->print(request->version());
+ _out->print(' ');
+ _out->print(response->code());
+ _out->print(' ');
+ _out->println(AsyncWebServerResponse::responseCodeToString(response->code()));
+ for (auto &h : response->getHeaders()) {
+ if (h.value().length()) {
+ _out->print('<');
+ _out->print(' ');
+ _out->print(h.name());
+ _out->print(':');
+ _out->print(' ');
+ _out->println(h.value());
+ }
+ }
+ _out->println('<');
+ } else {
+ _out->println(F("* Connection closed!"));
+ }
+}
+
+void AsyncCorsMiddleware::addCORSHeaders(AsyncWebServerResponse *response) {
+ response->addHeader(asyncsrv::T_CORS_ACAO, _origin.c_str());
+ response->addHeader(asyncsrv::T_CORS_ACAM, _methods.c_str());
+ response->addHeader(asyncsrv::T_CORS_ACAH, _headers.c_str());
+ response->addHeader(asyncsrv::T_CORS_ACAC, _credentials ? asyncsrv::T_TRUE : asyncsrv::T_FALSE);
+ response->addHeader(asyncsrv::T_CORS_ACMA, String(_maxAge).c_str());
+}
+
+void AsyncCorsMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
+ // Origin header ? => CORS handling
+ if (request->hasHeader(asyncsrv::T_CORS_O)) {
+ // check if this is a preflight request => handle it and return
+ if (request->method() == HTTP_OPTIONS) {
+ AsyncWebServerResponse *response = request->beginResponse(200);
+ addCORSHeaders(response);
+ request->send(response);
+ return;
+ }
+
+ // CORS request, no options => let the request pass and add CORS headers after
+ next();
+ AsyncWebServerResponse *response = request->getResponse();
+ if (response) {
+ addCORSHeaders(response);
+ }
+
+ } else {
+ // NO Origin header => no CORS handling
+ next();
+ }
+}
+
+bool AsyncRateLimitMiddleware::isRequestAllowed(uint32_t &retryAfterSeconds) {
+ uint32_t now = millis();
+
+ while (!_requestTimes.empty() && _requestTimes.front() <= now - _windowSizeMillis) {
+ _requestTimes.pop_front();
+ }
+
+ _requestTimes.push_back(now);
+
+ if (_requestTimes.size() > _maxRequests) {
+ _requestTimes.pop_front();
+ retryAfterSeconds = (_windowSizeMillis - (now - _requestTimes.front())) / 1000 + 1;
+ return false;
+ }
+
+ retryAfterSeconds = 0;
+ return true;
+}
+
+void AsyncRateLimitMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext next) {
+ uint32_t retryAfterSeconds;
+ if (isRequestAllowed(retryAfterSeconds)) {
+ next();
+ } else {
+ AsyncWebServerResponse *response = request->beginResponse(429);
+ response->addHeader(asyncsrv::T_retry_after, retryAfterSeconds);
+ request->send(response);
+ }
+}
diff --git a/libraries/ESP_Async_WebServer/src/WebAuthentication.cpp b/libraries/ESP_Async_WebServer/src/WebAuthentication.cpp
new file mode 100644
index 0000000..7ed7814
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/WebAuthentication.cpp
@@ -0,0 +1,247 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#include "WebAuthentication.h"
+#include <libb64/cencode.h>
+#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
+#include <MD5Builder.h>
+#else
+#include "md5.h"
+#endif
+#include "literals.h"
+
+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) {
+ return false;
+ }
+ return generateBasicHash(username, password).equalsIgnoreCase(hash);
+}
+
+String generateBasicHash(const char *username, const char *password) {
+ if (username == NULL || password == NULL) {
+ return emptyString;
+ }
+
+ size_t toencodeLen = strlen(username) + strlen(password) + 1;
+
+ char *toencode = new char[toencodeLen + 1];
+ if (toencode == NULL) {
+ return emptyString;
+ }
+ char *encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
+ if (encoded == NULL) {
+ delete[] toencode;
+ return emptyString;
+ }
+ sprintf_P(toencode, PSTR("%s:%s"), username, password);
+ if (base64_encode_chars(toencode, toencodeLen, encoded) > 0) {
+ String res = String(encoded);
+ delete[] toencode;
+ delete[] encoded;
+ return res;
+ }
+ delete[] toencode;
+ delete[] encoded;
+ return emptyString;
+}
+
+static bool getMD5(uint8_t *data, uint16_t len, char *output) { // 33 bytes or more
+#if defined(ESP32) || defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
+ MD5Builder md5;
+ md5.begin();
+ md5.add(data, len);
+ md5.calculate();
+ md5.getChars(output);
+#else
+ md5_context_t _ctx;
+
+ uint8_t *_buf = (uint8_t *)malloc(16);
+ if (_buf == NULL) {
+ return false;
+ }
+ memset(_buf, 0x00, 16);
+
+ MD5Init(&_ctx);
+ MD5Update(&_ctx, data, len);
+ MD5Final(_buf, &_ctx);
+
+ for (uint8_t i = 0; i < 16; i++) {
+ sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]);
+ }
+
+ free(_buf);
+#endif
+ return true;
+}
+
+String genRandomMD5() {
+#ifdef ESP8266
+ uint32_t r = RANDOM_REG32;
+#else
+ uint32_t r = rand();
+#endif
+ char *out = (char *)malloc(33);
+ 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;
+}
+
+static String stringMD5(const String &in) {
+ char *out = (char *)malloc(33);
+ 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;
+}
+
+String generateDigestHash(const char *username, const char *password, const char *realm) {
+ if (username == NULL || password == NULL || realm == NULL) {
+ return emptyString;
+ }
+ char *out = (char *)malloc(33);
+ if (out == NULL) {
+#ifdef ESP32
+ log_e("Failed to allocate");
+#endif
+ return emptyString;
+ }
+
+ String in;
+ 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 (!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
+) {
+ if (username == NULL || password == NULL || header == NULL || method == NULL) {
+ // os_printf("AUTH FAIL: missing required fields\n");
+ return false;
+ }
+
+ String myHeader(header);
+ int nextBreak = myHeader.indexOf(',');
+ if (nextBreak < 0) {
+ // os_printf("AUTH FAIL: no variables\n");
+ return false;
+ }
+
+ String myUsername;
+ String myRealm;
+ String myNonce;
+ String myUri;
+ String myResponse;
+ String myQop;
+ String myNc;
+ String myCnonce;
+
+ myHeader += (char)0x2c; // ','
+ myHeader += (char)0x20; // ' '
+ do {
+ String avLine(myHeader.substring(0, nextBreak));
+ avLine.trim();
+ myHeader = myHeader.substring(nextBreak + 1);
+ nextBreak = myHeader.indexOf(',');
+
+ int eqSign = avLine.indexOf('=');
+ if (eqSign < 0) {
+ // os_printf("AUTH FAIL: no = sign\n");
+ return false;
+ }
+ String varName(avLine.substring(0, eqSign));
+ avLine = avLine.substring(eqSign + 1);
+ if (avLine.startsWith(String('"'))) {
+ avLine = avLine.substring(1, avLine.length() - 1);
+ }
+
+ if (varName.equals(T_username)) {
+ if (!avLine.equals(username)) {
+ // os_printf("AUTH FAIL: username\n");
+ return false;
+ }
+ myUsername = avLine;
+ } else if (varName.equals(T_realm)) {
+ if (realm != NULL && !avLine.equals(realm)) {
+ // os_printf("AUTH FAIL: realm\n");
+ return false;
+ }
+ myRealm = avLine;
+ } else if (varName.equals(T_nonce)) {
+ if (nonce != NULL && !avLine.equals(nonce)) {
+ // os_printf("AUTH FAIL: nonce\n");
+ return false;
+ }
+ myNonce = avLine;
+ } else if (varName.equals(T_opaque)) {
+ if (opaque != NULL && !avLine.equals(opaque)) {
+ // os_printf("AUTH FAIL: opaque\n");
+ return false;
+ }
+ } else if (varName.equals(T_uri)) {
+ if (uri != NULL && !avLine.equals(uri)) {
+ // os_printf("AUTH FAIL: uri\n");
+ return false;
+ }
+ myUri = avLine;
+ } else if (varName.equals(T_response)) {
+ myResponse = avLine;
+ } else if (varName.equals(T_qop)) {
+ myQop = avLine;
+ } else if (varName.equals(T_nc)) {
+ myNc = avLine;
+ } else if (varName.equals(T_cnonce)) {
+ myCnonce = avLine;
+ }
+ } while (nextBreak > 0);
+
+ String ha1 = passwordIsHash ? password : stringMD5(myUsername + ':' + myRealm + ':' + password).c_str();
+ String ha2 = stringMD5(String(method) + ':' + myUri);
+ String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + ha2;
+
+ if (myResponse.equals(stringMD5(response))) {
+ // os_printf("AUTH SUCCESS\n");
+ return true;
+ }
+
+ // os_printf("AUTH FAIL: password\n");
+ return false;
+}
diff --git a/libraries/ESP_Async_WebServer/src/WebAuthentication.h b/libraries/ESP_Async_WebServer/src/WebAuthentication.h
new file mode 100644
index 0000000..1711821
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/WebAuthentication.h
@@ -0,0 +1,23 @@
+// 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_
+
+#include "Arduino.h"
+
+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
+);
+
+// for storing hashed versions on the device that can be authenticated against
+String generateDigestHash(const char *username, const char *password, const char *realm);
+
+String generateBasicHash(const char *username, const char *password);
+
+String genRandomMD5();
+
+#endif
diff --git a/libraries/ESP_Async_WebServer/src/WebHandlerImpl.h b/libraries/ESP_Async_WebServer/src/WebHandlerImpl.h
new file mode 100644
index 0000000..1f68d62
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/WebHandlerImpl.h
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#ifndef ASYNCWEBSERVERHANDLERIMPL_H_
+#define ASYNCWEBSERVERHANDLERIMPL_H_
+
+#include <string>
+#ifdef ASYNCWEBSERVER_REGEX
+#include <regex>
+#endif
+
+#include "stddef.h"
+#include <time.h>
+
+class AsyncStaticWebHandler : public AsyncWebHandler {
+ using File = fs::File;
+ using FS = fs::FS;
+
+private:
+ bool _getFile(AsyncWebServerRequest *request) const;
+ bool _searchFile(AsyncWebServerRequest *request, const String &path);
+
+protected:
+ FS _fs;
+ String _uri;
+ String _path;
+ String _default_file;
+ String _cache_control;
+ String _last_modified;
+ AwsTemplateProcessor _callback;
+ bool _isDir;
+ bool _tryGzipFirst = true;
+
+public:
+ AsyncStaticWebHandler(const char *uri, FS &fs, const char *path, const char *cache_control);
+ bool canHandle(AsyncWebServerRequest *request) const override final;
+ void handleRequest(AsyncWebServerRequest *request) override final;
+ AsyncStaticWebHandler &setTryGzipFirst(bool value);
+ AsyncStaticWebHandler &setIsDir(bool isDir);
+ AsyncStaticWebHandler &setDefaultFile(const char *filename);
+ AsyncStaticWebHandler &setCacheControl(const char *cache_control);
+
+ /**
+ * @brief Set the Last-Modified time for the object
+ *
+ * @param last_modified
+ * @return AsyncStaticWebHandler&
+ */
+ 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 running and time is updated
+ AsyncStaticWebHandler &setLastModified();
+
+ AsyncStaticWebHandler &setTemplateProcessor(AwsTemplateProcessor newCallback);
+};
+
+class AsyncCallbackWebHandler : public AsyncWebHandler {
+private:
+protected:
+ String _uri;
+ WebRequestMethodComposite _method;
+ ArRequestHandlerFunction _onRequest;
+ ArUploadHandlerFunction _onUpload;
+ ArBodyHandlerFunction _onBody;
+ bool _isRegex;
+
+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;
+ }
+
+ 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;
+ }
+};
+
+#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */
diff --git a/libraries/ESP_Async_WebServer/src/WebHandlers.cpp b/libraries/ESP_Async_WebServer/src/WebHandlers.cpp
new file mode 100644
index 0000000..acfc7c0
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/WebHandlers.cpp
@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#include "ESPAsyncWebServer.h"
+#include "WebHandlerImpl.h"
+
+using namespace asyncsrv;
+
+AsyncWebHandler &AsyncWebHandler::setFilter(ArRequestFilterFunction fn) {
+ _filter = fn;
+ return *this;
+}
+AsyncWebHandler &AsyncWebHandler::setAuthentication(const char *username, const char *password, AsyncAuthType authMethod) {
+ if (!_authMiddleware) {
+ _authMiddleware = new AsyncAuthenticationMiddleware();
+ _authMiddleware->_freeOnRemoval = true;
+ addMiddleware(_authMiddleware);
+ }
+ _authMiddleware->setUsername(username);
+ _authMiddleware->setPassword(password);
+ _authMiddleware->setAuthType(authMethod);
+ return *this;
+};
+
+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] != '/') {
+ _uri = String('/') + _uri;
+ }
+ 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.
+ _isDir = _path[_path.length() - 1] == '/';
+
+ // Remove the trailing '/' so we can handle default file
+ // Notice that root will be "" not "/"
+ if (_uri[_uri.length() - 1] == '/') {
+ _uri = _uri.substring(0, _uri.length() - 1);
+ }
+ if (_path[_path.length() - 1] == '/') {
+ _path = _path.substring(0, _path.length() - 1);
+ }
+}
+
+AsyncStaticWebHandler &AsyncStaticWebHandler::setTryGzipFirst(bool value) {
+ _tryGzipFirst = value;
+ return *this;
+}
+
+AsyncStaticWebHandler &AsyncStaticWebHandler::setIsDir(bool isDir) {
+ _isDir = isDir;
+ return *this;
+}
+
+AsyncStaticWebHandler &AsyncStaticWebHandler::setDefaultFile(const char *filename) {
+ _default_file = filename;
+ return *this;
+}
+
+AsyncStaticWebHandler &AsyncStaticWebHandler::setCacheControl(const char *cache_control) {
+ _cache_control = cache_control;
+ return *this;
+}
+
+AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified(const char *last_modified) {
+ _last_modified = last_modified;
+ return *this;
+}
+
+AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified(struct tm *last_modified) {
+ char result[30];
+#ifdef ESP8266
+ auto formatP = PSTR("%a, %d %b %Y %H:%M:%S GMT");
+ char format[strlen_P(formatP) + 1];
+ strcpy_P(format, formatP);
+#else
+ static constexpr const char *format = "%a, %d %b %Y %H:%M:%S GMT";
+#endif
+
+ strftime(result, sizeof(result), format, last_modified);
+ _last_modified = result;
+ return *this;
+}
+
+AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified(time_t last_modified) {
+ return setLastModified((struct tm *)gmtime(&last_modified));
+}
+
+AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified() {
+ time_t last_modified;
+ if (time(&last_modified) == 0) { // time is not yet set
+ return *this;
+ }
+ return setLastModified(last_modified);
+}
+
+bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request) const {
+ return request->isHTTP() && request->method() == HTTP_GET && request->url().startsWith(_uri) && _getFile(request);
+}
+
+bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) const {
+ // Remove the found uri
+ String path = request->url().substring(_uri.length());
+
+ // We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/'
+ bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length() - 1] == '/');
+
+ path = _path + path;
+
+ // Do we have a file or .gz file
+ if (!canSkipFileCheck && const_cast<AsyncStaticWebHandler *>(this)->_searchFile(request, path)) {
+ return true;
+ }
+
+ // Can't handle if not default file
+ if (_default_file.length() == 0) {
+ return false;
+ }
+
+ // 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);
+}
+
+#ifdef ESP32
+#define FILE_IS_REAL(f) (f == true && !f.isDirectory())
+#else
+#define FILE_IS_REAL(f) (f == true)
+#endif
+
+bool AsyncStaticWebHandler::_searchFile(AsyncWebServerRequest *request, const String &path) {
+ bool fileFound = false;
+ bool gzipFound = false;
+
+ String gzip = path + T__gz;
+
+ if (_tryGzipFirst) {
+ if (_fs.exists(gzip)) {
+ request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
+ gzipFound = FILE_IS_REAL(request->_tempFile);
+ }
+ if (!gzipFound) {
+ if (_fs.exists(path)) {
+ request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
+ fileFound = FILE_IS_REAL(request->_tempFile);
+ }
+ }
+ } else {
+ if (_fs.exists(path)) {
+ request->_tempFile = _fs.open(path, fs::FileOpenMode::read);
+ fileFound = FILE_IS_REAL(request->_tempFile);
+ }
+ if (!fileFound) {
+ if (_fs.exists(gzip)) {
+ request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read);
+ gzipFound = FILE_IS_REAL(request->_tempFile);
+ }
+ }
+ }
+
+ bool found = fileFound || gzipFound;
+
+ if (found) {
+ // 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;
+ }
+
+ return found;
+}
+
+void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) {
+ // Get the filename from request->_tempObject and free it
+ String filename((char *)request->_tempObject);
+ free(request->_tempObject);
+ request->_tempObject = NULL;
+
+ if (request->_tempFile != true) {
+ request->send(404);
+ return;
+ }
+
+ time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS)
+ // set etag to lastmod timestamp if available, otherwise to size
+ String etag;
+ if (lw) {
+ setLastModified(lw);
+#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];
+ char *ret = lltoa(lw ^ request->_tempFile.size(), buf, len, 10);
+ etag = ret ? String(ret) : String(request->_tempFile.size());
+#else
+ etag = lw ^ request->_tempFile.size(); // etag combines file size and lastmod timestamp
+#endif
+ } else {
+#if defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350)
+ 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)) {
+ not_modified = request->header(T_INM).equals(etag);
+ } else if (_last_modified.length()) {
+ not_modified = request->header(T_IMS).equals(_last_modified);
+ }
+
+ AsyncWebServerResponse *response;
+
+ if (not_modified) {
+ request->_tempFile.close();
+ response = new AsyncBasicResponse(304); // Not modified
+ } else {
+ 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()) {
+ response->addHeader(T_Last_Modified, _last_modified.c_str());
+ }
+ if (_cache_control.length()) {
+ response->addHeader(T_Cache_Control, _cache_control.c_str());
+ }
+
+ request->send(response);
+}
+
+AsyncStaticWebHandler &AsyncStaticWebHandler::setTemplateProcessor(AwsTemplateProcessor newCallback) {
+ _callback = newCallback;
+ return *this;
+}
+
+void AsyncCallbackWebHandler::setUri(const String &uri) {
+ _uri = uri;
+ _isRegex = uri.startsWith("^") && uri.endsWith("$");
+}
+
+bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest *request) const {
+ if (!_onRequest || !request->isHTTP() || !(_method & request->method())) {
+ return false;
+ }
+
+#ifdef ASYNCWEBSERVER_REGEX
+ if (_isRegex) {
+ std::regex pattern(_uri.c_str());
+ std::smatch matches;
+ std::string s(request->url().c_str());
+ if (std::regex_search(s, matches, pattern)) {
+ for (size_t i = 1; i < matches.size(); ++i) { // start from 1
+ request->_addPathParam(matches[i].str().c_str());
+ }
+ } else {
+ return false;
+ }
+ } else
+#endif
+ if (_uri.length() && _uri.startsWith("/*.")) {
+ String uriTemplate = String(_uri);
+ uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf("."));
+ 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)) {
+ return false;
+ }
+ } else if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) {
+ return false;
+ }
+
+ return true;
+}
+
+void AsyncCallbackWebHandler::handleRequest(AsyncWebServerRequest *request) {
+ if (_onRequest) {
+ _onRequest(request);
+ } 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) {
+ _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) {
+ _onBody(request, data, len, index, total);
+ }
+}
diff --git a/libraries/ESP_Async_WebServer/src/WebRequest.cpp b/libraries/ESP_Async_WebServer/src/WebRequest.cpp
new file mode 100644
index 0000000..8b735af
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/WebRequest.cpp
@@ -0,0 +1,1185 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#include "ESPAsyncWebServer.h"
+#include "WebAuthentication.h"
+#include "WebResponseImpl.h"
+#include "literals.h"
+#include <cstring>
+
+#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '='))
+
+static void doNotDelete(AsyncWebServerRequest *) {}
+
+using namespace asyncsrv;
+
+enum {
+ PARSE_REQ_START = 0,
+ PARSE_REQ_HEADERS = 1,
+ PARSE_REQ_BODY = 2,
+ PARSE_REQ_END = 3,
+ 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;
+ // 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();
+
+ AsyncWebServerResponse *r = _response;
+ _response = NULL;
+ delete r;
+
+ if (_tempObject != NULL) {
+ free(_tempObject);
+ }
+
+ if (_tempFile) {
+ _tempFile.close();
+ }
+
+ if (_itemBuffer) {
+ free(_itemBuffer);
+ }
+}
+
+void AsyncWebServerRequest::_onData(void *buf, size_t len) {
+ // SSL/TLS handshake detection
+#ifndef ASYNC_TCP_SSL_ENABLED
+ if (_parseState == PARSE_REQ_START && len && ((uint8_t *)buf)[0] == 0x16) { // 0x16 indicates a Handshake message (SSL/TLS).
+#ifdef ESP32
+ log_d("SSL/TLS handshake detected: resetting connection");
+#endif
+ _parseState = PARSE_REQ_FAIL;
+ abort();
+ return;
+ }
+#endif
+
+ size_t i = 0;
+ while (true) {
+
+ if (_parseState < PARSE_REQ_BODY) {
+ // Find new line in buf
+ char *str = (char *)buf;
+ for (i = 0; i < len; i++) {
+ // Check for null characters in header
+ if (!str[i]) {
+ _parseState = PARSE_REQ_FAIL;
+ abort();
+ return;
+ }
+ if (str[i] == '\n') {
+ break;
+ }
+ }
+ if (i == len) { // No new line, just add the buffer in _temp
+ char ch = str[len - 1];
+ str[len - 1] = 0;
+ 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
+ str[i] = 0; // Terminate the string at the end of the line.
+ _temp.concat(str);
+ _temp.trim();
+ _parseLine();
+ if (++i < len) {
+ // Still have more buffer to process
+ buf = str + i;
+ len -= i;
+ continue;
+ }
+ }
+ } else if (_parseState == PARSE_REQ_BODY) {
+ // 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;
+ for (i = 0; i < len; i++) {
+ _parseMultipartPostByte(((uint8_t *)buf)[i], i == len - 1);
+ _parsedLength++;
+ }
+ } 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++]));
+ if (i < len && ((char *)buf)[i - 1] == '=') {
+ _isPlainPost = true;
+ }
+ }
+ }
+ if (!_isPlainPost) {
+ // ESP_LOGD("AsyncWebServer", "_isPlainPost: %d, _handler: %p", _isPlainPost, _handler);
+ if (_handler) {
+ _handler->handleBody(this, (uint8_t *)buf, len, _parsedLength, _contentLength);
+ }
+ _parsedLength += len;
+ } else if (needParse) {
+ size_t i;
+ for (i = 0; i < len; i++) {
+ _parsedLength++;
+ _parsePlainPostChar(((uint8_t *)buf)[i]);
+ }
+ } else {
+ _parsedLength += len;
+ }
+ }
+ if (_parsedLength == _contentLength) {
+ _parseState = PARSE_REQ_END;
+ _runMiddlewareChain();
+ _send();
+ }
+ }
+ break;
+ }
+}
+
+void AsyncWebServerRequest::_onPoll() {
+ // os_printf("p\n");
+ if (_response != NULL && _client != NULL && _client->canSend()) {
+ if (!_response->_finished()) {
+ _response->_ack(this, 0, 0);
+ } else {
+ AsyncWebServerResponse *r = _response;
+ _response = NULL;
+ delete r;
+
+ _client->close();
+ }
+ }
+}
+
+void AsyncWebServerRequest::_onAck(size_t len, uint32_t time) {
+ // os_printf("a:%u:%u\n", len, time);
+ if (_response != NULL) {
+ if (!_response->_finished()) {
+ _response->_ack(this, len, time);
+ } else if (_response->_finished()) {
+ AsyncWebServerResponse *r = _response;
+ _response = NULL;
+ delete r;
+
+ _client->close();
+ }
+ }
+}
+
+void AsyncWebServerRequest::_onError(int8_t error) {
+ (void)error;
+}
+
+void AsyncWebServerRequest::_onTimeout(uint32_t time) {
+ (void)time;
+ // os_printf("TIMEOUT: %u, state: %s\n", time, _client->stateToString());
+ _client->close();
+}
+
+void AsyncWebServerRequest::onDisconnect(ArDisconnectHandler fn) {
+ _onDisconnectfn = fn;
+}
+
+void AsyncWebServerRequest::_onDisconnect() {
+ // os_printf("d\n");
+ if (_onDisconnectfn) {
+ _onDisconnectfn();
+ }
+ _server->_handleDisconnect(this);
+}
+
+void AsyncWebServerRequest::_addPathParam(const char *p) {
+ _pathParams.emplace_back(p);
+}
+
+void AsyncWebServerRequest::_addGetParams(const String &params) {
+ size_t start = 0;
+ while (start < params.length()) {
+ int end = params.indexOf('&', start);
+ if (end < 0) {
+ end = params.length();
+ }
+ int equal = params.indexOf('=', start);
+ if (equal < 0 || equal > end) {
+ equal = end;
+ }
+ 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;
+ }
+}
+
+bool AsyncWebServerRequest::_parseReqHead() {
+ // Split the head into method, url and version
+ int index = _temp.indexOf(' ');
+ String m = _temp.substring(0, index);
+ index = _temp.indexOf(' ', index + 1);
+ String u = _temp.substring(m.length() + 1, index);
+ _temp = _temp.substring(index + 1);
+
+ if (m == T_GET) {
+ _method = HTTP_GET;
+ } else if (m == T_POST) {
+ _method = HTTP_POST;
+ } else if (m == T_DELETE) {
+ _method = HTTP_DELETE;
+ } else if (m == T_PUT) {
+ _method = HTTP_PUT;
+ } else if (m == T_PATCH) {
+ _method = HTTP_PATCH;
+ } else if (m == T_HEAD) {
+ _method = HTTP_HEAD;
+ } else if (m == T_OPTIONS) {
+ _method = HTTP_OPTIONS;
+ } else {
+ return false;
+ }
+
+ String g;
+ index = u.indexOf('?');
+ if (index > 0) {
+ g = u.substring(index + 1);
+ u = u.substring(0, index);
+ }
+ _url = urlDecode(u);
+ _addGetParams(g);
+
+ if (!_url.length()) {
+ return false;
+ }
+
+ if (!_temp.startsWith(T_HTTP_1_0)) {
+ _version = 1;
+ }
+
+ _temp = emptyString;
+ return true;
+}
+
+bool AsyncWebServerRequest::_parseReqHeader() {
+ int index = _temp.indexOf(':');
+ if (index) {
+ String name(_temp.substring(0, index));
+ String value(_temp.substring(index + 2));
+ if (name.equalsIgnoreCase(T_Host)) {
+ _host = value;
+ } else if (name.equalsIgnoreCase(T_Content_Type)) {
+ _contentType = value.substring(0, value.indexOf(';'));
+ if (value.startsWith(T_MULTIPART_)) {
+ _boundary = value.substring(value.indexOf('=') + 1);
+ _boundary.replace(String('"'), String());
+ _isMultipart = true;
+ }
+ } else if (name.equalsIgnoreCase(T_Content_Length)) {
+ _contentLength = atoi(value.c_str());
+ } else if (name.equalsIgnoreCase(T_EXPECT) && value.equalsIgnoreCase(T_100_CONTINUE)) {
+ _expectingContinue = true;
+ } else if (name.equalsIgnoreCase(T_AUTH)) {
+ int space = value.indexOf(' ');
+ if (space == -1) {
+ _authorization = value;
+ _authMethod = AsyncAuthType::AUTH_OTHER;
+ } else {
+ String method = value.substring(0, space);
+ if (method.equalsIgnoreCase(T_BASIC)) {
+ _authMethod = AsyncAuthType::AUTH_BASIC;
+ } else if (method.equalsIgnoreCase(T_DIGEST)) {
+ _authMethod = AsyncAuthType::AUTH_DIGEST;
+ } else if (method.equalsIgnoreCase(T_BEARER)) {
+ _authMethod = AsyncAuthType::AUTH_BEARER;
+ } else {
+ _authMethod = AsyncAuthType::AUTH_OTHER;
+ }
+ _authorization = value.substring(space + 1);
+ }
+ } else if (name.equalsIgnoreCase(T_UPGRADE) && value.equalsIgnoreCase(T_WS)) {
+ // WebSocket request can be uniquely identified by header: [Upgrade: websocket]
+ _reqconntype = RCT_WS;
+ } else if (name.equalsIgnoreCase(T_ACCEPT)) {
+ String lowcase(value);
+ lowcase.toLowerCase();
+#ifndef ESP8266
+ const char *substr = std::strstr(lowcase.c_str(), T_text_event_stream);
+#else
+ const char *substr = std::strstr(lowcase.c_str(), String(T_text_event_stream).c_str());
+#endif
+ if (substr != NULL) {
+ // WebEvent request can be uniquely identified by header: [Accept: text/event-stream]
+ _reqconntype = RCT_EVENT;
+ }
+ }
+ _headers.emplace_back(name, value);
+ }
+#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 != '&') {
+ _temp += (char)data;
+ }
+ if (!data || (char)data == '&' || _parsedLength == _contentLength) {
+ String name(T_BODY);
+ String value(_temp);
+ if (!(_temp.charAt(0) == '{') && !(_temp.charAt(0) == '[') && _temp.indexOf('=') > 0) {
+ name = _temp.substring(0, _temp.indexOf('='));
+ value = _temp.substring(_temp.indexOf('=') + 1);
+ }
+ name = urlDecode(name);
+ if (name.length()) {
+ _params.emplace_back(name, urlDecode(value), true);
+ }
+
+#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
+ }
+}
+
+void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last) {
+ _itemBuffer[_itemBufferIndex++] = data;
+
+ if (last || _itemBufferIndex == RESPONSE_STREAM_BUFFER_SIZE) {
+ // check if authenticated before calling the upload
+ if (_handler) {
+ _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false);
+ }
+ _itemBufferIndex = 0;
+ }
+}
+
+enum {
+ EXPECT_BOUNDARY,
+ PARSE_HEADERS,
+ WAIT_FOR_RETURN1,
+ EXPECT_FEED1,
+ EXPECT_DASH1,
+ EXPECT_DASH2,
+ BOUNDARY_OR_DATA,
+ DASH3_OR_RETURN2,
+ EXPECT_FEED2,
+ PARSING_FINISHED,
+ PARSE_ERROR
+};
+
+void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
+#define itemWriteByte(b) \
+ do { \
+ _itemSize++; \
+ if (_itemIsFile) \
+ _handleUploadByte(b, last); \
+ else \
+ _itemValue += (char)(b); \
+ } while (0)
+
+ if (!_parsedLength) {
+ _multiParseState = EXPECT_BOUNDARY;
+ _temp = emptyString;
+ _itemName = emptyString;
+ _itemFilename = emptyString;
+ _itemType = emptyString;
+ }
+
+ if (_multiParseState == WAIT_FOR_RETURN1) {
+ if (data != '\r') {
+ itemWriteByte(data);
+ } else {
+ _multiParseState = EXPECT_FEED1;
+ }
+ } else if (_multiParseState == EXPECT_BOUNDARY) {
+ if (_parsedLength < 2 && data != '-') {
+ _multiParseState = PARSE_ERROR;
+ return;
+ } else if (_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data) {
+ _multiParseState = PARSE_ERROR;
+ return;
+ } else if (_parsedLength - 2 == _boundary.length() && data != '\r') {
+ _multiParseState = PARSE_ERROR;
+ return;
+ } else if (_parsedLength - 3 == _boundary.length()) {
+ if (data != '\n') {
+ _multiParseState = PARSE_ERROR;
+ return;
+ }
+ _multiParseState = PARSE_HEADERS;
+ _itemIsFile = false;
+ }
+ } else if (_multiParseState == PARSE_HEADERS) {
+ 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)) {
+ _itemType = _temp.substring(14);
+ _itemIsFile = true;
+ } else if (_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase(T_Content_Disposition)) {
+ _temp = _temp.substring(_temp.indexOf(';') + 2);
+ while (_temp.indexOf(';') > 0) {
+ String name = _temp.substring(0, _temp.indexOf('='));
+ String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1);
+ if (name == T_name) {
+ _itemName = nameVal;
+ } else if (name == T_filename) {
+ _itemFilename = nameVal;
+ _itemIsFile = true;
+ }
+ _temp = _temp.substring(_temp.indexOf(';') + 2);
+ }
+ String name = _temp.substring(0, _temp.indexOf('='));
+ String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1);
+ if (name == T_name) {
+ _itemName = nameVal;
+ } else if (name == T_filename) {
+ _itemFilename = nameVal;
+ _itemIsFile = true;
+ }
+ }
+ _temp = emptyString;
+ } else {
+ _multiParseState = WAIT_FOR_RETURN1;
+ // value starts from here
+ _itemSize = 0;
+ _itemStartIndex = _parsedLength;
+ _itemValue = emptyString;
+ if (_itemIsFile) {
+ 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;
+ }
+ }
+ }
+ } else if (_multiParseState == EXPECT_FEED1) {
+ if (data != '\n') {
+ _multiParseState = WAIT_FOR_RETURN1;
+ itemWriteByte('\r');
+ _parseMultipartPostByte(data, last);
+ } else {
+ _multiParseState = EXPECT_DASH1;
+ }
+ } else if (_multiParseState == EXPECT_DASH1) {
+ if (data != '-') {
+ _multiParseState = WAIT_FOR_RETURN1;
+ itemWriteByte('\r');
+ itemWriteByte('\n');
+ _parseMultipartPostByte(data, last);
+ } else {
+ _multiParseState = EXPECT_DASH2;
+ }
+ } else if (_multiParseState == EXPECT_DASH2) {
+ if (data != '-') {
+ _multiParseState = WAIT_FOR_RETURN1;
+ itemWriteByte('\r');
+ itemWriteByte('\n');
+ itemWriteByte('-');
+ _parseMultipartPostByte(data, last);
+ } else {
+ _multiParseState = BOUNDARY_OR_DATA;
+ _boundaryPosition = 0;
+ }
+ } else if (_multiParseState == BOUNDARY_OR_DATA) {
+ if (_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data) {
+ _multiParseState = WAIT_FOR_RETURN1;
+ itemWriteByte('\r');
+ itemWriteByte('\n');
+ itemWriteByte('-');
+ itemWriteByte('-');
+ uint8_t 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;
+ if (!_itemIsFile) {
+ _params.emplace_back(_itemName, _itemValue, true);
+ } else {
+ if (_itemSize) {
+ if (_handler) {
+ _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true);
+ }
+ _itemBufferIndex = 0;
+ _params.emplace_back(_itemName, _itemFilename, true, true, _itemSize);
+ }
+ free(_itemBuffer);
+ _itemBuffer = NULL;
+ }
+
+ } else {
+ _boundaryPosition++;
+ }
+ } else if (_multiParseState == DASH3_OR_RETURN2) {
+ if (data == '-' && (_contentLength - _parsedLength - 4) != 0) {
+ // os_printf("ERROR: The parser got to the end of the POST but is expecting %u bytes more!\nDrop an issue so we can have more info on the matter!\n", _contentLength - _parsedLength - 4);
+ _contentLength = _parsedLength + 4; // lets close the request gracefully
+ }
+ if (data == '\r') {
+ _multiParseState = EXPECT_FEED2;
+ } else if (data == '-' && _contentLength == (_parsedLength + 4)) {
+ _multiParseState = PARSING_FINISHED;
+ } else {
+ _multiParseState = WAIT_FOR_RETURN1;
+ itemWriteByte('\r');
+ itemWriteByte('\n');
+ itemWriteByte('-');
+ itemWriteByte('-');
+ uint8_t i;
+ for (i = 0; i < _boundary.length(); i++) {
+ itemWriteByte(_boundary.c_str()[i]);
+ }
+ _parseMultipartPostByte(data, last);
+ }
+ } else if (_multiParseState == EXPECT_FEED2) {
+ if (data == '\n') {
+ _multiParseState = PARSE_HEADERS;
+ _itemIsFile = false;
+ } else {
+ _multiParseState = WAIT_FOR_RETURN1;
+ itemWriteByte('\r');
+ itemWriteByte('\n');
+ itemWriteByte('-');
+ itemWriteByte('-');
+ uint8_t i;
+ for (i = 0; i < _boundary.length(); i++) {
+ itemWriteByte(_boundary.c_str()[i]);
+ }
+ itemWriteByte('\r');
+ _parseMultipartPostByte(data, last);
+ }
+ }
+}
+
+void AsyncWebServerRequest::_parseLine() {
+ if (_parseState == PARSE_REQ_START) {
+ if (!_temp.length()) {
+ _parseState = PARSE_REQ_FAIL;
+ abort();
+ } else {
+ if (_parseReqHead()) {
+ _parseState = PARSE_REQ_HEADERS;
+ } else {
+ _parseState = PARSE_REQ_FAIL;
+ abort();
+ }
+ }
+ return;
+ }
+
+ if (_parseState == PARSE_REQ_HEADERS) {
+ if (!_temp.length()) {
+ // end of headers
+ _server->_rewriteRequest(this);
+ _server->_attachHandler(this);
+ if (_expectingContinue) {
+ String response(T_HTTP_100_CONT);
+ _client->write(response.c_str(), response.length());
+ }
+ if (_contentLength) {
+ _parseState = PARSE_REQ_BODY;
+ } else {
+ _parseState = PARSE_REQ_END;
+ _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");
+ }
+
+ // 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;
+ }
+}
+
+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();
+ }
+}
+
+size_t AsyncWebServerRequest::headers() const {
+ return _headers.size();
+}
+
+bool AsyncWebServerRequest::hasHeader(const char *name) const {
+ for (const auto &h : _headers) {
+ if (h.name().equalsIgnoreCase(name)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+#ifdef ESP8266
+bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper *data) const {
+ return hasHeader(String(data));
+}
+#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);
+ });
+ return (iter == std::end(_headers)) ? nullptr : &(*iter);
+}
+
+#ifdef ESP8266
+const AsyncWebHeader *AsyncWebServerRequest::getHeader(const __FlashStringHelper *data) const {
+ PGM_P p = reinterpret_cast<PGM_P>(data);
+ size_t n = strlen_P(p);
+ char *name = (char *)malloc(n + 1);
+ if (name) {
+ strcpy_P(name, p);
+ const AsyncWebHeader *result = getHeader(String(name));
+ free(name);
+ return result;
+ } else {
+ return nullptr;
+ }
+}
+#endif
+
+const AsyncWebHeader *AsyncWebServerRequest::getHeader(size_t num) const {
+ 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 = names.size();
+ for (const auto &h : _headers) {
+ names.push_back(h.name().c_str());
+ }
+ 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);
+ });
+ return size != _headers.size();
+}
+
+size_t AsyncWebServerRequest::params() const {
+ return _params.size();
+}
+
+bool AsyncWebServerRequest::hasParam(const char *name, bool post, bool file) const {
+ for (const auto &p : _params) {
+ if (p.name().equals(name) && p.isPost() == post && p.isFile() == file) {
+ return true;
+ }
+ }
+ return false;
+}
+
+const AsyncWebParameter *AsyncWebServerRequest::getParam(const char *name, bool post, bool file) const {
+ for (const auto &p : _params) {
+ if (p.name() == name && p.isPost() == post && p.isFile() == file) {
+ return &p;
+ }
+ }
+ return nullptr;
+}
+
+#ifdef ESP8266
+const AsyncWebParameter *AsyncWebServerRequest::getParam(const __FlashStringHelper *data, bool post, bool file) const {
+ return getParam(String(data), post, file);
+}
+#endif
+
+const AsyncWebParameter *AsyncWebServerRequest::getParam(size_t num) const {
+ if (num >= _params.size()) {
+ return nullptr;
+ }
+ return &(*std::next(_params.cbegin(), num));
+}
+
+const String &AsyncWebServerRequest::getAttribute(const char *name, const String &defaultValue) const {
+ auto it = _attributes.find(name);
+ return it != _attributes.end() ? it->second : defaultValue;
+}
+bool AsyncWebServerRequest::getAttribute(const char *name, bool defaultValue) const {
+ auto it = _attributes.find(name);
+ return it != _attributes.end() ? it->second == "1" : defaultValue;
+}
+long AsyncWebServerRequest::getAttribute(const char *name, long defaultValue) const {
+ auto it = _attributes.find(name);
+ return it != _attributes.end() ? it->second.toInt() : defaultValue;
+}
+float AsyncWebServerRequest::getAttribute(const char *name, float defaultValue) const {
+ auto it = _attributes.find(name);
+ return it != _attributes.end() ? it->second.toFloat() : defaultValue;
+}
+double AsyncWebServerRequest::getAttribute(const char *name, double defaultValue) const {
+ auto it = _attributes.find(name);
+ return it != _attributes.end() ? it->second.toDouble() : defaultValue;
+}
+
+AsyncWebServerResponse *AsyncWebServerRequest::beginResponse(int code, const char *contentType, const char *content, AwsTemplateProcessor 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) {
+ 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))) {
+ 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) {
+ return new AsyncFileResponse(content, path, contentType, download, callback);
+ }
+ return NULL;
+}
+
+AsyncWebServerResponse *AsyncWebServerRequest::beginResponse(Stream &stream, const char *contentType, size_t len, AwsTemplateProcessor callback) {
+ return new AsyncStreamResponse(stream, contentType, len, callback);
+}
+
+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) {
+ return new AsyncChunkedResponse(contentType, callback, templateCallback);
+ }
+ return new AsyncCallbackResponse(contentType, 0, callback, templateCallback);
+}
+
+AsyncResponseStream *AsyncWebServerRequest::beginResponseStream(const char *contentType, size_t bufferSize) {
+ return new AsyncResponseStream(contentType, bufferSize);
+}
+
+AsyncWebServerResponse *AsyncWebServerRequest::beginResponse_P(int code, const String &contentType, PGM_P content, AwsTemplateProcessor callback) {
+ return new AsyncProgmemResponse(code, contentType, (const uint8_t *)content, strlen_P(content), callback);
+}
+
+void AsyncWebServerRequest::send(AsyncWebServerResponse *response) {
+ // request is already sent on the wire ?
+ if (_sent) {
+ return;
+ }
+
+ // 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) {
+ AsyncWebServerResponse *response = beginResponse(code);
+ response->addHeader(T_LOCATION, url);
+ send(response);
+}
+
+bool AsyncWebServerRequest::authenticate(const char *username, const char *password, const char *realm, bool passwordIsHash) const {
+ if (_authorization.length()) {
+ if (_authMethod == AsyncAuthType::AUTH_DIGEST) {
+ return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL);
+ } else if (!passwordIsHash) {
+ return checkBasicAuthentication(_authorization.c_str(), username, password);
+ } else {
+ return _authorization.equals(password);
+ }
+ }
+ return false;
+}
+
+bool AsyncWebServerRequest::authenticate(const char *hash) const {
+ if (!_authorization.length() || hash == NULL) {
+ return false;
+ }
+
+ if (_authMethod == AsyncAuthType::AUTH_DIGEST) {
+ String hStr = String(hash);
+ int separator = hStr.indexOf(':');
+ if (separator <= 0) {
+ return false;
+ }
+ String username = hStr.substring(0, separator);
+ hStr = hStr.substring(separator + 1);
+ separator = hStr.indexOf(':');
+ 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);
+ }
+
+ // Basic Auth, Bearer Auth, or other
+ return (_authorization.equals(hash));
+}
+
+void AsyncWebServerRequest::requestAuthentication(AsyncAuthType method, const char *realm, const char *_authFailMsg) {
+ if (!realm) {
+ realm = T_LOGIN_REQ;
+ }
+
+ AsyncWebServerResponse *r = _authFailMsg ? beginResponse(401, T_text_html, _authFailMsg) : beginResponse(401);
+
+ switch (method) {
+ case AsyncAuthType::AUTH_BASIC:
+ {
+ String header;
+ 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:
+ {
+ size_t len = strlen(T_DIGEST_) + strlen(T_realm__) + strlen(T_auth_nonce) + 32 + strlen(T__opaque) + 32 + 1;
+ String header;
+ 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(nonce);
+ header.concat(T__opaque);
+ 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;
+ }
+
+ send(r);
+}
+
+bool AsyncWebServerRequest::hasArg(const char *name) const {
+ for (const auto &arg : _params) {
+ if (arg.name() == name) {
+ return true;
+ }
+ }
+ return false;
+}
+
+#ifdef ESP8266
+bool AsyncWebServerRequest::hasArg(const __FlashStringHelper *data) const {
+ return hasArg(String(data).c_str());
+}
+#endif
+
+const String &AsyncWebServerRequest::arg(const char *name) const {
+ for (const auto &arg : _params) {
+ if (arg.name() == name) {
+ return arg.value();
+ }
+ }
+ return emptyString;
+}
+
+#ifdef ESP8266
+const String &AsyncWebServerRequest::arg(const __FlashStringHelper *data) const {
+ return arg(String(data).c_str());
+}
+#endif
+
+const String &AsyncWebServerRequest::arg(size_t i) const {
+ return getParam(i)->value();
+}
+
+const String &AsyncWebServerRequest::argName(size_t i) const {
+ return getParam(i)->name();
+}
+
+const String &AsyncWebServerRequest::pathArg(size_t i) const {
+ if (i >= _pathParams.size()) {
+ return emptyString;
+ }
+ auto it = _pathParams.begin();
+ std::advance(it, i);
+ return *it;
+}
+
+const String &AsyncWebServerRequest::header(const char *name) const {
+ const AsyncWebHeader *h = getHeader(name);
+ return h ? h->value() : emptyString;
+}
+
+#ifdef ESP8266
+const String &AsyncWebServerRequest::header(const __FlashStringHelper *data) const {
+ return header(String(data).c_str());
+};
+#endif
+
+const String &AsyncWebServerRequest::header(size_t i) const {
+ const AsyncWebHeader *h = getHeader(i);
+ return h ? h->value() : emptyString;
+}
+
+const String &AsyncWebServerRequest::headerName(size_t i) const {
+ const AsyncWebHeader *h = getHeader(i);
+ return h ? h->name() : emptyString;
+}
+
+String AsyncWebServerRequest::urlDecode(const String &text) const {
+ char temp[] = "0x00";
+ unsigned int len = text.length();
+ unsigned int i = 0;
+ String decoded;
+ // 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++);
+ if ((encodedChar == '%') && (i + 1 < len)) {
+ temp[2] = text.charAt(i++);
+ temp[3] = text.charAt(i++);
+ decodedChar = strtol(temp, NULL, 16);
+ } else if (encodedChar == '+') {
+ decodedChar = ' ';
+ } else {
+ decodedChar = encodedChar; // normal ascii char
+ }
+ decoded.concat(decodedChar);
+ }
+ return decoded;
+}
+
+const char *AsyncWebServerRequest::methodToString() const {
+ if (_method == HTTP_ANY) {
+ return T_ANY;
+ }
+ if (_method & HTTP_GET) {
+ return T_GET;
+ }
+ if (_method & HTTP_POST) {
+ return T_POST;
+ }
+ if (_method & HTTP_DELETE) {
+ return T_DELETE;
+ }
+ if (_method & HTTP_PUT) {
+ return T_PUT;
+ }
+ if (_method & HTTP_PATCH) {
+ return T_PATCH;
+ }
+ if (_method & HTTP_HEAD) {
+ return T_HEAD;
+ }
+ 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;
+ }
+}
+
+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));
+}
diff --git a/libraries/ESP_Async_WebServer/src/WebResponseImpl.h b/libraries/ESP_Async_WebServer/src/WebResponseImpl.h
new file mode 100644
index 0000000..6408625
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/WebResponseImpl.h
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_
+#define ASYNCWEBSERVERRESPONSEIMPL_H_
+
+#ifdef Arduino_h
+// arduino is not compatible with std::vector
+#undef min
+#undef max
+#endif
+#include "literals.h"
+#include <cbuf.h>
+#include <memory>
+#include <vector>
+
+// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max.
+
+class AsyncBasicResponse : public AsyncWebServerResponse {
+private:
+ String _content;
+
+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()) {}
+ 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;
+ }
+};
+
+class AsyncAbstractResponse : public AsyncWebServerResponse {
+private:
+#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
+ // 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};
+#endif
+ String _head;
+ // Data is inserted into cache at begin().
+ // This is inefficient with vector, but if we use some other container,
+ // we won't be able to access it as contiguous array of bytes when reading from it,
+ // so by gaining performance in one place, we'll lose it in another.
+ std::vector<uint8_t> _cache;
+ size_t _readDataFromCacheOrContent(uint8_t *data, const size_t len);
+ size_t _fillBufferAndProcessTemplates(uint8_t *buf, size_t maxLen);
+
+protected:
+ AwsTemplateProcessor _callback;
+
+public:
+ AsyncAbstractResponse(AwsTemplateProcessor callback = nullptr);
+ 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;
+ }
+};
+
+#ifndef TEMPLATE_PLACEHOLDER
+#define TEMPLATE_PLACEHOLDER '%'
+#endif
+
+#define TEMPLATE_PARAM_NAME_LENGTH 32
+class AsyncFileResponse : public AsyncAbstractResponse {
+ using File = fs::File;
+ using FS = fs::FS;
+
+private:
+ File _content;
+ String _path;
+ void _setContentTypeFromPath(const String &path);
+
+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 callback = nullptr)
+ : AsyncFileResponse(content, path, contentType.c_str(), download, callback) {}
+ ~AsyncFileResponse() {
+ _content.close();
+ }
+ bool _sourceValid() const override final {
+ return !!(_content);
+ }
+ size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
+};
+
+class AsyncStreamResponse : public AsyncAbstractResponse {
+private:
+ Stream *_content;
+
+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);
+ }
+ size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
+};
+
+class AsyncCallbackResponse : public AsyncAbstractResponse {
+private:
+ AwsResponseFiller _content;
+ size_t _filledLength;
+
+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);
+ }
+ size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
+};
+
+class AsyncChunkedResponse : public AsyncAbstractResponse {
+private:
+ AwsResponseFiller _content;
+ size_t _filledLength;
+
+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);
+ }
+ size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
+};
+
+class AsyncProgmemResponse : public AsyncAbstractResponse {
+private:
+ const uint8_t *_content;
+ size_t _readLength;
+
+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;
+ }
+ size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
+};
+
+class AsyncResponseStream : public AsyncAbstractResponse, public Print {
+private:
+ std::unique_ptr<cbuf> _content;
+
+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);
+ }
+ size_t _fillBuffer(uint8_t *buf, size_t maxLen) override final;
+ size_t write(const uint8_t *data, size_t len);
+ size_t write(uint8_t data);
+ /**
+ * @brief Returns the number of bytes available in the stream.
+ */
+ size_t available() const {
+ return _content->available();
+ }
+ using Print::write;
+};
+
+#endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */
diff --git a/libraries/ESP_Async_WebServer/src/WebResponses.cpp b/libraries/ESP_Async_WebServer/src/WebResponses.cpp
new file mode 100644
index 0000000..3de8f32
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/WebResponses.cpp
@@ -0,0 +1,859 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#include "ESPAsyncWebServer.h"
+#include "WebResponseImpl.h"
+
+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)) {
+ return --p;
+ }
+ }
+ return nullptr;
+}
+
+/*
+ * Abstract Response
+ *
+ */
+
+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;
+ }
+}
+
+AsyncWebServerResponse::AsyncWebServerResponse()
+ : _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) {
+ _code = code;
+ }
+}
+
+void AsyncWebServerResponse::setContentLength(size_t len) {
+ 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)) {
+ _contentType = type;
+ }
+}
+
+bool AsyncWebServerResponse::removeHeader(const char *name) {
+ 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;
+ }
+ }
+ return false;
+}
+
+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);
+ });
+ 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)) {
+ // header already set
+ if (replaceExisting) {
+ // remove, break and add the new one
+ _headers.erase(i);
+ break;
+ } else if (headerMustBePresentOnce(i->name())) { // we can have only one header with that name
+ // do not update
+ return false;
+ } else {
+ break; // accept multiple headers with the same name
+ }
+ }
+ }
+ // header was not found found, or existing one was removed
+ _headers.emplace_back(name, value);
+ return true;
+}
+
+void AsyncWebServerResponse::_assembleHead(String &buffer, uint8_t version) {
+ if (version) {
+ addHeader(T_Accept_Ranges, T_none, false);
+ if (_chunked) {
+ addHeader(T_Transfer_Encoding, T_chunked, false);
+ }
+ }
+
+ if (_sendContentLength) {
+ addHeader(T_Content_Length, String(_contentLength), false);
+ }
+
+ 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) {
+ len += header.name().length() + header.value().length() + 4;
+ }
+
+ // prepare buffer
+ buffer.reserve(len);
+
+ // HTTP header
+#ifdef ESP8266
+ buffer.concat(PSTR("HTTP/1."));
+#else
+ buffer.concat("HTTP/1.");
+#endif
+ buffer.concat(version);
+ buffer.concat(' ');
+ buffer.concat(_code);
+ buffer.concat(' ');
+ buffer.concat(responseCodeToString(_code));
+ buffer.concat(T_rn);
+
+ // Add headers
+ for (const auto &header : _headers) {
+ buffer.concat(header.name());
+#ifdef ESP8266
+ buffer.concat(PSTR(": "));
+#else
+ buffer.concat(": ");
+#endif
+ buffer.concat(header.value());
+ buffer.concat(T_rn);
+ }
+
+ buffer.concat(T_rn);
+ _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;
+}
+void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request) {
+ _state = RESPONSE_END;
+ request->client()->close();
+}
+size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
+ (void)request;
+ (void)len;
+ (void)time;
+ return 0;
+}
+
+/*
+ * String/Code Response
+ * */
+AsyncBasicResponse::AsyncBasicResponse(int code, const char *contentType, const char *content) {
+ _code = code;
+ _content = content;
+ _contentType = contentType;
+ if (_content.length()) {
+ _contentLength = _content.length();
+ if (!_contentType.length()) {
+ _contentType = T_text_plain;
+ }
+ }
+ addHeader(T_Connection, T_close, false);
+}
+
+void AsyncBasicResponse::_respond(AsyncWebServerRequest *request) {
+ _state = RESPONSE_HEADERS;
+ String out;
+ _assembleHead(out, request->version());
+ size_t outLen = out.length();
+ size_t space = request->client()->space();
+ if (!_contentLength && space >= outLen) {
+ _writtenLength += request->client()->write(out.c_str(), outLen);
+ _state = RESPONSE_WAIT_ACK;
+ } else if (_contentLength && space >= outLen + _contentLength) {
+ out += _content;
+ outLen += _contentLength;
+ _writtenLength += request->client()->write(out.c_str(), outLen);
+ _state = RESPONSE_WAIT_ACK;
+ } else if (space && space < outLen) {
+ String partial = out.substring(0, space);
+ _content = out.substring(space) + _content;
+ _contentLength += outLen - space;
+ _writtenLength += request->client()->write(partial.c_str(), partial.length());
+ _state = RESPONSE_CONTENT;
+ } else if (space > outLen && space < (outLen + _contentLength)) {
+ size_t shift = space - outLen;
+ outLen += shift;
+ _sentLength += shift;
+ out += _content.substring(0, shift);
+ _content = _content.substring(shift);
+ _writtenLength += request->client()->write(out.c_str(), outLen);
+ _state = RESPONSE_CONTENT;
+ } else {
+ _content = out + _content;
+ _contentLength += outLen;
+ _state = RESPONSE_CONTENT;
+ }
+}
+
+size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
+ (void)time;
+ _ackedLength += len;
+ if (_state == RESPONSE_CONTENT) {
+ size_t available = _contentLength - _sentLength;
+ size_t space = request->client()->space();
+ // we can fit in this packet
+ if (space > available) {
+ _writtenLength += request->client()->write(_content.c_str(), available);
+ _content = emptyString;
+ _state = RESPONSE_WAIT_ACK;
+ return available;
+ }
+ // send some data, the rest on ack
+ String out = _content.substring(0, space);
+ _content = _content.substring(space);
+ _sentLength += space;
+ _writtenLength += request->client()->write(out.c_str(), space);
+ return space;
+ } else if (_state == RESPONSE_WAIT_ACK) {
+ if (_ackedLength >= _writtenLength) {
+ _state = RESPONSE_END;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Abstract Response
+ * */
+
+AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) : _callback(callback) {
+ // In case of template processing, we're unable to determine real response size
+ if (callback) {
+ _contentLength = 0;
+ _sendContentLength = false;
+ _chunked = true;
+ }
+}
+
+void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request) {
+ addHeader(T_Connection, T_close, false);
+ _assembleHead(_head, request->version());
+ _state = RESPONSE_HEADERS;
+ _ack(request, 0, 0);
+}
+
+size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) {
+ (void)time;
+ if (!_sourceValid()) {
+ _state = RESPONSE_FAILED;
+ request->client()->close();
+ return 0;
+ }
+
+#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
+ // return a credit for each chunk of acked data (polls does not give any credits)
+ if (len) {
+ ++_in_flight_credit;
+ }
+
+ // for chunked responses ignore acks if there are no _in_flight_credits left
+ if (_chunked && !_in_flight_credit) {
+#ifdef ESP32
+ log_d("(chunk) out of in-flight credits");
+#endif
+ return 0;
+ }
+
+ _in_flight -= (_in_flight > len) ? len : _in_flight;
+ // get the size of available sock space
+#endif
+
+ _ackedLength += len;
+ size_t space = request->client()->space();
+
+ size_t headLen = _head.length();
+ if (_state == RESPONSE_HEADERS) {
+ if (space >= headLen) {
+ _state = RESPONSE_CONTENT;
+ space -= headLen;
+ } else {
+ String out = _head.substring(0, space);
+ _head = _head.substring(space);
+ _writtenLength += request->client()->write(out.c_str(), out.length());
+#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
+ _in_flight += out.length();
+ --_in_flight_credit; // take a credit
+#endif
+ return out.length();
+ }
+ }
+
+ if (_state == RESPONSE_CONTENT) {
+#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
+ // for response data we need to control the queue and in-flight fragmentation. Sending small chunks could give low latency,
+ // but flood asynctcp's queue and fragment socket buffer space for large responses.
+ // Let's ignore polled acks and acks in case when we have more in-flight data then the available socket buff space.
+ // That way we could balance on having half the buffer in-flight while another half is filling up, while minimizing events in asynctcp q
+ 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) {
+ --_in_flight_credit;
+ }
+ return 0;
+ }
+#endif
+
+ size_t outLen;
+ if (_chunked) {
+ if (space <= 8) {
+ return 0;
+ }
+
+ outLen = space;
+ } else if (!_sendContentLength) {
+ outLen = space;
+ } else {
+ outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength);
+ }
+
+ uint8_t *buf = (uint8_t *)malloc(outLen + headLen);
+ if (!buf) {
+#ifdef ESP32
+ log_e("Failed to allocate");
+#endif
+ request->abort();
+ return 0;
+ }
+
+ if (headLen) {
+ memcpy(buf, _head.c_str(), _head.length());
+ }
+
+ size_t readLen = 0;
+
+ if (_chunked) {
+ // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added.
+ // See RFC2616 sections 2, 3.6.1.
+ readLen = _fillBufferAndProcessTemplates(buf + headLen + 6, outLen - 8);
+ if (readLen == RESPONSE_TRY_AGAIN) {
+ free(buf);
+ return 0;
+ }
+ outLen = sprintf((char *)buf + headLen, "%04x", readLen) + headLen;
+ buf[outLen++] = '\r';
+ buf[outLen++] = '\n';
+ outLen += readLen;
+ buf[outLen++] = '\r';
+ buf[outLen++] = '\n';
+ } else {
+ readLen = _fillBufferAndProcessTemplates(buf + headLen, outLen);
+ if (readLen == RESPONSE_TRY_AGAIN) {
+ free(buf);
+ return 0;
+ }
+ outLen = readLen + headLen;
+ }
+
+ if (headLen) {
+ _head = emptyString;
+ }
+
+ if (outLen) {
+ _writtenLength += request->client()->write((const char *)buf, outLen);
+#if ASYNCWEBSERVER_USE_CHUNK_INFLIGHT
+ _in_flight += outLen;
+ --_in_flight_credit; // take a credit
+#endif
+ }
+
+ if (_chunked) {
+ _sentLength += readLen;
+ } else {
+ _sentLength += outLen - headLen;
+ }
+
+ free(buf);
+
+ if ((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)) {
+ _state = RESPONSE_WAIT_ACK;
+ }
+ return outLen;
+
+ } else if (_state == RESPONSE_WAIT_ACK) {
+ if (!_sendContentLength || _ackedLength >= _writtenLength) {
+ _state = RESPONSE_END;
+ if (!_chunked && !_sendContentLength) {
+ request->client()->close(true);
+ }
+ }
+ }
+ return 0;
+}
+
+size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t *data, const size_t len) {
+ // If we have something in cache, copy it to buffer
+ const size_t readFromCache = std::min(len, _cache.size());
+ if (readFromCache) {
+ memcpy(data, _cache.data(), readFromCache);
+ _cache.erase(_cache.begin(), _cache.begin() + readFromCache);
+ }
+ // If we need to read more...
+ const size_t needFromFile = len - readFromCache;
+ const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile);
+ return readFromCache + readFromContent;
+}
+
+size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t *data, size_t len) {
+ 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;
+ // temporary buffer to hold parameter name
+ uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1];
+ String paramName;
+ // If closing placeholder is found:
+ if (pTemplateEnd) {
+ // prepare argument to callback
+ const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1));
+ if (paramNameLength) {
+ memcpy(buf, pTemplateStart + 1, paramNameLength);
+ buf[paramNameLength] = 0;
+ paramName = String(reinterpret_cast<char *>(buf));
+ } else { // double percent sign encountered, this is single percent sign escaped.
+ // remove the 2nd percent sign
+ memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1);
+ 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
+ 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));
+ if (readFromCacheOrContent) {
+ pTemplateEnd = (uint8_t *)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent);
+ if (pTemplateEnd) {
+ // prepare argument to callback
+ *pTemplateEnd = 0;
+ paramName = String(reinterpret_cast<char *>(buf));
+ // Copy remaining read-ahead data into cache
+ _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent);
+ pTemplateEnd = &data[len - 1];
+ } else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position
+ {
+ // but first, store read file data in cache
+ _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
+ ++pTemplateStart;
+ }
+ } 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.
+ // Data after pTemplateEnd may need to be moved.
+ // The first byte of data after placeholder is located at pTemplateEnd + 1.
+ // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value).
+ const String paramValue(_callback(paramName));
+ const char *pvstr = paramValue.c_str();
+ const unsigned int pvlen = paramValue.length();
+ const size_t numBytesCopied = std::min(pvlen, static_cast<unsigned int>(&data[originalLen - 1] - pTemplateStart + 1));
+ // make room for param value
+ // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store
+ if ((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) {
+ _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]);
+ // 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) {
+ // 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)
+ if (numBytesCopied < pvlen) {
+ _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen);
+ } else if (pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text...
+ // there is some free room, fill it from cache
+ const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied;
+ const size_t totalFreeRoom = originalLen - len + roomFreed;
+ len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed;
+ } else { // result is copied fully; it is longer than placeholder text
+ const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1;
+ len = std::min(len + roomTaken, originalLen);
+ }
+ }
+ } // while(pTemplateStart)
+ return len;
+}
+
+/*
+ * File Response
+ * */
+
+void AsyncFileResponse::_setContentTypeFromPath(const String &path) {
+#if HAVE_EXTERN_GET_Content_Type_FUNCTION
+#ifndef ESP8266
+ extern const char *getContentType(const String &path);
+#else
+ extern const __FlashStringHelper *getContentType(const String &path);
+#endif
+ _contentType = getContentType(path);
+#else
+ if (path.endsWith(T__html)) {
+ _contentType = T_text_html;
+ } else if (path.endsWith(T__htm)) {
+ _contentType = T_text_html;
+ } else if (path.endsWith(T__css)) {
+ _contentType = T_text_css;
+ } else if (path.endsWith(T__json)) {
+ _contentType = T_application_json;
+ } else if (path.endsWith(T__js)) {
+ _contentType = T_application_javascript;
+ } else if (path.endsWith(T__png)) {
+ _contentType = T_image_png;
+ } else if (path.endsWith(T__gif)) {
+ _contentType = T_image_gif;
+ } else if (path.endsWith(T__jpg)) {
+ _contentType = T_image_jpeg;
+ } else if (path.endsWith(T__ico)) {
+ _contentType = T_image_x_icon;
+ } else if (path.endsWith(T__svg)) {
+ _contentType = T_image_svg_xml;
+ } else if (path.endsWith(T__eot)) {
+ _contentType = T_font_eot;
+ } else if (path.endsWith(T__woff)) {
+ _contentType = T_font_woff;
+ } else if (path.endsWith(T__woff2)) {
+ _contentType = T_font_woff2;
+ } else if (path.endsWith(T__ttf)) {
+ _contentType = T_font_ttf;
+ } else if (path.endsWith(T__xml)) {
+ _contentType = T_text_xml;
+ } else if (path.endsWith(T__pdf)) {
+ _contentType = T_application_pdf;
+ } else if (path.endsWith(T__zip)) {
+ _contentType = T_application_zip;
+ } else if (path.endsWith(T__gz)) {
+ _contentType = T_application_x_gzip;
+ } else {
+ _contentType = T_text_plain;
+ }
+#endif
+}
+
+AsyncFileResponse::AsyncFileResponse(FS &fs, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
+ : AsyncAbstractResponse(callback) {
+ _code = 200;
+ _path = path;
+
+ if (!download && !fs.exists(_path) && fs.exists(_path + T__gz)) {
+ _path = _path + T__gz;
+ addHeader(T_Content_Encoding, T_gzip, false);
+ _callback = nullptr; // Unable to process zipped templates
+ _sendContentLength = true;
+ _chunked = false;
+ }
+
+ _content = fs.open(_path, fs::FileOpenMode::read);
+ _contentLength = _content.size();
+
+ if (strlen(contentType) == 0) {
+ _setContentTypeFromPath(path);
+ } else {
+ _contentType = contentType;
+ }
+
+ int filenameStart = path.lastIndexOf('/') + 1;
+ char buf[26 + path.length() - filenameStart];
+ char *filename = (char *)path.c_str() + filenameStart;
+
+ if (download) {
+ // set filename and force download
+ snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
+ } else {
+ // set filename and force rendering
+ snprintf_P(buf, sizeof(buf), PSTR("inline"));
+ }
+ addHeader(T_Content_Disposition, buf, false);
+}
+
+AsyncFileResponse::AsyncFileResponse(File content, const String &path, const char *contentType, bool download, AwsTemplateProcessor callback)
+ : AsyncAbstractResponse(callback) {
+ _code = 200;
+ _path = path;
+
+ if (!download && String(content.name()).endsWith(T__gz) && !path.endsWith(T__gz)) {
+ addHeader(T_Content_Encoding, T_gzip, false);
+ _callback = nullptr; // Unable to process gzipped templates
+ _sendContentLength = true;
+ _chunked = false;
+ }
+
+ _content = content;
+ _contentLength = _content.size();
+
+ if (strlen(contentType) == 0) {
+ _setContentTypeFromPath(path);
+ } else {
+ _contentType = contentType;
+ }
+
+ int filenameStart = path.lastIndexOf('/') + 1;
+ char buf[26 + path.length() - filenameStart];
+ char *filename = (char *)path.c_str() + filenameStart;
+
+ if (download) {
+ snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename);
+ } else {
+ snprintf_P(buf, sizeof(buf), PSTR("inline"));
+ }
+ addHeader(T_Content_Disposition, buf, false);
+}
+
+size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len) {
+ return _content.read(data, len);
+}
+
+/*
+ * Stream Response
+ * */
+
+AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const char *contentType, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) {
+ _code = 200;
+ _content = &stream;
+ _contentLength = len;
+ _contentType = contentType;
+}
+
+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++) {
+ data[i] = _content->read();
+ }
+ return outLen;
+}
+
+/*
+ * Callback Response
+ * */
+
+AsyncCallbackResponse::AsyncCallbackResponse(const char *contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback)
+ : AsyncAbstractResponse(templateCallback) {
+ _code = 200;
+ _content = callback;
+ _contentLength = len;
+ if (!len) {
+ _sendContentLength = false;
+ }
+ _contentType = contentType;
+ _filledLength = 0;
+}
+
+size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len) {
+ size_t ret = _content(data, len, _filledLength);
+ if (ret != RESPONSE_TRY_AGAIN) {
+ _filledLength += ret;
+ }
+ return ret;
+}
+
+/*
+ * Chunked Response
+ * */
+
+AsyncChunkedResponse::AsyncChunkedResponse(const char *contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback)
+ : AsyncAbstractResponse(processorCallback) {
+ _code = 200;
+ _content = callback;
+ _contentLength = 0;
+ _contentType = contentType;
+ _sendContentLength = false;
+ _chunked = true;
+ _filledLength = 0;
+}
+
+size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len) {
+ size_t ret = _content(data, len, _filledLength);
+ if (ret != RESPONSE_TRY_AGAIN) {
+ _filledLength += ret;
+ }
+ return ret;
+}
+
+/*
+ * Progmem Response
+ * */
+
+AsyncProgmemResponse::AsyncProgmemResponse(int code, const char *contentType, const uint8_t *content, size_t len, AwsTemplateProcessor callback)
+ : AsyncAbstractResponse(callback) {
+ _code = code;
+ _content = content;
+ _contentType = contentType;
+ _contentLength = len;
+ _readLength = 0;
+}
+
+size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len) {
+ size_t left = _contentLength - _readLength;
+ if (left > len) {
+ memcpy_P(data, _content + _readLength, len);
+ _readLength += len;
+ return len;
+ }
+ memcpy_P(data, _content + _readLength, left);
+ _readLength += left;
+ return left;
+}
+
+/*
+ * Response Stream (You can print/write/printf to it, up to the contentLen bytes)
+ * */
+
+AsyncResponseStream::AsyncResponseStream(const char *contentType, size_t bufferSize) {
+ _code = 200;
+ _contentLength = 0;
+ _contentType = contentType;
+ // internal buffer will be null on allocation failure
+ _content = std::unique_ptr<cbuf>(new cbuf(bufferSize));
+ if (bufferSize && _content->size() < bufferSize) {
+#ifdef ESP32
+ log_e("Failed to allocate");
+#endif
+ }
+}
+
+size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen) {
+ return _content->read((char *)buf, maxLen);
+}
+
+size_t AsyncResponseStream::write(const uint8_t *data, size_t len) {
+ if (_started()) {
+ return 0;
+ }
+ if (len > _content->room()) {
+ size_t needed = len - _content->room();
+ _content->resizeAdd(needed);
+ // log a warning if allocation failed, but do not return: keep writing the bytes we can
+ // with _content->write: if len is more than the available size in the buffer, only
+ // the available size will be written
+ if (len > _content->room()) {
+#ifdef ESP32
+ log_e("Failed to allocate");
+#endif
+ }
+ }
+ size_t written = _content->write((const char *)data, len);
+ _contentLength += written;
+ return written;
+}
+
+size_t AsyncResponseStream::write(uint8_t data) {
+ return write(&data, 1);
+}
diff --git a/libraries/ESP_Async_WebServer/src/WebServer.cpp b/libraries/ESP_Async_WebServer/src/WebServer.cpp
new file mode 100644
index 0000000..7fc54bf
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/WebServer.cpp
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#include "ESPAsyncWebServer.h"
+#include "WebHandlerImpl.h"
+
+using namespace asyncsrv;
+
+bool ON_STA_FILTER(AsyncWebServerRequest *request) {
+#ifndef CONFIG_IDF_TARGET_ESP32H2
+ return WiFi.localIP() == request->client()->localIP();
+#else
+ return false;
+#endif
+}
+
+bool ON_AP_FILTER(AsyncWebServerRequest *request) {
+#ifndef CONFIG_IDF_TARGET_ESP32H2
+ return WiFi.localIP() != request->client()->localIP();
+#else
+ return false;
+#endif
+}
+
+#ifndef HAVE_FS_FILE_OPEN_MODE
+const char *fs::FileOpenMode::read = "r";
+const char *fs::FileOpenMode::write = "w";
+const char *fs::FileOpenMode::append = "a";
+#endif
+
+AsyncWebServer::AsyncWebServer(uint16_t port) : _server(port) {
+ _catchAllHandler = new AsyncCallbackWebHandler();
+ _server.onClient(
+ [](void *s, AsyncClient *c) {
+ if (c == NULL) {
+ return;
+ }
+ c->setRxTimeout(3);
+ AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer *)s, c);
+ if (r == NULL) {
+ c->abort();
+ delete c;
+ }
+ },
+ this
+ );
+}
+
+AsyncWebServer::~AsyncWebServer() {
+ reset();
+ end();
+ delete _catchAllHandler;
+ _catchAllHandler = nullptr; // Prevent potential use-after-free
+}
+
+AsyncWebRewrite &AsyncWebServer::addRewrite(std::shared_ptr<AsyncWebRewrite> rewrite) {
+ _rewrites.emplace_back(rewrite);
+ return *_rewrites.back().get();
+}
+
+AsyncWebRewrite &AsyncWebServer::addRewrite(AsyncWebRewrite *rewrite) {
+ _rewrites.emplace_back(rewrite);
+ return *_rewrites.back().get();
+}
+
+bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite) {
+ return removeRewrite(rewrite->from().c_str(), rewrite->toUrl().c_str());
+}
+
+bool AsyncWebServer::removeRewrite(const char *from, const char *to) {
+ for (auto r = _rewrites.begin(); r != _rewrites.end(); ++r) {
+ if (r->get()->from() == from && r->get()->toUrl() == to) {
+ _rewrites.erase(r);
+ return true;
+ }
+ }
+ return false;
+}
+
+AsyncWebRewrite &AsyncWebServer::rewrite(const char *from, const char *to) {
+ _rewrites.emplace_back(std::make_shared<AsyncWebRewrite>(from, to));
+ return *_rewrites.back().get();
+}
+
+AsyncWebHandler &AsyncWebServer::addHandler(AsyncWebHandler *handler) {
+ _handlers.emplace_back(handler);
+ return *(_handlers.back().get());
+}
+
+bool AsyncWebServer::removeHandler(AsyncWebHandler *handler) {
+ for (auto i = _handlers.begin(); i != _handlers.end(); ++i) {
+ if (i->get() == handler) {
+ _handlers.erase(i);
+ return true;
+ }
+ }
+ return false;
+}
+
+void AsyncWebServer::begin() {
+ _server.setNoDelay(true);
+ _server.begin();
+}
+
+void AsyncWebServer::end() {
+ _server.end();
+}
+
+#if ASYNC_TCP_SSL_ENABLED
+void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void *arg) {
+ _server.onSslFileRequest(cb, arg);
+}
+
+void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password) {
+ _server.beginSecure(cert, key, password);
+}
+#endif
+
+void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request) {
+ delete 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();
+ request->_addGetParams(r->params());
+ }
+ }
+}
+
+void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request) {
+ for (auto &h : _handlers) {
+ if (h->filter(request) && h->canHandle(request)) {
+ request->setHandler(h.get());
+ return;
+ }
+ }
+ // ESP_LOGD("AsyncWebServer", "No handler found for %s, using _catchAllHandler pointer: %p", request->url().c_str(), _catchAllHandler);
+ request->setHandler(_catchAllHandler);
+}
+
+AsyncCallbackWebHandler &AsyncWebServer::on(
+ const char *uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody
+) {
+ AsyncCallbackWebHandler *handler = new AsyncCallbackWebHandler();
+ handler->setUri(uri);
+ handler->setMethod(method);
+ handler->onRequest(onRequest);
+ handler->onUpload(onUpload);
+ handler->onBody(onBody);
+ addHandler(handler);
+ return *handler;
+}
+
+AsyncStaticWebHandler &AsyncWebServer::serveStatic(const char *uri, fs::FS &fs, const char *path, const char *cache_control) {
+ AsyncStaticWebHandler *handler = new AsyncStaticWebHandler(uri, fs, path, cache_control);
+ addHandler(handler);
+ return *handler;
+}
+
+void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn) {
+ _catchAllHandler->onRequest(fn);
+}
+
+void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn) {
+ _catchAllHandler->onUpload(fn);
+}
+
+void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn) {
+ _catchAllHandler->onBody(fn);
+}
+
+AsyncWebHandler &AsyncWebServer::catchAllHandler() const {
+ return *_catchAllHandler;
+}
+
+void AsyncWebServer::reset() {
+ _rewrites.clear();
+ _handlers.clear();
+
+ _catchAllHandler->onRequest(NULL);
+ _catchAllHandler->onUpload(NULL);
+ _catchAllHandler->onBody(NULL);
+}
diff --git a/libraries/ESP_Async_WebServer/src/literals.h b/libraries/ESP_Async_WebServer/src/literals.h
new file mode 100644
index 0000000..a69f78b
--- /dev/null
+++ b/libraries/ESP_Async_WebServer/src/literals.h
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
+
+#pragma once
+
+namespace asyncsrv {
+
+static constexpr const char *empty = "";
+
+static constexpr const char *T__opaque = "\", opaque=\"";
+static constexpr const char *T_100_CONTINUE = "100-continue";
+static constexpr const char *T_13 = "13";
+static constexpr const char *T_ACCEPT = "accept";
+static constexpr const char *T_Accept_Ranges = "accept-ranges";
+static constexpr const char *T_app_xform_urlencoded = "application/x-www-form-urlencoded";
+static constexpr const char *T_AUTH = "authorization";
+static constexpr const char *T_auth_nonce = "\", qop=\"auth\", nonce=\"";
+static constexpr const char *T_BASIC = "basic";
+static constexpr const char *T_BASIC_REALM = "basic realm=\"";
+static constexpr const char *T_BEARER = "bearer";
+static constexpr const char *T_BODY = "body";
+static constexpr const char *T_Cache_Control = "cache-control";
+static constexpr const char *T_chunked = "chunked";
+static constexpr const char *T_close = "close";
+static constexpr const char *T_cnonce = "cnonce";
+static constexpr const char *T_Connection = "connection";
+static constexpr const char *T_Content_Disposition = "content-disposition";
+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";
+static constexpr const char *T_CORS_ACAM = "access-control-allow-methods";
+static constexpr const char *T_CORS_ACAO = "access-control-allow-origin";
+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";
+static constexpr const char *T_event_ = "event: ";
+static constexpr const char *T_EXPECT = "expect";
+static constexpr const char *T_FALSE = "false";
+static constexpr const char *T_filename = "filename";
+static constexpr const char *T_gzip = "gzip";
+static constexpr const char *T_Host = "host";
+static constexpr const char *T_HTTP_1_0 = "HTTP/1.0";
+static constexpr const char *T_HTTP_100_CONT = "HTTP/1.1 100 Continue\r\n\r\n";
+static constexpr const char *T_id__ = "id: ";
+static constexpr const char *T_IMS = "if-modified-since";
+static constexpr const char *T_INM = "if-none-match";
+static constexpr const char *T_keep_alive = "keep-alive";
+static constexpr const char *T_Last_Event_ID = "last-event-id";
+static constexpr const char *T_Last_Modified = "last-modified";
+static constexpr const char *T_LOCATION = "location";
+static constexpr const char *T_LOGIN_REQ = "Login Required";
+static constexpr const char *T_MULTIPART_ = "multipart/";
+static constexpr const char *T_name = "name";
+static constexpr const char *T_nc = "nc";
+static constexpr const char *T_no_cache = "no-cache";
+static constexpr const char *T_nonce = "nonce";
+static constexpr const char *T_none = "none";
+static constexpr const char *T_opaque = "opaque";
+static constexpr const char *T_qop = "qop";
+static constexpr const char *T_realm = "realm";
+static constexpr const char *T_realm__ = "realm=\"";
+static constexpr const char *T_response = "response";
+static constexpr const char *T_retry_ = "retry: ";
+static constexpr const char *T_retry_after = "retry-after";
+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";
+static constexpr const char *T_uri = "uri";
+static constexpr const char *T_username = "username";
+static constexpr const char *T_WS = "websocket";
+static constexpr const char *T_WWW_AUTH = "www-authenticate";
+
+// HTTP Methods
+
+static constexpr const char *T_ANY = "ANY";
+static constexpr const char *T_GET = "GET";
+static constexpr const char *T_POST = "POST";
+static constexpr const char *T_PUT = "PUT";
+static constexpr const char *T_DELETE = "DELETE";
+static constexpr const char *T_PATCH = "PATCH";
+static constexpr const char *T_HEAD = "HEAD";
+static constexpr const char *T_OPTIONS = "OPTIONS";
+static constexpr const char *T_UNKNOWN = "UNKNOWN";
+
+// Req content types
+static constexpr const char *T_RCT_NOT_USED = "RCT_NOT_USED";
+static constexpr const char *T_RCT_DEFAULT = "RCT_DEFAULT";
+static constexpr const char *T_RCT_HTTP = "RCT_HTTP";
+static constexpr const char *T_RCT_WS = "RCT_WS";
+static constexpr const char *T_RCT_EVENT = "RCT_EVENT";
+static constexpr const char *T_ERROR = "ERROR";
+
+// extensions & MIME-Types
+static constexpr const char *T__css = ".css";
+static constexpr const char *T__eot = ".eot";
+static constexpr const char *T__gif = ".gif";
+static constexpr const char *T__gz = ".gz";
+static constexpr const char *T__htm = ".htm";
+static constexpr const char *T__html = ".html";
+static constexpr const char *T__ico = ".ico";
+static constexpr const char *T__jpg = ".jpg";
+static constexpr const char *T__js = ".js";
+static constexpr const char *T__json = ".json";
+static constexpr const char *T__pdf = ".pdf";
+static constexpr const char *T__png = ".png";
+static constexpr const char *T__svg = ".svg";
+static constexpr const char *T__ttf = ".ttf";
+static constexpr const char *T__woff = ".woff";
+static constexpr const char *T__woff2 = ".woff2";
+static constexpr const char *T__xml = ".xml";
+static constexpr const char *T__zip = ".zip";
+static constexpr const char *T_application_javascript = "application/javascript";
+static constexpr const char *T_application_json = "application/json";
+static constexpr const char *T_application_msgpack = "application/msgpack";
+static constexpr const char *T_application_pdf = "application/pdf";
+static constexpr const char *T_application_x_gzip = "application/x-gzip";
+static constexpr const char *T_application_zip = "application/zip";
+static constexpr const char *T_font_eot = "font/eot";
+static constexpr const char *T_font_ttf = "font/ttf";
+static constexpr const char *T_font_woff = "font/woff";
+static constexpr const char *T_font_woff2 = "font/woff2";
+static constexpr const char *T_image_gif = "image/gif";
+static constexpr const char *T_image_jpeg = "image/jpeg";
+static constexpr const char *T_image_png = "image/png";
+static constexpr const char *T_image_svg_xml = "image/svg+xml";
+static constexpr const char *T_image_x_icon = "image/x-icon";
+static constexpr const char *T_text_css = "text/css";
+static constexpr const char *T_text_event_stream = "text/event-stream";
+static constexpr const char *T_text_html = "text/html";
+static constexpr const char *T_text_plain = "text/plain";
+static constexpr const char *T_text_xml = "text/xml";
+
+// 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";
+static constexpr const char *T_HTTP_CODE_201 = "Created";
+static constexpr const char *T_HTTP_CODE_202 = "Accepted";
+static constexpr const char *T_HTTP_CODE_203 = "Non-Authoritative Information";
+static constexpr const char *T_HTTP_CODE_204 = "No Content";
+static constexpr const char *T_HTTP_CODE_205 = "Reset Content";
+static constexpr const char *T_HTTP_CODE_206 = "Partial Content";
+static constexpr const char *T_HTTP_CODE_300 = "Multiple Choices";
+static constexpr const char *T_HTTP_CODE_301 = "Moved Permanently";
+static constexpr const char *T_HTTP_CODE_302 = "Found";
+static constexpr const char *T_HTTP_CODE_303 = "See Other";
+static constexpr const char *T_HTTP_CODE_304 = "Not Modified";
+static constexpr const char *T_HTTP_CODE_305 = "Use Proxy";
+static constexpr const char *T_HTTP_CODE_307 = "Temporary Redirect";
+static constexpr const char *T_HTTP_CODE_400 = "Bad Request";
+static constexpr const char *T_HTTP_CODE_401 = "Unauthorized";
+static constexpr const char *T_HTTP_CODE_402 = "Payment Required";
+static constexpr const char *T_HTTP_CODE_403 = "Forbidden";
+static constexpr const char *T_HTTP_CODE_404 = "Not Found";
+static constexpr const char *T_HTTP_CODE_405 = "Method Not Allowed";
+static constexpr const char *T_HTTP_CODE_406 = "Not Acceptable";
+static constexpr const char *T_HTTP_CODE_407 = "Proxy Authentication Required";
+static constexpr const char *T_HTTP_CODE_408 = "Request Time-out";
+static constexpr const char *T_HTTP_CODE_409 = "Conflict";
+static constexpr const char *T_HTTP_CODE_410 = "Gone";
+static constexpr const char *T_HTTP_CODE_411 = "Length Required";
+static constexpr const char *T_HTTP_CODE_412 = "Precondition Failed";
+static constexpr const char *T_HTTP_CODE_413 = "Request Entity Too Large";
+static constexpr const char *T_HTTP_CODE_414 = "Request-URI Too Large";
+static constexpr const char *T_HTTP_CODE_415 = "Unsupported Media Type";
+static constexpr const char *T_HTTP_CODE_416 = "Requested range not satisfiable";
+static constexpr const char *T_HTTP_CODE_417 = "Expectation Failed";
+static constexpr const char *T_HTTP_CODE_429 = "Too Many Requests";
+static constexpr const char *T_HTTP_CODE_500 = "Internal Server Error";
+static constexpr const char *T_HTTP_CODE_501 = "Not Implemented";
+static constexpr const char *T_HTTP_CODE_502 = "Bad Gateway";
+static constexpr const char *T_HTTP_CODE_503 = "Service Unavailable";
+static constexpr const char *T_HTTP_CODE_504 = "Gateway Time-out";
+static constexpr const char *T_HTTP_CODE_505 = "HTTP Version not supported";
+static constexpr const char *T_HTTP_CODE_ANY = "Unknown code";
+
+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