diff --git a/README.md b/README.md index 6618c74..70b2143 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # ESPAsyncWebServer -[![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/) -[![Continuous Integration](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml/badge.svg)](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml) - [![Latest Release](https://img.shields.io/github/release/mathieucarbou/ESPAsyncWebServer.svg)](https://GitHub.com/mathieucarbou/ESPAsyncWebServer/releases/) [![PlatformIO Registry](https://badges.registry.platformio.org/packages/mathieucarbou/library/ESPAsyncWebServer.svg)](https://registry.platformio.org/libraries/mathieucarbou/ESPAsyncWebServer) +[![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/) +[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](code_of_conduct.md) + +[![Build](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml/badge.svg)](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml) +[![GitHub latest commit](https://badgen.net/github/last-commit/mathieucarbou/ESPAsyncWebServer)](https://GitHub.com/mathieucarbou/ESPAsyncWebServer/commit/) [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/mathieucarbou/ESPAsyncWebServer) Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040 @@ -18,12 +20,12 @@ This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubo **WARNING** The library name was changed from `ESP Async WebServer` to `ESPAsyncWebServer` as per the Arduino Lint recommendations. ``` -mathieucarbou/ESPAsyncWebServer @ 3.2.0 +mathieucarbou/ESPAsyncWebServer @ 3.2.4 ``` Dependency: -- **ESP32**: `mathieucarbou/AsyncTCP @ 3.2.4` (Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.2.4](https://github.com/mathieucarbou/AsyncTCP/releases/tag/v3.2.0)) +- **ESP32**: `mathieucarbou/AsyncTCP @ 3.2.5` (Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.2.5](https://github.com/mathieucarbou/AsyncTCP/releases)) - **ESP8266**: `esphome/ESPAsyncTCP-esphome @ 2.0.0` (Arduino IDE: [https://github.com/mathieucarbou/esphome-ESPAsyncTCP#v2.0.0](https://github.com/mathieucarbou/esphome-ESPAsyncTCP/releases/tag/v2.0.0)) - **RP2040**: `khoih-prog/AsyncTCP_RP2040W @ 1.2.0` (Arduino IDE: [https://github.com/khoih-prog/AsyncTCP_RP2040W#v1.2.0](https://github.com/khoih-prog/AsyncTCP_RP2040W/releases/tag/v1.2.0)) @@ -40,10 +42,9 @@ Dependency: - [@mathieucarbou](https://github.com/mathieucarbou): Arduino 3 / ESP-IDF 5.1 compatibility - [@mathieucarbou](https://github.com/mathieucarbou): Arduino Json 7 compatibility and backward compatible with 6 and 6 (changes in `AsyncJson.h`). The API to use Json has not changed. These are only internal changes. - [@mathieucarbou](https://github.com/mathieucarbou): CI -- [@mathieucarbou](https://github.com/mathieucarbou): Depends on `mathieucarbou/AsyncTCP @ 3.2.4` +- [@mathieucarbou](https://github.com/mathieucarbou): Depends on `mathieucarbou/AsyncTCP @ 3.2.5` - [@mathieucarbou](https://github.com/mathieucarbou): Deployed in PlatformIO registry and Arduino IDE library manager - [@mathieucarbou](https://github.com/mathieucarbou): Firmware size optimization: remove mbedtls dependency (accounts for 33KB in firmware) -- [@mathieucarbou](https://github.com/mathieucarbou): Made DEFAULT_MAX_SSE_CLIENTS customizable - [@mathieucarbou](https://github.com/mathieucarbou): Made DEFAULT_MAX_WS_CLIENTS customizable - [@mathieucarbou](https://github.com/mathieucarbou): MessagePack Support ([#62](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/62)) - [@mathieucarbou](https://github.com/mathieucarbou): Remove filename after inline in Content-Disposition header according to RFC2183 diff --git a/docs/index.md b/docs/index.md index 6618c74..70b2143 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,11 +1,13 @@ # ESPAsyncWebServer -[![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/) -[![Continuous Integration](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml/badge.svg)](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml) - [![Latest Release](https://img.shields.io/github/release/mathieucarbou/ESPAsyncWebServer.svg)](https://GitHub.com/mathieucarbou/ESPAsyncWebServer/releases/) [![PlatformIO Registry](https://badges.registry.platformio.org/packages/mathieucarbou/library/ESPAsyncWebServer.svg)](https://registry.platformio.org/libraries/mathieucarbou/ESPAsyncWebServer) +[![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/) +[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](code_of_conduct.md) + +[![Build](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml/badge.svg)](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml) +[![GitHub latest commit](https://badgen.net/github/last-commit/mathieucarbou/ESPAsyncWebServer)](https://GitHub.com/mathieucarbou/ESPAsyncWebServer/commit/) [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/mathieucarbou/ESPAsyncWebServer) Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040 @@ -18,12 +20,12 @@ This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubo **WARNING** The library name was changed from `ESP Async WebServer` to `ESPAsyncWebServer` as per the Arduino Lint recommendations. ``` -mathieucarbou/ESPAsyncWebServer @ 3.2.0 +mathieucarbou/ESPAsyncWebServer @ 3.2.4 ``` Dependency: -- **ESP32**: `mathieucarbou/AsyncTCP @ 3.2.4` (Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.2.4](https://github.com/mathieucarbou/AsyncTCP/releases/tag/v3.2.0)) +- **ESP32**: `mathieucarbou/AsyncTCP @ 3.2.5` (Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.2.5](https://github.com/mathieucarbou/AsyncTCP/releases)) - **ESP8266**: `esphome/ESPAsyncTCP-esphome @ 2.0.0` (Arduino IDE: [https://github.com/mathieucarbou/esphome-ESPAsyncTCP#v2.0.0](https://github.com/mathieucarbou/esphome-ESPAsyncTCP/releases/tag/v2.0.0)) - **RP2040**: `khoih-prog/AsyncTCP_RP2040W @ 1.2.0` (Arduino IDE: [https://github.com/khoih-prog/AsyncTCP_RP2040W#v1.2.0](https://github.com/khoih-prog/AsyncTCP_RP2040W/releases/tag/v1.2.0)) @@ -40,10 +42,9 @@ Dependency: - [@mathieucarbou](https://github.com/mathieucarbou): Arduino 3 / ESP-IDF 5.1 compatibility - [@mathieucarbou](https://github.com/mathieucarbou): Arduino Json 7 compatibility and backward compatible with 6 and 6 (changes in `AsyncJson.h`). The API to use Json has not changed. These are only internal changes. - [@mathieucarbou](https://github.com/mathieucarbou): CI -- [@mathieucarbou](https://github.com/mathieucarbou): Depends on `mathieucarbou/AsyncTCP @ 3.2.4` +- [@mathieucarbou](https://github.com/mathieucarbou): Depends on `mathieucarbou/AsyncTCP @ 3.2.5` - [@mathieucarbou](https://github.com/mathieucarbou): Deployed in PlatformIO registry and Arduino IDE library manager - [@mathieucarbou](https://github.com/mathieucarbou): Firmware size optimization: remove mbedtls dependency (accounts for 33KB in firmware) -- [@mathieucarbou](https://github.com/mathieucarbou): Made DEFAULT_MAX_SSE_CLIENTS customizable - [@mathieucarbou](https://github.com/mathieucarbou): Made DEFAULT_MAX_WS_CLIENTS customizable - [@mathieucarbou](https://github.com/mathieucarbou): MessagePack Support ([#62](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/62)) - [@mathieucarbou](https://github.com/mathieucarbou): Remove filename after inline in Content-Disposition header according to RFC2183 diff --git a/examples/SimpleServer/SimpleServer.ino b/examples/SimpleServer/SimpleServer.ino index 2119242..2a2079a 100644 --- a/examples/SimpleServer/SimpleServer.ino +++ b/examples/SimpleServer/SimpleServer.ino @@ -25,8 +25,40 @@ #include AsyncWebServer server(80); +AsyncEventSource events("/events"); +AsyncWebSocket ws("/ws"); -const char* PARAM_MESSAGE = "message"; +const char* PARAM_MESSAGE PROGMEM = "message"; +const char* SSE_HTLM PROGMEM = R"( + + + + Server-Sent Events + + + +

Open your browser console!

+ + +)"; void notFound(AsyncWebServerRequest* request) { request->send(404, "text/plain", "Not found"); @@ -69,12 +101,15 @@ void setup() { Connection: close Accept-Ranges: bytes */ - // Ref: https://github.com/mathieucarbou/ESPAsyncWebServer/pull/80 + // Ref: https://github.com/mathieucarbou/ESPAsyncWebServer/pull/80 server.on("/download", HTTP_HEAD | HTTP_GET, [](AsyncWebServerRequest* request) { if (request->method() == HTTP_HEAD) { AsyncWebServerResponse* response = request->beginResponse(200, "application/octet-stream"); - response->setContentLength(1024); // myFile.getSize() - response->addHeader("Accept-Ranges", "bytes"); + response->addHeader(asyncsrv::T_Accept_Ranges, "bytes"); + response->addHeader(asyncsrv::T_Content_Length, 10); + response->setContentLength(1024); // overrides previous one + response->addHeader(asyncsrv::T_Content_Type, "foo"); + response->setContentType("application/octet-stream"); // overrides previous one // ... request->send(response); } else { @@ -108,7 +143,7 @@ void setup() { // receives JSON and sends JSON jsonHandler->onRequest([](AsyncWebServerRequest* request, JsonVariant& json) { - JsonObject jsonObj = json.as(); + // JsonObject jsonObj = json.as(); // ... AsyncJsonResponse* response = new AsyncJsonResponse(); @@ -131,7 +166,7 @@ void setup() { // receives MessagePack and sends MessagePack msgPackHandler->onRequest([](AsyncWebServerRequest* request, JsonVariant& json) { - JsonObject jsonObj = json.as(); + // JsonObject jsonObj = json.as(); // ... AsyncMessagePackResponse* response = new AsyncMessagePackResponse(); @@ -150,6 +185,43 @@ void setup() { request->send(response); }); + events.onConnect([](AsyncEventSourceClient* client) { + if (client->lastId()) { + Serial.printf("SSE Client reconnected! Last message ID that it gat is: %" PRIu32 "\n", client->lastId()); + } + client->send("hello!", NULL, millis(), 1000); + }); + + server.on("/sse", HTTP_GET, [](AsyncWebServerRequest* request) { + request->send(200, "text/html", SSE_HTLM); + }); + + ws.onEvent([](AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) { + (void) len; + if (type == WS_EVT_CONNECT) { + Serial.println("ws connect"); + client->setCloseClientOnQueueFull(false); + client->ping(); + } else if (type == WS_EVT_DISCONNECT) { + Serial.println("ws disconnect"); + } else if (type == WS_EVT_ERROR) { + Serial.println("ws error"); + } else if (type == WS_EVT_PONG) { + Serial.println("ws pong"); + } else if (type == WS_EVT_DATA) { + AwsFrameInfo* info = (AwsFrameInfo*)arg; + String msg = ""; + if (info->final && info->index == 0 && info->len == len) { + if (info->opcode == WS_TEXT) { + data[len] = 0; + Serial.printf("ws text: %s\n", (char*)data); + } + } + } + }); + + server.addHandler(&events); + server.addHandler(&ws); server.addHandler(jsonHandler); server.addHandler(msgPackHandler); @@ -158,5 +230,20 @@ void setup() { server.begin(); } +uint32_t lastSSE = 0; +uint32_t deltaSSE = 5; + +uint32_t lastWS = 0; +uint32_t deltaWS = 100; + void loop() { -} \ No newline at end of file + uint32_t now = millis(); + if (now - lastSSE >= deltaSSE) { + events.send(String("ping-") + now, "heartbeat", now); + lastSSE = millis(); + } + if (now - lastWS >= deltaWS) { + ws.printfAll("kp%.4f", (10.0 / 3.0)); + lastWS = millis(); + } +} diff --git a/library.json_ b/library.json_ index f2a6ed0..db3d040 100644 --- a/library.json_ +++ b/library.json_ @@ -1,6 +1,6 @@ { "name": "ESPAsyncWebServer", - "version": "3.2.0", + "version": "3.2.4", "description": "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.", "keywords": "http,async,websocket,webserver", "homepage": "https://github.com/mathieucarbou/ESPAsyncWebServer", @@ -28,7 +28,7 @@ { "owner": "mathieucarbou", "name": "AsyncTCP", - "version": "^3.2.4", + "version": "^3.2.5", "platforms": "espressif32" }, { diff --git a/library.properties b/library.properties index 46dac86..bc5ba6d 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ESPAsyncWebServer -version=3.2.0 +version=3.2.4 author=Me-No-Dev maintainer=Mathieu Carbou sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040 diff --git a/platformio.ini b/platformio.ini index 6ac85dd..b72ac65 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2,6 +2,7 @@ framework = arduino build_flags = -Wall -Wextra + -Wno-unused-parameter -D CONFIG_ARDUHAL_LOG_COLORS -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000 @@ -22,19 +23,12 @@ src_dir = examples/SimpleServer ; src_dir = examples/Draft ; src_dir = examples/issues/Issue14 -[env:arduino] -platform = espressif32 -board = esp32dev -lib_deps = - bblanchon/ArduinoJson @ 7.1.0 - mathieucarbou/AsyncTCP @ 3.2.4 - [env:arduino-2] platform = espressif32@6.8.1 board = esp32dev lib_deps = bblanchon/ArduinoJson @ 7.1.0 - mathieucarbou/AsyncTCP @ 3.2.4 + mathieucarbou/AsyncTCP @ 3.2.5 [env:arduino-3] platform = espressif32 @@ -44,7 +38,7 @@ platform_packages= board = esp32dev lib_deps = bblanchon/ArduinoJson @ 7.1.0 - mathieucarbou/AsyncTCP @ 3.2.4 + mathieucarbou/AsyncTCP @ 3.2.5 [env:esp8266] platform = espressif8266 @@ -70,18 +64,18 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/ board = esp32dev lib_deps = bblanchon/ArduinoJson @ 7.1.0 - mathieucarbou/AsyncTCP @ 3.2.4 + mathieucarbou/AsyncTCP @ 3.2.5 [env:pioarduino-c6] platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.04/platform-espressif32.zip board = esp32-c6-devkitc-1 lib_deps = bblanchon/ArduinoJson @ 7.1.0 - mathieucarbou/AsyncTCP @ 3.2.4 + mathieucarbou/AsyncTCP @ 3.2.5 [env:pioarduino-h2] platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.04/platform-espressif32.zip board = esp32-h2-devkitm-1 lib_deps = bblanchon/ArduinoJson @ 7.1.0 - mathieucarbou/AsyncTCP @ 3.2.4 + mathieucarbou/AsyncTCP @ 3.2.5 diff --git a/src/AsyncEventSource.cpp b/src/AsyncEventSource.cpp index 639fd56..6a290e1 100644 --- a/src/AsyncEventSource.cpp +++ b/src/AsyncEventSource.cpp @@ -128,8 +128,7 @@ AsyncEventSourceMessage::~AsyncEventSourceMessage() { free(_data); } -size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) { - (void)time; +size_t AsyncEventSourceMessage::ack(size_t len, __attribute__((unused)) uint32_t time) { // If the whole message is now acked... if (_acked + len > _len) { // Return the number of extra bytes acked (they will be carried on to the next message) @@ -142,17 +141,19 @@ size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) { return 0; } -// This could also return void as the return value is not used. -// Leaving as-is for compatibility... -size_t AsyncEventSourceMessage::send(AsyncClient* client) { - if (_sent >= _len) { +size_t AsyncEventSourceMessage::write(AsyncClient* client) { + if (_sent >= _len || !client->canSend()) { return 0; } - const size_t len_to_send = _len - _sent; - auto position = reinterpret_cast(_data + _sent); - const size_t sent_now = client->write(position, len_to_send); - _sent += sent_now; - return sent_now; + size_t len = min(_len - _sent, client->space()); + size_t sent = client->add((const char*)_data + _sent, len); + _sent += sent; + return sent; +} + +size_t AsyncEventSourceMessage::send(AsyncClient* client) { + size_t sent = write(client); + return sent && client->send() ? sent : 0; } // Client @@ -174,6 +175,8 @@ AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest* request, A _server->_addClient(this); delete request; + + _client->setNoDelay(true); } AsyncEventSourceClient::~AsyncEventSourceClient() { @@ -211,11 +214,6 @@ void AsyncEventSourceClient::_onAck(size_t len, uint32_t time) { // Same here, acquiring the lock early std::lock_guard lock(_lockmq); #endif - while (len && _messageQueue.size()) { - len = _messageQueue.front().ack(len, time); - if (_messageQueue.front().finished()) - _messageQueue.pop_front(); - } _runQueue(); } @@ -264,11 +262,24 @@ size_t AsyncEventSourceClient::packetsWaiting() const { } void AsyncEventSourceClient::_runQueue() { - // Calls to this private method now already protected by _lockmq acquisition - // so no extra call of _lockmq.lock() here.. - for (auto& i : _messageQueue) { - if (!i.sent()) - i.send(_client); + 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; + if (bytes_written == 0) + break; + } + } + if (total_bytes_written > 0) + _client->send(); + + size_t len = total_bytes_written; + while (len && _messageQueue.size()) { + len = _messageQueue.front().ack(len); + if (_messageQueue.front().finished()) { + _messageQueue.pop_front(); + } } } @@ -392,7 +403,8 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource* server) { } void AsyncEventSourceResponse::_respond(AsyncWebServerRequest* request) { - String out = _assembleHead(request->version()); + String out; + _assembleHead(out, request->version()); request->client()->write(out.c_str(), _headLength); _state = RESPONSE_WAIT_ACK; } diff --git a/src/AsyncEventSource.h b/src/AsyncEventSource.h index 0289ebf..2eae211 100644 --- a/src/AsyncEventSource.h +++ b/src/AsyncEventSource.h @@ -49,14 +49,6 @@ #endif #endif -#ifndef DEFAULT_MAX_SSE_CLIENTS - #ifdef ESP32 - #define DEFAULT_MAX_SSE_CLIENTS 8 - #else - #define DEFAULT_MAX_SSE_CLIENTS 4 - #endif -#endif - class AsyncEventSource; class AsyncEventSourceResponse; class AsyncEventSourceClient; @@ -74,7 +66,8 @@ class AsyncEventSourceMessage { public: AsyncEventSourceMessage(const char* data, size_t len); ~AsyncEventSourceMessage(); - size_t ack(size_t len, uint32_t time __attribute__((unused))); + size_t ack(size_t len, uint32_t time = 0); + size_t write(AsyncClient* client); size_t send(AsyncClient* client); bool finished() { return _acked == _len; } bool sent() { return _sent == _len; } @@ -99,6 +92,8 @@ class AsyncEventSourceClient { AsyncClient* client() { return _client; } void close(); void write(const char* message, size_t len); + void send(const String& message, const String& event, uint32_t id = 0, uint32_t reconnect = 0) { send(message.c_str(), event.c_str(), id, reconnect); } + void send(const String& message, const char* event, uint32_t id = 0, uint32_t reconnect = 0) { send(message.c_str(), event, id, reconnect); } void send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0); bool connected() const { return (_client != NULL) && _client->connected(); } uint32_t lastId() const { return _lastId; } @@ -124,13 +119,15 @@ class AsyncEventSource : public AsyncWebHandler { ArAuthorizeConnectHandler _authorizeConnectHandler; public: - AsyncEventSource(const String& url) : _url(url){}; + AsyncEventSource(const String& url) : _url(url) {}; ~AsyncEventSource() { close(); }; const char* url() const { return _url.c_str(); } void close(); void onConnect(ArEventHandlerFunction cb); void authorizeConnect(ArAuthorizeConnectHandler cb); + void send(const String& message, const String& event, uint32_t id = 0, uint32_t reconnect = 0) { send(message.c_str(), event.c_str(), id, reconnect); } + void send(const String& message, const char* event, uint32_t id = 0, uint32_t reconnect = 0) { send(message.c_str(), event, id, reconnect); } void send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0); // number of clients connected size_t count() const; diff --git a/src/AsyncWebSocket.cpp b/src/AsyncWebSocket.cpp index 88d88ec..0b87cef 100644 --- a/src/AsyncWebSocket.cpp +++ b/src/AsyncWebSocket.cpp @@ -27,7 +27,7 @@ #if defined(ESP32) #if ESP_IDF_VERSION_MAJOR < 5 - #include "./port/SHA1Builder.h" + #include "BackPort_SHA1Builder.h" #else #include #endif @@ -36,11 +36,8 @@ #include #endif -#define MAX_PRINTF_LEN 64 - using namespace asyncsrv; - size_t webSocketSendFrameWindow(AsyncClient* client) { if (!client->canSend()) return 0; @@ -589,30 +586,23 @@ void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) { size_t AsyncWebSocketClient::printf(const char* format, ...) { va_list arg; va_start(arg, format); - char* temp = new char[MAX_PRINTF_LEN]; - if (!temp) { - va_end(arg); - return 0; - } - char* buffer = temp; - size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg); + 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); - if (len > (MAX_PRINTF_LEN - 1)) { - buffer = new char[len + 1]; - if (!buffer) { - delete[] temp; - return 0; - } - va_start(arg, format); - vsnprintf(buffer, len + 1, format, arg); - va_end(arg); - } text(buffer, len); - if (buffer != temp) { - delete[] buffer; - } - delete[] temp; + delete[] buffer; return len; } @@ -620,30 +610,23 @@ size_t AsyncWebSocketClient::printf(const char* format, ...) { size_t AsyncWebSocketClient::printf_P(PGM_P formatP, ...) { va_list arg; va_start(arg, formatP); - char* temp = new char[MAX_PRINTF_LEN]; - if (!temp) { - va_end(arg); - return 0; - } - char* buffer = temp; - size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg); + 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); - if (len > (MAX_PRINTF_LEN - 1)) { - buffer = new char[len + 1]; - if (!buffer) { - delete[] temp; - return 0; - } - va_start(arg, formatP); - vsnprintf_P(buffer, len + 1, formatP, arg); - va_end(arg); - } text(buffer, len); - if (buffer != temp) { - delete[] buffer; - } - delete[] temp; + delete[] buffer; return len; } #endif @@ -1008,22 +991,24 @@ size_t AsyncWebSocket::printf(uint32_t id, const char* format, ...) { size_t AsyncWebSocket::printfAll(const char* format, ...) { va_list arg; - char* temp = new char[MAX_PRINTF_LEN]; - if (!temp) + 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); - size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg); - va_end(arg); - delete[] temp; - - AsyncWebSocketSharedBuffer buffer = std::make_shared>(len); - - va_start(arg, format); - vsnprintf((char*)buffer->data(), len + 1, format, arg); + len = vsnprintf(buffer, len + 1, format, arg); va_end(arg); - textAll(buffer); + textAll(buffer, len); + delete[] buffer; return len; } @@ -1042,22 +1027,24 @@ size_t AsyncWebSocket::printf_P(uint32_t id, PGM_P formatP, ...) { size_t AsyncWebSocket::printfAll_P(PGM_P formatP, ...) { va_list arg; - char* temp = new char[MAX_PRINTF_LEN]; - if (!temp) + 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); - size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg); - va_end(arg); - delete[] temp; - - AsyncWebSocketSharedBuffer buffer = std::make_shared>(len + 1); - - va_start(arg, formatP); - vsnprintf_P((char*)buffer->data(), len + 1, formatP, arg); + len = vsnprintf_P(buffer, len + 1, formatP, arg); va_end(arg); - textAll(buffer); + textAll(buffer, len); + delete[] buffer; return len; } #endif @@ -1192,7 +1179,8 @@ void AsyncWebSocketResponse::_respond(AsyncWebServerRequest* request) { request->client()->close(true); return; } - String out(_assembleHead(request->version())); + String out; + _assembleHead(out, request->version()); request->client()->write(out.c_str(), _headLength); _state = RESPONSE_WAIT_ACK; } diff --git a/src/port/SHA1Builder.cpp b/src/BackPort_SHA1Builder.cpp similarity index 99% rename from src/port/SHA1Builder.cpp rename to src/BackPort_SHA1Builder.cpp index 901fb80..08ba32c 100644 --- a/src/port/SHA1Builder.cpp +++ b/src/BackPort_SHA1Builder.cpp @@ -23,7 +23,7 @@ #include #if ESP_IDF_VERSION_MAJOR < 5 -#include "SHA1Builder.h" +#include "BackPort_SHA1Builder.h" // 32-bit integer manipulation macros (big endian) @@ -202,7 +202,7 @@ void SHA1Builder::process(const uint8_t *data) { // Public methods -void SHA1Builder::begin(void) { +void SHA1Builder::begin() { total[0] = 0; total[1] = 0; diff --git a/src/port/SHA1Builder.h b/src/BackPort_SHA1Builder.h similarity index 93% rename from src/port/SHA1Builder.h rename to src/BackPort_SHA1Builder.h index da9a77a..8f51825 100644 --- a/src/port/SHA1Builder.h +++ b/src/BackPort_SHA1Builder.h @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include +#if ESP_IDF_VERSION_MAJOR < 5 + #ifndef SHA1Builder_h #define SHA1Builder_h @@ -37,3 +40,5 @@ class SHA1Builder { }; #endif // SHA1Builder_h + +#endif // ESP_IDF_VERSION_MAJOR < 5 diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index d0790d5..df578f5 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -45,10 +45,10 @@ #include "literals.h" -#define ASYNCWEBSERVER_VERSION "3.2.0" +#define ASYNCWEBSERVER_VERSION "3.2.4" #define ASYNCWEBSERVER_VERSION_MAJOR 3 #define ASYNCWEBSERVER_VERSION_MINOR 2 -#define ASYNCWEBSERVER_VERSION_REVISION 0 +#define ASYNCWEBSERVER_VERSION_REVISION 4 #define ASYNCWEBSERVER_FORK_mathieucarbou #ifdef ASYNCWEBSERVER_REGEX @@ -155,7 +155,9 @@ class AsyncWebHeader { const String& name() const { return _name; } const String& value() const { return _value; } String toString() const { - String str = _name; + String str; + str.reserve(_name.length() + _value.length() + 2); + str.concat(_name); str.concat((char)0x3a); str.concat((char)0x20); str.concat(_value); @@ -333,11 +335,15 @@ class AsyncWebServerRequest { 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(...)")]] +#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(...)")]] +#endif void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr) { send(code, contentType, content, callback); } @@ -370,13 +376,17 @@ class AsyncWebServerRequest { AsyncResponseStream* beginResponseStream(const char* contentType, size_t bufferSize = 1460); AsyncResponseStream* beginResponseStream(const String& contentType, size_t bufferSize = 1460) { return beginResponseStream(contentType.c_str(), bufferSize); } +#ifndef ESP8266 [[deprecated("Replaced by beginResponse(...)")]] +#endif AsyncWebServerResponse* beginResponse_P(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { - return beginResponse(code, contentType, content, len, callback); + return beginResponse(code, contentType.c_str(), content, len, callback); } +#ifndef ESP8266 [[deprecated("Replaced by beginResponse(...)")]] +#endif AsyncWebServerResponse* beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr) { - return beginResponse(code, contentType, content, callback); + return beginResponse(code, contentType.c_str(), content, callback); } #ifdef ESP8266 @@ -583,8 +593,21 @@ class AsyncWebServerResponse { virtual void setContentType(const char* type); virtual 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); } virtual bool removeHeader(const char* name); - virtual String _assembleHead(uint8_t version); + virtual const AsyncWebHeader* getHeader(const char* name) const; + +#ifndef ESP8266 + [[deprecated("Use instead: _assembleHead(String& buffer, uint8_t version)")]] +#endif + String _assembleHead(uint8_t version) { + String buffer; + _assembleHead(buffer, version); + return buffer; + } + virtual void _assembleHead(String& buffer, uint8_t version); + virtual bool _started() const; virtual bool _finished() const; virtual bool _failed() const; diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index 95fda7e..022911e 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -624,7 +624,6 @@ bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper* data) const { 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); } diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp index af9c349..1fb6188 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp @@ -125,9 +125,8 @@ const char* AsyncWebServerResponse::responseCodeToString(int code) { return T_HTTP_CODE_ANY; } } -#else // ESP8266 -const __FlashStringHelper* AsyncWebServerResponse::responseCodeToString(int code) -{ +#else // ESP8266 +const __FlashStringHelper* AsyncWebServerResponse::responseCodeToString(int code) { switch (code) { case 100: return FPSTR(T_HTTP_CODE_100); @@ -230,12 +229,12 @@ void AsyncWebServerResponse::setCode(int code) { } void AsyncWebServerResponse::setContentLength(size_t len) { - if (_state == RESPONSE_SETUP) + if (_state == RESPONSE_SETUP && addHeader(T_Content_Length, len, true)) _contentLength = len; } void AsyncWebServerResponse::setContentType(const char* type) { - if (_state == RESPONSE_SETUP) + if (_state == RESPONSE_SETUP && addHeader(T_Content_Type, type, true)) _contentType = type; } @@ -249,6 +248,11 @@ bool AsyncWebServerResponse::removeHeader(const char* name) { 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::addHeader(const char* name, const char* value, bool replaceExisting) { for (auto i = _headers.begin(); i != _headers.end(); ++i) { if (i->name().equalsIgnoreCase(name)) { @@ -268,41 +272,56 @@ bool AsyncWebServerResponse::addHeader(const char* name, const char* value, bool return true; } -String AsyncWebServerResponse::_assembleHead(uint8_t version) { +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); } - String out; - constexpr size_t bufSize = 300; - char buf[bufSize]; -#ifndef ESP8266 - snprintf(buf, bufSize, "HTTP/1.%d %d %s\r\n", version, _code, responseCodeToString(_code)); + 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 \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 - snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, String(responseCodeToString(_code)).c_str()); + buffer.concat("HTTP/1."); #endif - out.concat(buf); - - if (_sendContentLength) { - snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength); - out.concat(buf); - } - if (_contentType.length()) { - snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str()); - out.concat(buf); - } + 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) { - snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header.name().c_str(), header.value().c_str()); - out.concat(buf); + buffer.concat(header.name()); +#ifdef ESP8266 + buffer.concat(PSTR(": ")); +#else + buffer.concat(": "); +#endif + buffer.concat(header.value()); + buffer.concat(T_rn); } _headers.clear(); - out.concat(T_rn); - _headLength = out.length(); - return out; + buffer.concat(T_rn); + _headLength = buffer.length(); } bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; } @@ -337,7 +356,8 @@ AsyncBasicResponse::AsyncBasicResponse(int code, const char* contentType, const void AsyncBasicResponse::_respond(AsyncWebServerRequest* request) { _state = RESPONSE_HEADERS; - String out = _assembleHead(request->version()); + String out; + _assembleHead(out, request->version()); size_t outLen = out.length(); size_t space = request->client()->space(); if (!_contentLength && space >= outLen) { @@ -411,7 +431,7 @@ AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) : _c void AsyncAbstractResponse::_respond(AsyncWebServerRequest* request) { addHeader(T_Connection, T_close, false); - _head = _assembleHead(request->version()); + _assembleHead(_head, request->version()); _state = RESPONSE_HEADERS; _ack(request, 0, 0); } @@ -472,9 +492,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u free(buf); return 0; } - outLen = sprintf_P((char*)buf + headLen, PSTR("%x"), readLen) + headLen; - while (outLen < headLen + 4) - buf[outLen++] = ' '; + outLen = sprintf((char*)buf+headLen, "%04x", readLen) + headLen; buf[outLen++] = '\r'; buf[outLen++] = '\n'; outLen += readLen; @@ -635,9 +653,9 @@ AsyncFileResponse::~AsyncFileResponse() { void AsyncFileResponse::_setContentTypeFromPath(const String& path) { #if HAVE_EXTERN_GET_Content_Type_FUNCTION #ifndef ESP8266 - extern const char* getContentType(const String& path); + extern const char* getContentType(const String& path); #else - extern const __FlashStringHelper* getContentType(const String& path); + extern const __FlashStringHelper* getContentType(const String& path); #endif _contentType = getContentType(path); #else