diff --git a/.github/ISSUE_TEMPLATE/issue-report.yml b/.github/ISSUE_TEMPLATE/issue-report.yml index d48e5ba..854897b 100644 --- a/.github/ISSUE_TEMPLATE/issue-report.yml +++ b/.github/ISSUE_TEMPLATE/issue-report.yml @@ -60,7 +60,7 @@ body: id: stack-trace attributes: label: Stack Trace - description: Please provide a debug message or error message. If you have a Guru Meditation Error or Backtrace, [please decode it](https://maximeborges.github.io/esp-stacktrace-decoder/). + description: Please provide a debug message or error message. If you have a Guru Meditation Error or Backtrace, [please decode it](https://esphome.github.io/esp-stacktrace-decoder/). placeholder: For Arduino IDE, Enable Core debug level - Debug on tools menu, then put the serial output here. validations: required: true diff --git a/.github/workflows/arduino-lint.yml b/.github/workflows/arduino-lint.yml index b8906ae..3f8d62f 100644 --- a/.github/workflows/arduino-lint.yml +++ b/.github/workflows/arduino-lint.yml @@ -27,7 +27,7 @@ jobs: name: Arduino Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: arduino/arduino-lint-action@v2 with: library-manager: update diff --git a/.github/workflows/build-esp32.yml b/.github/workflows/build-esp32.yml index e5482ff..c320dec 100644 --- a/.github/workflows/build-esp32.yml +++ b/.github/workflows/build-esp32.yml @@ -43,7 +43,7 @@ jobs: run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/ESP32Async/AsyncTCP#v3.4.10 - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Build Examples run: | @@ -74,7 +74,7 @@ jobs: run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/ESP32Async/AsyncTCP#v3.4.10 - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Build Examples run: | @@ -99,7 +99,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Python uses: actions/setup-python@v6 with: @@ -139,7 +139,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Python uses: actions/setup-python@v6 with: @@ -178,7 +178,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Python uses: actions/setup-python@v6 with: diff --git a/.github/workflows/build-esp8266.yml b/.github/workflows/build-esp8266.yml index c270abb..6190a77 100644 --- a/.github/workflows/build-esp8266.yml +++ b/.github/workflows/build-esp8266.yml @@ -43,7 +43,7 @@ jobs: run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/ESP32Async/ESPAsyncTCP#v2.0.0 - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Build Examples run: | @@ -66,7 +66,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Python uses: actions/setup-python@v6 with: diff --git a/.github/workflows/build-libretiny.yml b/.github/workflows/build-libretiny.yml index 6b1f63d..6745ad4 100644 --- a/.github/workflows/build-libretiny.yml +++ b/.github/workflows/build-libretiny.yml @@ -35,7 +35,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Python uses: actions/setup-python@v6 with: diff --git a/.github/workflows/build-rpi.yml b/.github/workflows/build-rpi.yml index 6eafb32..7f2bb1f 100644 --- a/.github/workflows/build-rpi.yml +++ b/.github/workflows/build-rpi.yml @@ -50,7 +50,7 @@ jobs: run: ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=true arduino-cli lib install --git-url https://github.com/ayushsharma82/RPAsyncTCP#v1.3.2 - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Build Examples run: | @@ -73,7 +73,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Python uses: actions/setup-python@v6 with: diff --git a/.github/workflows/cpplint.yml b/.github/workflows/cpplint.yml index d8f6c3c..12bf175 100644 --- a/.github/workflows/cpplint.yml +++ b/.github/workflows/cpplint.yml @@ -28,10 +28,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: key: ${{ runner.os }}-cpplint path: ~/.cache/pip diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index bf82de7..d99c323 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout latest commit - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 2 diff --git a/.github/workflows/publish-pio-registry.yml b/.github/workflows/publish-pio-registry.yml index c0591ee..0ab41f9 100644 --- a/.github/workflows/publish-pio-registry.yml +++ b/.github/workflows/publish-pio-registry.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-tags: true fetch-depth: 0 # Ensure all commits and tags are fetched @@ -39,7 +39,7 @@ jobs: curl -L -o project.zip "https://github.com/${{ github.repository }}/archive/refs/tags/$TAG_NAME.zip" # - name: Cache PlatformIO - # uses: actions/cache@v4 + # uses: actions/cache@v5 # with: # key: ${{ runner.os }}-pio # path: | diff --git a/.github/workflows/upload-idf-component.yml b/.github/workflows/upload-idf-component.yml index 2ab44b4..2a2adbe 100644 --- a/.github/workflows/upload-idf-component.yml +++ b/.github/workflows/upload-idf-component.yml @@ -36,7 +36,7 @@ jobs: echo "Tag: $tag" echo "RELEASE_TAG=$tag" >> $GITHUB_ENV - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: ref: ${{ inputs.git_ref || env.RELEASE_TAG }} submodules: "recursive" diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 4ef1d3b..7a29cb3 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -12,8 +12,8 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: 3.x - run: pip install mkdocs-material diff --git a/docs/backup/wiki.md b/docs/backup/wiki.md index 3cbcc80..881cb38 100644 --- a/docs/backup/wiki.md +++ b/docs/backup/wiki.md @@ -1887,7 +1887,7 @@ void setup(){ }, onUpload); // send a file when /index is requested - server.on("/index", HTTP_ANY, [](AsyncWebServerRequest *request){ + server.on("/index", HTTP_ALL, [](AsyncWebServerRequest *request){ request->send(SPIFFS, "/index.htm"); }); @@ -1974,10 +1974,10 @@ public : void begin(){ // attach global request handler - classWebServer.on("/example", HTTP_ANY, handleRequest); + classWebServer.on("/example", HTTP_ALL, handleRequest); // attach class request handler - classWebServer.on("/example", HTTP_ANY, std::bind(&WebClass::classRequest, this, std::placeholders::_1)); + classWebServer.on("/example", HTTP_ALL, std::bind(&WebClass::classRequest, this, std::placeholders::_1)); } }; @@ -1986,10 +1986,10 @@ WebClass webClassInstance; void setup() { // attach global request handler - globalWebServer.on("/example", HTTP_ANY, handleRequest); + globalWebServer.on("/example", HTTP_ALL, handleRequest); // attach class request handler - globalWebServer.on("/example", HTTP_ANY, std::bind(&WebClass::classRequest, webClassInstance, std::placeholders::_1)); + globalWebServer.on("/example", HTTP_ALL, std::bind(&WebClass::classRequest, webClassInstance, std::placeholders::_1)); } void loop() { diff --git a/docs/setup.md b/docs/setup.md index e0b0925..4038db1 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -71,12 +71,12 @@ void setup(){ }, onUpload); // send a file when /index is requested (SPIFFS example) - server.on("/index", HTTP_ANY, [](AsyncWebServerRequest *request){ + server.on("/index", HTTP_ALL, [](AsyncWebServerRequest *request){ request->send(SPIFFS, "/index.htm"); }); // send a file when /index is requested (LittleFS example) - server.on("/index", HTTP_ANY, [](AsyncWebServerRequest *request){ + server.on("/index", HTTP_ALL, [](AsyncWebServerRequest *request){ request->send(LittleFS, "/index.htm"); }); @@ -161,10 +161,10 @@ public : void begin(){ // attach global request handler - classWebServer.on("/example", HTTP_ANY, handleRequest); + classWebServer.on("/example", HTTP_ALL, handleRequest); // attach class request handler - classWebServer.on("/example", HTTP_ANY, std::bind(&WebClass::classRequest, this, std::placeholders::_1)); + classWebServer.on("/example", HTTP_ALL, std::bind(&WebClass::classRequest, this, std::placeholders::_1)); } }; @@ -173,10 +173,10 @@ WebClass webClassInstance; void setup() { // attach global request handler - globalWebServer.on("/example", HTTP_ANY, handleRequest); + globalWebServer.on("/example", HTTP_ALL, handleRequest); // attach class request handler - globalWebServer.on("/example", HTTP_ANY, std::bind(&WebClass::classRequest, webClassInstance, std::placeholders::_1)); + globalWebServer.on("/example", HTTP_ALL, std::bind(&WebClass::classRequest, webClassInstance, std::placeholders::_1)); } void loop() { diff --git a/examples/HTTPMethodsWithArduino/HTTPMethodsWithArduino.ino b/examples/HTTPMethodsWithArduino/HTTPMethodsWithArduino.ino new file mode 100644 index 0000000..a833681 --- /dev/null +++ b/examples/HTTPMethodsWithArduino/HTTPMethodsWithArduino.ino @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles + +// +// HTTP Method usage example and check compatibility with Arduino HTTP Methods when HTTP_Method.h is included. +// ESP-IDf enums are imported, and HTTP_ANY is defined by Arduino Core. +// In that case, we cannot use directly asyncws enums: we have to namespace them. +// Also, asycnws HTTP_ANY is not available sine already defined by Arduino as a macro. +// So we have to use AsyncWebRequestMethod::HTTP_ALL instead of HTTP_ANY in that case. +// + +#include + +#if !defined(ESP8266) +#include +#endif + +#if defined(ESP32) || defined(LIBRETINY) +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +#if ASYNC_JSON_SUPPORT == 1 +// https://github.com/ESP32Async/ESPAsyncWebServer/issues/404 +static void handlePostTest(AsyncWebServerRequest *req, JsonVariant &json) { + AsyncWebServerResponse *response; + if (req->method() == WebRequestMethod::HTTP_POST) { + response = req->beginResponse(200, "application/json", "{\"msg\": \"OK\"}"); + } else { + response = req->beginResponse(501, "application/json", "{\"msg\": \"Not Implemented\"}"); + } + req->send(response); +} +#endif + +// user defined functions that turns out to have similar signatures to the ones in global header +int stringToMethod(const String &) { + return 0; +} +const char *methodToString(WebRequestMethod) { + return "METHOD"; +} +bool methodMatches(WebRequestMethodComposite c, WebRequestMethod m) { + return false; +}; + +static WebRequestMethodComposite allowed = AsyncWebRequestMethod::HTTP_HEAD | AsyncWebRequestMethod::HTTP_OPTIONS; + +class MyRequestHandler : public AsyncWebHandler { +public: + bool canHandle(__unused AsyncWebServerRequest *request) const override { + // Test backward compatibility with previous way of checking if a method is allowed in a composite using a bit operator + return allowed & request->method(); + } + + void handleRequest(AsyncWebServerRequest *request) override { + request->send(200, "text/plain", "Hello from custom handler"); + } +}; + +void setup() { + Serial.begin(115200); + +#if ASYNCWEBSERVER_WIFI_SUPPORTED + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // curl -v http://192.168.4.1/get-or-post + // curl -v -X POST -d "a=b" http://192.168.4.1/get-or-post + server.on("/get-or-post", AsyncWebRequestMethod::HTTP_GET | AsyncWebRequestMethod::HTTP_POST, [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello"); + }); + + // curl -v http://192.168.4.1/all + server.on("/all", AsyncWebRequestMethod::HTTP_ALL, [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello"); + }); + +#if ASYNC_JSON_SUPPORT == 1 + // curl -v http://192.168.4.1/test => Not Implemented + // curl -v -X POST -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/test => OK + AsyncCallbackJsonWebHandler *testHandler = new AsyncCallbackJsonWebHandler("/test", handlePostTest); + server.addHandler(testHandler); +#endif + + // curl -v -X HEAD http://192.168.4.1/custom => answers + // curl -v -X OPTIONS http://192.168.4.1/custom => answers + // curl -v -X POST http://192.168.4.1/custom => no answer + server.addHandler(new MyRequestHandler()); + + server.begin(); + + WebRequestMethodComposite composite1 = AsyncWebRequestMethod::HTTP_GET | AsyncWebRequestMethod::HTTP_POST; + WebRequestMethodComposite composite2 = AsyncWebRequestMethod::HTTP_GET | AsyncWebRequestMethod::HTTP_POST; + WebRequestMethodComposite composite3 = AsyncWebRequestMethod::HTTP_HEAD | AsyncWebRequestMethod::HTTP_OPTIONS; + WebRequestMethodComposite composite4 = composite3; + WebRequestMethodComposite composite5 = AsyncWebRequestMethod::HTTP_GET; + + assert(composite1.matches(AsyncWebRequestMethod::HTTP_GET)); + assert(composite1.matches(AsyncWebRequestMethod::HTTP_POST)); + assert(!composite1.matches(AsyncWebRequestMethod::HTTP_HEAD)); + assert(!composite3.matches(AsyncWebRequestMethod::HTTP_GET)); + + assert(composite1 == composite2); + assert(composite3 == composite4); + assert(composite1 != composite3); + assert(composite5 == AsyncWebRequestMethod::HTTP_GET); +} + +// not needed +void loop() { + delay(100); +} diff --git a/examples/HTTPMethodsWithESPIDF/HTTPMethodsWithESPIDF.ino b/examples/HTTPMethodsWithESPIDF/HTTPMethodsWithESPIDF.ino new file mode 100644 index 0000000..c2e7cbc --- /dev/null +++ b/examples/HTTPMethodsWithESPIDF/HTTPMethodsWithESPIDF.ino @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright 2016-2026 Hristo Gochkov, Mathieu Carbou, Emil Muratov, Will Miles + +// +// HTTP Method usage example and check compatibility with ESP-IDF HTTP Methods when http_parser.h is included. +// ESP-IDF enums are imported, and HTTP_ANY is NOT defined by ESP-IDF. +// So asyncws is able to define it. +// We cannot use directly other asyncws enums to avoid conflicts with ESP-IDF: we have to namespace them. +// + +#include + +#if !defined(ESP8266) +#include "http_parser.h" +#endif + +#if defined(ESP32) || defined(LIBRETINY) +#include +#include +#elif defined(ESP8266) +#include +#include +#elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) +#include +#include +#endif + +#include + +static AsyncWebServer server(80); + +#if ASYNC_JSON_SUPPORT == 1 +// https://github.com/ESP32Async/ESPAsyncWebServer/issues/404 +static void handlePostTest(AsyncWebServerRequest *req, JsonVariant &json) { + AsyncWebServerResponse *response; + if (req->method() == WebRequestMethod::HTTP_POST) { + response = req->beginResponse(200, "application/json", "{\"msg\": \"OK\"}"); + } else { + response = req->beginResponse(501, "application/json", "{\"msg\": \"Not Implemented\"}"); + } + req->send(response); +} +#endif + +// user defined functions that turns out to have similar signatures to the ones in global header +int stringToMethod(const String &) { + return 0; +} +const char *methodToString(WebRequestMethod) { + return "METHOD"; +} +bool methodMatches(WebRequestMethodComposite c, WebRequestMethod m) { + return false; +}; + +static WebRequestMethodComposite allowed = AsyncWebRequestMethod::HTTP_HEAD | AsyncWebRequestMethod::HTTP_OPTIONS; + +class MyRequestHandler : public AsyncWebHandler { +public: + bool canHandle(__unused AsyncWebServerRequest *request) const override { + // Test backward compatibility with previous way of checking if a method is allowed in a composite using a bit operator + return allowed & request->method(); + } + + void handleRequest(AsyncWebServerRequest *request) override { + request->send(200, "text/plain", "Hello from custom handler"); + } +}; + +void setup() { + Serial.begin(115200); + +#if ASYNCWEBSERVER_WIFI_SUPPORTED + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); +#endif + + // curl -v http://192.168.4.1/get-or-post + // curl -v -X POST -d "a=b" http://192.168.4.1/get-or-post + server.on("/get-or-post", AsyncWebRequestMethod::HTTP_GET | AsyncWebRequestMethod::HTTP_POST, [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello"); + }); + + // curl -v http://192.168.4.1/all + server.on("/all", AsyncWebRequestMethod::HTTP_ALL, [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello"); + }); + + server.on("/any", AsyncWebRequestMethod::HTTP_ANY, [](AsyncWebServerRequest *request) { + request->send(200, "text/plain", "Hello"); + }); + +#if ASYNC_JSON_SUPPORT == 1 + // curl -v http://192.168.4.1/test => Not Implemented + // curl -v -X POST -H 'Content-Type: application/json' -d '{"name":"You"}' http://192.168.4.1/test => OK + AsyncCallbackJsonWebHandler *testHandler = new AsyncCallbackJsonWebHandler("/test", handlePostTest); + server.addHandler(testHandler); +#endif + + // curl -v -X HEAD http://192.168.4.1/custom => answers + // curl -v -X OPTIONS http://192.168.4.1/custom => answers + // curl -v -X POST http://192.168.4.1/custom => no answer + server.addHandler(new MyRequestHandler()); + + server.begin(); + + WebRequestMethodComposite composite1 = AsyncWebRequestMethod::HTTP_GET | AsyncWebRequestMethod::HTTP_POST; + WebRequestMethodComposite composite2 = AsyncWebRequestMethod::HTTP_GET | AsyncWebRequestMethod::HTTP_POST; + WebRequestMethodComposite composite3 = AsyncWebRequestMethod::HTTP_HEAD | AsyncWebRequestMethod::HTTP_OPTIONS; + WebRequestMethodComposite composite4 = composite3; + WebRequestMethodComposite composite5 = AsyncWebRequestMethod::HTTP_GET; + + assert(composite1.matches(AsyncWebRequestMethod::HTTP_GET)); + assert(composite1.matches(AsyncWebRequestMethod::HTTP_POST)); + assert(!composite1.matches(AsyncWebRequestMethod::HTTP_HEAD)); + assert(!composite3.matches(AsyncWebRequestMethod::HTTP_GET)); + + assert(composite1 == composite2); + assert(composite3 == composite4); + assert(composite1 != composite3); + assert(composite5 == AsyncWebRequestMethod::HTTP_GET); +} + +// not needed +void loop() { + delay(100); +} diff --git a/examples/WebSocket/WebSocket.ino b/examples/WebSocket/WebSocket.ino index 248593e..ae65679 100644 --- a/examples/WebSocket/WebSocket.ino +++ b/examples/WebSocket/WebSocket.ino @@ -118,6 +118,8 @@ void setup() { if (info->message_opcode == WS_TEXT) { Serial.printf("ws text: %s\n", (char *)data); client->ping(); + // Also send a message in the message queue when we get one + ws.textAll("Message received: " + String((char *)data)); } } else { diff --git a/idf_component.yml b/idf_component.yml index 77c4bc1..6519745 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -32,7 +32,7 @@ dependencies: version: "^3.4.10" require: public bblanchon/arduinojson: - version: "^7.4.2" + version: "^7.4.3" require: public examples: - path: ./idf_component_examples/catchall diff --git a/library.json b/library.json index d0cb14c..eee98dc 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "ESPAsyncWebServer", - "version": "3.10.0", + "version": "3.10.3", "description": "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.", "keywords": "http,async,websocket,webserver", "homepage": "https://github.com/ESP32Async/ESPAsyncWebServer", diff --git a/library.properties b/library.properties index 6026769..a6ca4f0 100644 --- a/library.properties +++ b/library.properties @@ -1,6 +1,6 @@ name=ESP Async WebServer includes=ESPAsyncWebServer.h -version=3.10.0 +version=3.10.3 author=ESP32Async maintainer=ESP32Async sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040 diff --git a/pioarduino_examples/IncreaseMaxSockets/platformio.ini b/pioarduino_examples/IncreaseMaxSockets/platformio.ini index ee89878..75ca8b6 100644 --- a/pioarduino_examples/IncreaseMaxSockets/platformio.ini +++ b/pioarduino_examples/IncreaseMaxSockets/platformio.ini @@ -18,7 +18,7 @@ lib_compat_mode = strict lib_ldf_mode = chain lib_deps = ESP32Async/AsyncTCP @ 3.4.10 - ESP32Async/ESpAsyncWebServer @ 3.9.6 + ESP32Async/ESpAsyncWebServer @ 3.10.2 custom_component_remove = espressif/esp_hosted espressif/esp_wifi_remote diff --git a/platformio.ini b/platformio.ini index 17d46c9..7320582 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,6 +14,9 @@ lib_dir = . ; src_dir = examples/Filters ; src_dir = examples/FlashResponse ; src_dir = examples/HeaderManipulation +; src_dir = examples/Headers +; src_dir = examples/HTTPMethodsWithArduino +; src_dir = examples/HTTPMethodsWithESPIDF ; src_dir = examples/Json ; src_dir = examples/LargeResponse ; src_dir = examples/Logging @@ -38,9 +41,9 @@ src_dir = examples/PerfTests ; src_dir = examples/UploadFlash ; src_dir = examples/URIMatcher ; src_dir = examples/URIMatcherTest +; src_dir = examples/WebDAVMethods ; src_dir = examples/WebSocket ; src_dir = examples/WebSocketEasy -; src_dir = examples/WebDAVMethods [env] framework = arduino @@ -72,7 +75,7 @@ monitor_filters = esp32_exception_decoder, log2file lib_compat_mode = strict lib_ldf_mode = chain lib_deps = - bblanchon/ArduinoJson @ 7.4.2 + bblanchon/ArduinoJson @ 7.4.3 ; bblanchon/ArduinoJson @ 6.21.5 ; bblanchon/ArduinoJson @ 5.13.4 ESP32Async/AsyncTCP @ 3.4.10 @@ -93,7 +96,7 @@ platform = espressif8266 ; board = huzzah board = d1_mini lib_deps = - bblanchon/ArduinoJson @ 7.4.2 + bblanchon/ArduinoJson @ 7.4.3 ESP32Async/ESPAsyncTCP @ 2.0.0 [env:raspberrypi] @@ -167,7 +170,7 @@ board = ${sysenv.PIO_BOARD} platform = espressif8266 board = ${sysenv.PIO_BOARD} lib_deps = - bblanchon/ArduinoJson @ 7.4.2 + bblanchon/ArduinoJson @ 7.4.3 ESP32Async/ESPAsyncTCP @ 2.0.0 [env:ci-raspberrypi] diff --git a/src/AsyncEventSource.cpp b/src/AsyncEventSource.cpp index d0f3473..60fd927 100644 --- a/src/AsyncEventSource.cpp +++ b/src/AsyncEventSource.cpp @@ -192,6 +192,7 @@ AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, A AsyncEventSourceClient::~AsyncEventSourceClient() { #ifdef ESP32 + // Protect message queue access (size checks and modifications) which is not thread-safe. std::lock_guard lock(_lockmq); #endif _messageQueue.clear(); @@ -199,16 +200,16 @@ AsyncEventSourceClient::~AsyncEventSourceClient() { } bool AsyncEventSourceClient::_queueMessage(const char *message, size_t len) { - if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) { - async_ws_log_e("Event message queue overflow: discard message"); - return false; - } - #ifdef ESP32 - // length() is not thread-safe, thus acquiring the lock before this call.. + // Protect message queue access (size checks and modifications) which is not thread-safe. std::lock_guard lock(_lockmq); #endif + if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) { + async_ws_log_w("Event message queue overflow: discard message"); + return false; + } + if (_client) { _messageQueue.emplace_back(message, len); } else { @@ -230,16 +231,16 @@ bool AsyncEventSourceClient::_queueMessage(const char *message, size_t len) { } bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) { - if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) { - async_ws_log_e("Event message queue overflow: discard message"); - return false; - } - #ifdef ESP32 - // length() is not thread-safe, thus acquiring the lock before this call.. + // Protect message queue access (size checks and modifications) which is not thread-safe. std::lock_guard lock(_lockmq); #endif + if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) { + async_ws_log_w("Event message queue overflow: discard message"); + return false; + } + if (_client) { _messageQueue.emplace_back(std::move(msg)); } else { @@ -261,7 +262,7 @@ bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) { void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))) { #ifdef ESP32 - // Same here, acquiring the lock early + // Protect message queue access (size checks and modifications) which is not thread-safe. std::lock_guard lock(_lockmq); #endif @@ -288,11 +289,11 @@ void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t } void AsyncEventSourceClient::_onPoll() { - if (_messageQueue.size()) { #ifdef ESP32 - // Same here, acquiring the lock early - std::lock_guard lock(_lockmq); + // Protect message queue access (size checks and modifications) which is not thread-safe. + std::lock_guard lock(_lockmq); #endif + if (_messageQueue.size()) { _runQueue(); } } @@ -367,14 +368,17 @@ void AsyncEventSource::_addClient(AsyncEventSourceClient *client) { if (!client) { return; } -#ifdef ESP32 - std::lock_guard lock(_client_queue_lock); -#endif - _clients.emplace_back(client); + if (_connectcb) { _connectcb(client); } +#ifdef ESP32 + std::lock_guard lock(_client_queue_lock); +#endif + + _clients.emplace_back(client); + _adjust_inflight_window(); } diff --git a/src/AsyncEventSource.h b/src/AsyncEventSource.h index 69c755f..4282fff 100644 --- a/src/AsyncEventSource.h +++ b/src/AsyncEventSource.h @@ -205,6 +205,9 @@ public: return _lastId; } size_t packetsWaiting() const { +#ifdef ESP32 + std::lock_guard lock(_lockmq); +#endif return _messageQueue.size(); }; diff --git a/src/AsyncJson.cpp b/src/AsyncJson.cpp index 90db6ce..4cdbfb3 100644 --- a/src/AsyncJson.cpp +++ b/src/AsyncJson.cpp @@ -112,18 +112,23 @@ size_t AsyncMessagePackResponse::_fillBuffer(uint8_t *data, size_t len) { #endif // Body handler supporting both content types: JSON and MessagePack +constexpr static WebRequestMethodComposite JsonHandlerMethods = + AsyncWebRequestMethod::HTTP_GET | AsyncWebRequestMethod::HTTP_POST | AsyncWebRequestMethod::HTTP_PUT | AsyncWebRequestMethod::HTTP_PATCH; #if ARDUINOJSON_VERSION_MAJOR == 6 AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(AsyncURIMatcher uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize) - : _uri(std::move(uri)), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), - _maxContentLength(16384) {} + : _uri(std::move(uri)), + _method(AsyncWebRequestMethod::HTTP_GET | AsyncWebRequestMethod::HTTP_POST | AsyncWebRequestMethod::HTTP_PUT | AsyncWebRequestMethod::HTTP_PATCH), + _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {} #else AsyncCallbackJsonWebHandler::AsyncCallbackJsonWebHandler(AsyncURIMatcher uri, ArJsonRequestHandlerFunction onRequest) - : _uri(std::move(uri)), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {} + : _uri(std::move(uri)), + _method(AsyncWebRequestMethod::HTTP_GET | AsyncWebRequestMethod::HTTP_POST | AsyncWebRequestMethod::HTTP_PUT | AsyncWebRequestMethod::HTTP_PATCH), + _onRequest(onRequest), _maxContentLength(16384) {} #endif bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) const { - if (!_onRequest || !request->isHTTP() || !(_method & request->method())) { + if (!_onRequest || !request->isHTTP() || !_method.matches(request->method())) { return false; } @@ -132,17 +137,17 @@ bool AsyncCallbackJsonWebHandler::canHandle(AsyncWebServerRequest *request) cons } #if ASYNC_MSG_PACK_SUPPORT == 1 - return request->method() == HTTP_GET || request->contentType().equalsIgnoreCase(asyncsrv::T_application_json) + return request->method() == AsyncWebRequestMethod::HTTP_GET || request->contentType().equalsIgnoreCase(asyncsrv::T_application_json) || request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack); #else - return request->method() == HTTP_GET || request->contentType().equalsIgnoreCase(asyncsrv::T_application_json); + return request->method() == AsyncWebRequestMethod::HTTP_GET || request->contentType().equalsIgnoreCase(asyncsrv::T_application_json); #endif } void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) { if (_onRequest) { // GET request: - if (request->method() == HTTP_GET) { + if (request->method() == AsyncWebRequestMethod::HTTP_GET) { JsonVariant json; _onRequest(request, json); return; @@ -151,7 +156,7 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) // POST / PUT / ... requests: // check if JSON body is too large, if it is, don't deserialize if (request->contentLength() > _maxContentLength) { - async_ws_log_e("Content length exceeds maximum allowed"); + async_ws_log_w("Content length exceeds maximum allowed"); request->send(413); return; } @@ -206,7 +211,7 @@ void AsyncCallbackJsonWebHandler::handleBody(AsyncWebServerRequest *request, uin // no way to know the actual length in advance. The best // way to handle this would be to use a String instead of // a fixed-length buffer, but for now we just reject. - async_ws_log_e("AsyncJson cannot handle chunked requests without X-Expected-Entity-Length"); + async_ws_log_w("AsyncJson cannot handle chunked requests without X-Expected-Entity-Length"); request->abort(); return; } diff --git a/src/AsyncJson.h b/src/AsyncJson.h index e610d18..423e019 100644 --- a/src/AsyncJson.h +++ b/src/AsyncJson.h @@ -6,6 +6,8 @@ #include #include "ChunkPrint.h" +#include + #if ASYNC_JSON_SUPPORT == 1 #if ARDUINOJSON_VERSION_MAJOR == 6 @@ -104,7 +106,7 @@ public: #endif void setMethod(WebRequestMethodComposite method) { - _method = method; + _method = std::move(method); } void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; diff --git a/src/AsyncWebServerVersion.h b/src/AsyncWebServerVersion.h index 91bc6ce..1e90fc1 100644 --- a/src/AsyncWebServerVersion.h +++ b/src/AsyncWebServerVersion.h @@ -12,7 +12,7 @@ extern "C" { /** Minor version number (x.X.x) */ #define ASYNCWEBSERVER_VERSION_MINOR 10 /** Patch version number (x.x.X) */ -#define ASYNCWEBSERVER_VERSION_PATCH 0 +#define ASYNCWEBSERVER_VERSION_PATCH 3 /** * Macro to convert version number into an integer diff --git a/src/AsyncWebSocket.cpp b/src/AsyncWebSocket.cpp index 49abfe6..1f6adee 100644 --- a/src/AsyncWebSocket.cpp +++ b/src/AsyncWebSocket.cpp @@ -171,8 +171,9 @@ size_t AsyncWebSocketMessage::ack(size_t len, uint32_t time) { if (_sent >= _WSbuffer->size() && _acked >= _ack) { _status = WS_MSG_SENT; } - async_ws_log_v("opcode: %" PRIu8 ", acked: %u/%u, left: %u/%u, status: %d", _opcode, _acked, _ack, len - pending, len, static_cast(_status)); - return len - pending; + const size_t remaining = len - pending; + async_ws_log_v("ACK[%" PRIu8 "] %u/%u (acked: %u/%u) => %" PRIu8, _opcode, _sent, _WSbuffer->size(), _acked, _ack, static_cast(_status)); + return remaining; } size_t AsyncWebSocketMessage::send(AsyncClient *client) { @@ -182,7 +183,7 @@ size_t AsyncWebSocketMessage::send(AsyncClient *client) { } if (_status != WS_MSG_SENDING) { - async_ws_log_v("C[%" PRIu16 "] Wrong status: got: %d, expected: %d", client->remotePort(), static_cast(_status), static_cast(WS_MSG_SENDING)); + async_ws_log_v("SEND[%" PRIu8 "] => [%" PRIu16 "] WS_MSG_SENDING != %" PRIu8, _opcode, client->remotePort(), static_cast(_status)); return 0; } @@ -190,12 +191,14 @@ size_t AsyncWebSocketMessage::send(AsyncClient *client) { if (_acked == _ack) { _status = WS_MSG_SENT; } - async_ws_log_v("C[%" PRIu16 "] Already sent: %u/%u", client->remotePort(), _sent, _WSbuffer->size()); + async_ws_log_v("SEND[%" PRIu8 "] => [%" PRIu16 "] WS_MSG_SENT %u/%u (acked: %u/%u)", _opcode, client->remotePort(), _sent, _WSbuffer->size(), _acked, _ack); return 0; } if (_sent > _WSbuffer->size()) { _status = WS_MSG_ERROR; - async_ws_log_v("C[%" PRIu16 "] Error, sent more: %u/%u", client->remotePort(), _sent, _WSbuffer->size()); + async_ws_log_v( + "SEND[%" PRIu8 "] => [%" PRIu16 "] WS_MSG_ERROR %u/%u (acked: %u/%u)", _opcode, client->remotePort(), _sent, _WSbuffer->size(), _acked, _ack + ); return 0; } @@ -204,7 +207,7 @@ size_t AsyncWebSocketMessage::send(AsyncClient *client) { // not enough space in lwip buffer ? if (!window) { - async_ws_log_v("C[%" PRIu16 "] No space left to send more data: acked: %u, sent: %u, remaining: %u", client->remotePort(), _acked, _sent, toSend); + async_ws_log_v("SEND[%" PRIu8 "] => [%" PRIu16 "] NO_SPACE %u", _opcode, client->remotePort(), toSend); return 0; } @@ -224,7 +227,9 @@ size_t AsyncWebSocketMessage::send(AsyncClient *client) { _ack -= (toSend - sent); } - async_ws_log_v("C[%" PRIu16 "] sent: %u/%u, final: %d, acked: %u/%u", client->remotePort(), _sent, _WSbuffer->size(), final, _acked, _ack); + async_ws_log_v( + "SEND[%" PRIu8 "] => [%" PRIu16 "] WS_MSG_SENDING %u/%u (acked: %u/%u)", _opcode, client->remotePort(), _sent, _WSbuffer->size(), _acked, _ack + ); return sent; } @@ -308,6 +313,8 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) { std::unique_lock lock(_lock); #endif + async_ws_log_v("[%s][%" PRIu32 "] START ACK(%u, %" PRIu32 ") Q:%u", _server->url(), _clientId, len, time, _messageQueue.size()); + if (!_controlQueue.empty()) { auto &head = _controlQueue.front(); if (head.finished()) { @@ -315,6 +322,7 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) { if (_status == WS_DISCONNECTING && head.opcode() == WS_DISCONNECT) { _controlQueue.pop_front(); _status = WS_DISCONNECTED; + async_ws_log_v("[%s][%" PRIu32 "] ACK WS_DISCONNECTED", _server->url(), _clientId); if (_client) { #ifdef ESP32 /* @@ -343,6 +351,8 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) { _clearQueue(); + async_ws_log_v("[%s][%" PRIu32 "] END ACK(%u, %" PRIu32 ") Q:%u", _server->url(), _clientId, len, time, _messageQueue.size()); + _runQueue(); } @@ -386,7 +396,7 @@ void AsyncWebSocketClient::_runQueue() { continue; } if (space > (size_t)(ctrl.len() - 1)) { - async_ws_log_v("WS[%" PRIu32 "] Sending control frame: %" PRIu8 ", len: %" PRIu8, _clientId, ctrl.opcode(), ctrl.len()); + async_ws_log_v("[%s][%" PRIu32 "] SEND CTRL %" PRIu8, _server->url(), _clientId, ctrl.opcode()); ctrl.send(_client); space = webSocketSendFrameWindow(_client); } @@ -398,9 +408,10 @@ void AsyncWebSocketClient::_runQueue() { for (auto &msg : _messageQueue) { if (msg._remainingBytesToSend()) { async_ws_log_v( - "WS[%" PRIu32 "] Send message fragment: %u/%u, acked: %u/%u", _clientId, msg._remainingBytesToSend(), msg._sent + msg._remainingBytesToSend(), - msg._acked, msg._ack + "[%s][%" PRIu32 "][%" PRIu8 "] SEND %u/%u (acked: %u/%u)", _server->url(), _clientId, msg._opcode, msg._sent, msg._WSbuffer->size(), msg._acked, + msg._ack ); + // will use all the remaining space, or all the remaining bytes to send, whichever is smaller msg.send(_client); space = webSocketSendFrameWindow(_client); @@ -408,12 +419,12 @@ void AsyncWebSocketClient::_runQueue() { // If we haven't finished sending this message, we must stop here to preserve WebSocket ordering. // We can only pipeline subsequent messages if the current one is fully passed to TCP buffer. if (msg._remainingBytesToSend()) { + async_ws_log_v("[%s][%" PRIu32 "][%" PRIu8 "] NO_SPACE", _server->url(), _clientId, msg._opcode); break; } - } - - // not enough space for another message - if (!space) { + } else if (!space) { + // not enough space for another message + async_ws_log_v("[%s][%" PRIu32 "] NO_SPACE", _server->url(), _clientId); break; } } @@ -452,6 +463,7 @@ bool AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t *data, si #endif _controlQueue.emplace_back(opcode, data, len, mask); + async_ws_log_v("[%s][%" PRIu32 "] QUEUE CTRL (%u) << %" PRIu8, _server->url(), _clientId, _controlQueue.size(), opcode); if (_client && _client->canSend()) { _runQueue(); @@ -485,16 +497,17 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint _client->close(); } - async_ws_log_e("Too many messages queued: closing connection"); + async_ws_log_w("[%s][%" PRIu32 "] Too many messages queued: closing connection", _server->url(), _clientId); } else { - async_ws_log_e("Too many messages queued: discarding new message"); + async_ws_log_w("[%s][%" PRIu32 "] Too many messages queued: discarding new message", _server->url(), _clientId); } return false; } _messageQueue.emplace_back(buffer, opcode, mask); + async_ws_log_v("[%s][%" PRIu32 "] QUEUE MSG (%u/%u) << %" PRIu8, _server->url(), _clientId, _messageQueue.size(), WS_MAX_QUEUED_MESSAGES, opcode); if (_client && _client->canSend()) { _runQueue(); @@ -508,6 +521,8 @@ void AsyncWebSocketClient::close(uint16_t code, const char *message) { return; } + async_ws_log_w("[%s][%" PRIu32 "] CLOSE", _server->url(), _clientId); + _status = WS_DISCONNECTING; if (code) { @@ -541,21 +556,20 @@ 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::_onError(int8_t err) { + async_ws_log_v("[%s][%" PRIu32 "] ERROR %" PRIi8, _server->url(), _clientId, static_cast(err)); } void AsyncWebSocketClient::_onTimeout(uint32_t time) { if (!_client) { return; } - // Serial.println("onTime"); - (void)time; + async_ws_log_v("[%s][%" PRIu32 "] TIMEOUT %" PRIu32, _server->url(), _clientId, time); _client->close(); } void AsyncWebSocketClient::_onDisconnect() { - // Serial.println("onDis"); + async_ws_log_v("[%s][%" PRIu32 "] DISCONNECT", _server->url(), _clientId); _client = nullptr; _server->_handleDisconnect(this); } @@ -566,7 +580,7 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) { while (plen > 0) { async_ws_log_v( - "WS[%" PRIu32 "] _onData: plen: %" PRIu32 ", _pstate: %" PRIu8 ", _status: %" PRIu8, _clientId, plen, _pstate, static_cast(_status) + "[%s][%" PRIu32 "] DATA plen: %" PRIu32 ", _pstate: %" PRIu8 ", _status: %" PRIu8, _server->url(), _clientId, plen, _pstate, static_cast(_status) ); if (_pstate == STATE_FRAME_START) { @@ -595,8 +609,8 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) { } async_ws_log_v( - "WS[%" PRIu32 "] _pinfo: index: %" PRIu64 ", final: %" PRIu8 ", opcode: %" PRIu8 ", masked: %" PRIu8 ", len: %" PRIu64, _clientId, _pinfo.index, - _pinfo.final, _pinfo.opcode, _pinfo.masked, _pinfo.len + "[%s][%" PRIu32 "] DATA _pinfo: index: %" PRIu64 ", final: %" PRIu8 ", opcode: %" PRIu8 ", masked: %" PRIu8 ", len: %" PRIu64, _server->url(), _clientId, + _pinfo.index, _pinfo.final, _pinfo.opcode, _pinfo.masked, _pinfo.len ); // Handle fragmented mask data - Safari may split the 4-byte mask across multiple packets @@ -608,7 +622,7 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) { if (plen == 0) { // Safari close frame edge case: masked bit set but no mask data if (_pinfo.opcode == WS_DISCONNECT) { - async_ws_log_v("WS[%" PRIu32 "] close frame with incomplete mask, treating as unmasked", _clientId); + async_ws_log_v("[%s][%" PRIu32 "] DATA close frame with incomplete mask, treating as unmasked", _server->url(), _clientId); _pinfo.masked = 0; _pinfo.index = 0; _pinfo.len = 0; @@ -616,9 +630,9 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) { break; } - //wait for more data + // wait for more data _pstate = STATE_FRAME_MASK; - async_ws_log_v("WS[%" PRIu32 "] waiting for more mask data: read: %" PRIu8 "/4", _clientId, _pinfo.masked - 1); + async_ws_log_v("[%s][%" PRIu32 "] DATA waiting for more mask data: read: %" PRIu8 "/4", _server->url(), _clientId, _pinfo.masked - 1); return; } @@ -634,7 +648,7 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) { // restore masked to 1 for backward compatibility if (_pinfo.masked >= 5) { - async_ws_log_v("WS[%" PRIu32 "] mask read complete", _clientId); + async_ws_log_v("[%s][%" PRIu32 "] DATA mask read complete", _server->url(), _clientId); _pinfo.masked = 1; } @@ -663,7 +677,7 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) { if (datalen > 0) { async_ws_log_v( - "WS[%" PRIu32 "] processing next fragment of %s frame %" PRIu32 ", index: %" PRIu64 ", len: %" PRIu32 "", _clientId, + "[%s][%" PRIu32 "] DATA processing next fragment of %s frame %" PRIu32 ", index: %" PRIu64 ", len: %" PRIu32 "", _server->url(), _clientId, (_pinfo.message_opcode == WS_TEXT) ? "text" : "binary", _pinfo.num, _pinfo.index, (uint32_t)datalen ); _handleDataEvent(data, datalen, datalen == plen); // datalen == plen means that we are processing the last part of the current TCP packet @@ -676,7 +690,7 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) { _pstate = STATE_FRAME_START; if (_pinfo.opcode == WS_DISCONNECT) { - async_ws_log_v("WS[%" PRIu32 "] processing disconnect", _clientId); + async_ws_log_v("[%s][%" PRIu32 "] DATA WS_DISCONNECT", _server->url(), _clientId); if (datalen) { uint16_t reasonCode = (uint16_t)(data[0] << 8) + data[1]; @@ -699,19 +713,19 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) { } } else if (_pinfo.opcode == WS_PING) { - async_ws_log_v("WS[%" PRIu32 "] processing ping", _clientId); + async_ws_log_v("[%s][%" PRIu32 "] DATA PING", _server->url(), _clientId); _server->_handleEvent(this, WS_EVT_PING, NULL, NULL, 0); _queueControl(WS_PONG, data, datalen); } else if (_pinfo.opcode == WS_PONG) { - async_ws_log_v("WS[%" PRIu32 "] processing pong", _clientId); + async_ws_log_v("[%s][%" PRIu32 "] DATA PONG", _server->url(), _clientId); 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 async_ws_log_v( - "WS[%" PRIu32 "] processing final fragment of %s frame %" PRIu32 ", index: %" PRIu64 ", len: %" PRIu32 "", _clientId, + "[%s][%" PRIu32 "] DATA processing final fragment of %s frame %" PRIu32 ", index: %" PRIu64 ", len: %" PRIu32 "", _server->url(), _clientId, (_pinfo.message_opcode == WS_TEXT) ? "text" : "binary", _pinfo.num, _pinfo.index, (uint32_t)datalen ); @@ -728,7 +742,9 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) { // unexpected frame error, close connection _pstate = STATE_FRAME_START; - async_ws_log_v("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len); + async_ws_log_v( + "[%s][%" PRIu32 "] DATA frame error: len: %u, index: %" PRIu64 ", total: %" PRIu64 "\n", _server->url(), _clientId, datalen, _pinfo.index, _pinfo.len + ); _status = WS_DISCONNECTING; if (_client) { @@ -972,6 +988,9 @@ void AsyncWebSocket::_handleEvent(AsyncWebSocketClient *client, AwsEventType typ } AsyncWebSocketClient *AsyncWebSocket::_newClient(AsyncWebServerRequest *request) { +#ifdef ESP32 + std::lock_guard lock(_lock); +#endif _clients.emplace_back(request, this); // we've just detached AsyncTCP client from AsyncWebServerRequest _handleEvent(&_clients.back(), WS_EVT_CONNECT, request, NULL, 0); @@ -981,6 +1000,9 @@ AsyncWebSocketClient *AsyncWebSocket::_newClient(AsyncWebServerRequest *request) } void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient *client) { +#ifdef ESP32 + std::lock_guard lock(_lock); +#endif 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; @@ -991,12 +1013,18 @@ void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient *client) { } bool AsyncWebSocket::availableForWriteAll() { +#ifdef ESP32 + std::lock_guard lock(_lock); +#endif return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) { return c.queueIsFull(); }); } bool AsyncWebSocket::availableForWrite(uint32_t id) { +#ifdef ESP32 + std::lock_guard lock(_lock); +#endif const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [id](const AsyncWebSocketClient &c) { return c.id() == id; }); @@ -1007,12 +1035,18 @@ bool AsyncWebSocket::availableForWrite(uint32_t id) { } size_t AsyncWebSocket::count() const { +#ifdef ESP32 + std::lock_guard lock(_lock); +#endif return std::count_if(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient &c) { return c.status() == WS_CONNECTED; }); } AsyncWebSocketClient *AsyncWebSocket::client(uint32_t id) { +#ifdef ESP32 + std::lock_guard lock(_lock); +#endif const auto iter = std::find_if(_clients.begin(), _clients.end(), [id](const AsyncWebSocketClient &c) { return c.id() == id && c.status() == WS_CONNECTED; }); @@ -1030,6 +1064,9 @@ void AsyncWebSocket::close(uint32_t id, uint16_t code, const char *message) { } void AsyncWebSocket::closeAll(uint16_t code, const char *message) { +#ifdef ESP32 + std::lock_guard lock(_lock); +#endif for (auto &c : _clients) { if (c.status() == WS_CONNECTED) { c.close(code, message); @@ -1038,7 +1075,12 @@ void AsyncWebSocket::closeAll(uint16_t code, const char *message) { } void AsyncWebSocket::cleanupClients(uint16_t maxClients) { - if (count() > maxClients) { +#ifdef ESP32 + std::lock_guard lock(_lock); +#endif + const size_t c = count(); + if (c > maxClients) { + async_ws_log_v("[%s] CLEANUP %" PRIu32 " (%u/%" PRIu16 ")", _url.c_str(), _clients.front().id(), c, maxClients); _clients.front().close(); } @@ -1056,6 +1098,9 @@ bool AsyncWebSocket::ping(uint32_t id, const uint8_t *data, size_t len) { } AsyncWebSocket::SendStatus AsyncWebSocket::pingAll(const uint8_t *data, size_t len) { +#ifdef ESP32 + std::lock_guard lock(_lock); +#endif size_t hit = 0; size_t miss = 0; for (auto &c : _clients) { @@ -1164,6 +1209,9 @@ AsyncWebSocket::SendStatus AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer * } AsyncWebSocket::SendStatus AsyncWebSocket::textAll(AsyncWebSocketSharedBuffer buffer) { +#ifdef ESP32 + std::lock_guard lock(_lock); +#endif size_t hit = 0; size_t miss = 0; for (auto &c : _clients) { @@ -1253,6 +1301,9 @@ AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer return status; } AsyncWebSocket::SendStatus AsyncWebSocket::binaryAll(AsyncWebSocketSharedBuffer buffer) { +#ifdef ESP32 + std::lock_guard lock(_lock); +#endif size_t hit = 0; size_t miss = 0; for (auto &c : _clients) { diff --git a/src/AsyncWebSocket.h b/src/AsyncWebSocket.h index 4461342..ea181c5 100644 --- a/src/AsyncWebSocket.h +++ b/src/AsyncWebSocket.h @@ -373,7 +373,7 @@ private: AwsHandshakeHandler _handshakeHandler; bool _enabled; #ifdef ESP32 - mutable std::mutex _lock; + mutable std::recursive_mutex _lock; #endif public: diff --git a/src/ESPAsyncWebServer.h b/src/ESPAsyncWebServer.h index de416f5..f7e222d 100644 --- a/src/ESPAsyncWebServer.h +++ b/src/ESPAsyncWebServer.h @@ -41,8 +41,6 @@ #include #elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) #include -#include -#include #else #error Platform not supported #endif @@ -68,6 +66,12 @@ #define ASYNCWEBSERVER_WIFI_SUPPORTED 0 #endif +// Enable integration with other HTTP libraries +#if defined(HTTP_ANY) || defined(http_parser_h) +#define ASYNCWEBSERVER_HTTP_METHOD_INTEGRATION +#define ASYNCWEBSERVER_NO_GLOBAL_HTTP_METHODS +#endif + class AsyncWebServer; class AsyncWebServerRequest; class AsyncWebServerResponse; @@ -80,29 +84,170 @@ 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 = 0b0000000000000001, - HTTP_POST = 0b0000000000000010, - HTTP_DELETE = 0b0000000000000100, - HTTP_PUT = 0b0000000000001000, - HTTP_PATCH = 0b0000000000010000, - HTTP_HEAD = 0b0000000000100000, - HTTP_OPTIONS = 0b0000000001000000, - HTTP_PROPFIND = 0b0000000010000000, - HTTP_LOCK = 0b0000000100000000, - HTTP_UNLOCK = 0b0000001000000000, - HTTP_PROPPATCH = 0b0000010000000000, - HTTP_MKCOL = 0b0000100000000000, - HTTP_MOVE = 0b0001000000000000, - HTTP_COPY = 0b0010000000000000, - HTTP_RESERVED = 0b0100000000000000, - HTTP_ANY = 0b0111111111111111, -} WebRequestMethod; +// Namespace for web request method defines +namespace AsyncWebRequestMethod { +// The long name here is because we sometimes include this in the global namespace +enum AsyncWebRequestMethodType : uint32_t { + HTTP_UNKNOWN = 0u, + + HTTP_DELETE = 1u << 0, + HTTP_GET = 1u << 1, + HTTP_HEAD = 1u << 2, + HTTP_POST = 1u << 3, + HTTP_PUT = 1u << 4, + + /* pathological */ + HTTP_CONNECT = 1u << 5, + HTTP_OPTIONS = 1u << 6, + HTTP_TRACE = 1u << 7, + + /* WebDAV */ + HTTP_COPY = 1u << 8, + HTTP_LOCK = 1u << 9, + HTTP_MKCOL = 1u << 10, + HTTP_MOVE = 1u << 11, + HTTP_PROPFIND = 1u << 12, + HTTP_PROPPATCH = 1u << 13, + HTTP_SEARCH = 1u << 14, + HTTP_UNLOCK = 1u << 15, + HTTP_BIND = 1u << 16, + HTTP_REBIND = 1u << 17, + HTTP_UNBIND = 1u << 18, + HTTP_ACL = 1u << 19, + + /* subversion */ + // HTTP_REPORT + // HTTP_MKACTIVITY + // HTTP_CHECKOUT + // HTTP_MERGE + + /* upnp */ + // HTTP_MSEARCH + // HTTP_NOTIFY + // HTTP_SUBSCRIBE + // HTTP_UNSUBSCRIBE + + /* RFC-5789 */ + HTTP_PATCH = 1u << 20, + HTTP_PURGE = 1u << 21, + + /* CalDAV */ + // HTTP_MKCALENDAR + + /* RFC-2068, section 19.6.1.2 */ + HTTP_LINK = 1u << 22, + HTTP_UNLINK = 1u << 23, + + /* icecast */ + // HTTP_SOURCE + + HTTP_INVALID = 1u << 31 // Sentinel +}; + +}; // namespace AsyncWebRequestMethod + +typedef AsyncWebRequestMethod::AsyncWebRequestMethodType WebRequestMethod; +class WebRequestMethodComposite { + uint32_t mask; + +private: + constexpr WebRequestMethodComposite(uint32_t m) : mask(m){}; + +public: + // Default constructor: by default, matches nothing + constexpr WebRequestMethodComposite() : mask(0){}; + + // Constructor: allows implicit conversion from WebRequestMethod + constexpr WebRequestMethodComposite(WebRequestMethod m) : mask(static_cast(m)){}; + + // Combine composites + constexpr inline WebRequestMethodComposite operator|(const WebRequestMethodComposite &r) const { + return WebRequestMethodComposite(mask | r.mask); + }; + + // == operator for composite + constexpr inline bool operator==(const WebRequestMethodComposite &r) const { + return mask == r.mask; + }; + + constexpr inline bool operator!=(const WebRequestMethodComposite &r) const { + return mask != r.mask; + }; + + // Check for a match + constexpr inline bool matches(WebRequestMethod m) const { + return mask & static_cast(m); + }; + + constexpr inline bool operator&(WebRequestMethod m) const { + return matches(m); + } + + // Super cool feature: integration with platform `http_method` enum +#ifdef ASYNCWEBSERVER_HTTP_METHOD_INTEGRATION + +// Conversion function for integration with external libraries. +// Horrible ternary implementation for C++11 compatibility. +#define MAP_EXTERNAL_TERNARY(x) (t == http_method::x) ? static_cast(WebRequestMethod::x) + constexpr static inline uint32_t map_http_method(http_method t) { + return MAP_EXTERNAL_TERNARY(HTTP_DELETE) + : MAP_EXTERNAL_TERNARY(HTTP_GET) + : MAP_EXTERNAL_TERNARY(HTTP_HEAD) + : MAP_EXTERNAL_TERNARY(HTTP_POST) + : MAP_EXTERNAL_TERNARY(HTTP_PUT) + : MAP_EXTERNAL_TERNARY(HTTP_CONNECT) + : MAP_EXTERNAL_TERNARY(HTTP_OPTIONS) + : MAP_EXTERNAL_TERNARY(HTTP_TRACE) + : MAP_EXTERNAL_TERNARY(HTTP_COPY) + : MAP_EXTERNAL_TERNARY(HTTP_LOCK) + : MAP_EXTERNAL_TERNARY(HTTP_MKCOL) + : MAP_EXTERNAL_TERNARY(HTTP_MOVE) + : MAP_EXTERNAL_TERNARY(HTTP_PROPFIND) + : MAP_EXTERNAL_TERNARY(HTTP_PROPPATCH) + : MAP_EXTERNAL_TERNARY(HTTP_SEARCH) + : MAP_EXTERNAL_TERNARY(HTTP_UNLOCK) + : MAP_EXTERNAL_TERNARY(HTTP_BIND) + : MAP_EXTERNAL_TERNARY(HTTP_REBIND) + : MAP_EXTERNAL_TERNARY(HTTP_UNBIND) + : MAP_EXTERNAL_TERNARY(HTTP_ACL) + : MAP_EXTERNAL_TERNARY(HTTP_PATCH) + : MAP_EXTERNAL_TERNARY(HTTP_PURGE) + : MAP_EXTERNAL_TERNARY(HTTP_LINK) + : MAP_EXTERNAL_TERNARY(HTTP_UNLINK) +#if defined(HTTP_ANY) + : (t == HTTP_ANY) ? static_cast(WebRequestMethod::HTTP_INVALID) - 1 #endif + : static_cast(WebRequestMethod::HTTP_INVALID); + } +#undef MAP_EXTERNAL_TERNARY + + constexpr WebRequestMethodComposite(http_method m) : mask(map_http_method(m)){}; +#endif +}; // WebRequestMethodComposite + +// Operator| for WebRequestMethod: combine to a WebRequestMethodComposite +constexpr inline WebRequestMethodComposite operator|(WebRequestMethod l, WebRequestMethod r) { + return static_cast(l) | r; +}; + +namespace AsyncWebRequestMethod { +constexpr WebRequestMethodComposite HTTP_ALL = static_cast(static_cast(HTTP_INVALID) - 1); + +// Support HTTP_ANY if we can +#ifndef HTTP_ANY +constexpr WebRequestMethodComposite HTTP_ANY = HTTP_ALL; +#endif +} // namespace AsyncWebRequestMethod + +// WebRequestMethod string conversion functions +namespace asyncsrv { +WebRequestMethod stringToMethod(const String &); +const char *methodToString(WebRequestMethod); +} // namespace asyncsrv + +#if !defined(ASYNCWEBSERVER_NO_GLOBAL_HTTP_METHODS) +// Import the method enum values to the global namespace +using namespace AsyncWebRequestMethod; #endif #ifndef HAVE_FS_FILE_OPEN_MODE @@ -122,7 +267,6 @@ public: #define RESPONSE_TRY_AGAIN 0xFFFFFFFF #define RESPONSE_STREAM_BUFFER_SIZE 1460 -typedef uint16_t WebRequestMethodComposite; typedef std::function ArDisconnectHandler; /* @@ -254,7 +398,7 @@ private: uint8_t _parseState; uint8_t _version; - WebRequestMethodComposite _method; + WebRequestMethod _method; String _url; String _host; String _contentType; @@ -344,7 +488,7 @@ public: uint8_t version() const { return _version; } - WebRequestMethodComposite method() const { + WebRequestMethod method() const { return _method; } const String &url() const { @@ -363,7 +507,9 @@ public: return _isMultipart; } - const char *methodToString() const; + inline const char *methodToString() const { + return asyncsrv::methodToString(_method); + }; const char *requestedConnTypeToString() const; RequestedConnectionType requestedConnType() const { @@ -372,10 +518,10 @@ public: 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); + return _method == AsyncWebRequestMethod::HTTP_GET && isExpectedRequestedConnType(RCT_WS); } bool isSSE() const { - return _method == HTTP_GET && isExpectedRequestedConnType(RCT_EVENT); + return _method == AsyncWebRequestMethod::HTTP_GET && isExpectedRequestedConnType(RCT_EVENT); } bool isHTTP() const { return isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP); @@ -1545,7 +1691,7 @@ public: bool removeHandler(AsyncWebHandler *handler); AsyncCallbackWebHandler &on(AsyncURIMatcher uri, ArRequestHandlerFunction onRequest) { - return on(std::move(uri), HTTP_ANY, onRequest); + return on(std::move(uri), AsyncWebRequestMethod::HTTP_ALL, onRequest); } AsyncCallbackWebHandler &on( AsyncURIMatcher uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload = nullptr, diff --git a/src/Middleware.cpp b/src/Middleware.cpp index 94a720f..42c05af 100644 --- a/src/Middleware.cpp +++ b/src/Middleware.cpp @@ -249,7 +249,7 @@ void AsyncCorsMiddleware::run(AsyncWebServerRequest *request, ArMiddlewareNext n // 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) { + if (request->method() == AsyncWebRequestMethod::HTTP_OPTIONS) { AsyncWebServerResponse *response = request->beginResponse(200); addCORSHeaders(request, response); request->send(response); diff --git a/src/WebHandlerImpl.h b/src/WebHandlerImpl.h index 5332e79..fbff491 100644 --- a/src/WebHandlerImpl.h +++ b/src/WebHandlerImpl.h @@ -7,6 +7,7 @@ #include #include +#include class AsyncStaticWebHandler : public AsyncWebHandler { using File = fs::File; @@ -62,10 +63,10 @@ protected: bool _isRegex; public: - AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} + AsyncCallbackWebHandler() : _uri(), _method(AsyncWebRequestMethod::HTTP_ALL), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} void setUri(AsyncURIMatcher uri); void setMethod(WebRequestMethodComposite method) { - _method = method; + _method = std::move(method); } void onRequest(ArRequestHandlerFunction fn) { _onRequest = fn; diff --git a/src/WebHandlers.cpp b/src/WebHandlers.cpp index cb0860e..698ab00 100644 --- a/src/WebHandlers.cpp +++ b/src/WebHandlers.cpp @@ -103,7 +103,7 @@ AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified() { } bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request) const { - return request->isHTTP() && request->method() == HTTP_GET && request->url().startsWith(_uri) && _getFile(request); + return request->isHTTP() && request->method() == AsyncWebRequestMethod::HTTP_GET && request->url().startsWith(_uri) && _getFile(request); } bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) const { @@ -218,7 +218,7 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) { //File is a gz, get etag from CRC in trailer if (!AsyncWebServerRequest::_getEtag(request->_tempFile, etag)) { // File is corrupted or invalid - async_ws_log_e("File is corrupted or invalid: %s", tempFileName); + async_ws_log_w("File is corrupted or invalid: %s", tempFileName); request->send(404); return; } @@ -300,7 +300,7 @@ void AsyncCallbackWebHandler::setUri(AsyncURIMatcher uri) { } bool AsyncCallbackWebHandler::canHandle(AsyncWebServerRequest *request) const { - if (!_onRequest || !request->isHTTP() || !(_method & request->method())) { + if (!_onRequest || !request->isHTTP() || !_method.matches(request->method())) { return false; } return _uri.matches(request); diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index bf35982..87593bb 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -39,11 +39,12 @@ enum { }; AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c) - : _client(c), _server(s), _handler(NULL), _response(NULL), _onDisconnectfn(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), _method(HTTP_ANY), - _url(), _host(), _contentType(), _boundary(), _authorization(), _reqconntype(RCT_HTTP), _authMethod(AsyncAuthType::AUTH_NONE), _isMultipart(false), - _isPlainPost(false), _expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), - _itemSize(0), _itemName(), _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _chunkStartIndex(0), - _chunkOffset(0), _chunkSize(0), _chunkedParseState(CHUNK_NONE), _chunkedLastChar(0), _tempObject(NULL) { + : _client(c), _server(s), _handler(NULL), _response(NULL), _onDisconnectfn(NULL), _temp(), _parseState(PARSE_REQ_START), _version(0), + _method(AsyncWebRequestMethod::HTTP_UNKNOWN), _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), _chunkStartIndex(0), _chunkOffset(0), _chunkSize(0), _chunkedParseState(CHUNK_NONE), _chunkedLastChar(0), + _tempObject(NULL) { c->onError( [](void *r, AsyncClient *c, int8_t error) { (void)c; @@ -313,35 +314,8 @@ bool AsyncWebServerRequest::_parseReqHead() { 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 if (m == T_PROPFIND) { - _method = HTTP_PROPFIND; - } else if (m == T_LOCK) { - _method = HTTP_LOCK; - } else if (m == T_UNLOCK) { - _method = HTTP_UNLOCK; - } else if (m == T_PROPPATCH) { - _method = HTTP_PROPPATCH; - } else if (m == T_MKCOL) { - _method = HTTP_MKCOL; - } else if (m == T_MOVE) { - _method = HTTP_MOVE; - } else if (m == T_COPY) { - _method = HTTP_COPY; - } else { + _method = asyncsrv::stringToMethod(m); + if (_method == AsyncWebRequestMethod::HTTP_INVALID) { return false; } @@ -1310,55 +1284,6 @@ String AsyncWebServerRequest::urlDecode(const String &text) const { 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; - } - if (_method & HTTP_PROPFIND) { - return T_PROPFIND; - } - if (_method & HTTP_LOCK) { - return T_LOCK; - } - if (_method & HTTP_UNLOCK) { - return T_UNLOCK; - } - if (_method & HTTP_PROPPATCH) { - return T_PROPPATCH; - } - if (_method & HTTP_MKCOL) { - return T_MKCOL; - } - if (_method & HTTP_MOVE) { - return T_MOVE; - } - if (_method & HTTP_COPY) { - return T_COPY; - } - return T_UNKNOWN; -} - const char *AsyncWebServerRequest::requestedConnTypeToString() const { switch (_reqconntype) { case RCT_NOT_USED: return T_RCT_NOT_USED; @@ -1380,3 +1305,95 @@ AsyncClient *AsyncWebServerRequest::clientRelease() { _client = nullptr; return c; } + +namespace asyncsrv { +// WebRequestMethod conversions +WebRequestMethod stringToMethod(const String &m) { + if (m == T_GET) { + return AsyncWebRequestMethod::HTTP_GET; + } else if (m == T_POST) { + return AsyncWebRequestMethod::HTTP_POST; + } else if (m == T_DELETE) { + return AsyncWebRequestMethod::HTTP_DELETE; + } else if (m == T_PUT) { + return AsyncWebRequestMethod::HTTP_PUT; + } else if (m == T_PATCH) { + return AsyncWebRequestMethod::HTTP_PATCH; + } else if (m == T_HEAD) { + return AsyncWebRequestMethod::HTTP_HEAD; + } else if (m == T_OPTIONS) { + return AsyncWebRequestMethod::HTTP_OPTIONS; + } else if (m == T_TRACE) { + return AsyncWebRequestMethod::HTTP_TRACE; + } else if (m == T_CONNECT) { + return AsyncWebRequestMethod::HTTP_CONNECT; + } else if (m == T_PURGE) { + return AsyncWebRequestMethod::HTTP_PURGE; + } else if (m == T_LINK) { + return AsyncWebRequestMethod::HTTP_LINK; + } else if (m == T_UNLINK) { + return AsyncWebRequestMethod::HTTP_UNLINK; + } else if (m == T_PROPFIND) { + return AsyncWebRequestMethod::HTTP_PROPFIND; + } else if (m == T_LOCK) { + return AsyncWebRequestMethod::HTTP_LOCK; + } else if (m == T_UNLOCK) { + return AsyncWebRequestMethod::HTTP_UNLOCK; + } else if (m == T_PROPPATCH) { + return AsyncWebRequestMethod::HTTP_PROPPATCH; + } else if (m == T_MKCOL) { + return AsyncWebRequestMethod::HTTP_MKCOL; + } else if (m == T_MOVE) { + return AsyncWebRequestMethod::HTTP_MOVE; + } else if (m == T_COPY) { + return AsyncWebRequestMethod::HTTP_COPY; + } else if (m == T_SEARCH) { + return AsyncWebRequestMethod::HTTP_SEARCH; + } else if (m == T_BIND) { + return AsyncWebRequestMethod::HTTP_BIND; + } else if (m == T_REBIND) { + return AsyncWebRequestMethod::HTTP_REBIND; + } else if (m == T_UNBIND) { + return AsyncWebRequestMethod::HTTP_UNBIND; + } else if (m == T_ACL) { + return AsyncWebRequestMethod::HTTP_ACL; + } else { + return AsyncWebRequestMethod::HTTP_INVALID; + } +} + +const char *methodToString(WebRequestMethod method) { + switch (method) { + case AsyncWebRequestMethod::HTTP_DELETE: return T_DELETE; + case AsyncWebRequestMethod::HTTP_GET: return T_GET; + case AsyncWebRequestMethod::HTTP_HEAD: return T_HEAD; + case AsyncWebRequestMethod::HTTP_POST: return T_POST; + case AsyncWebRequestMethod::HTTP_PUT: return T_PUT; + /* pathological */ + case AsyncWebRequestMethod::HTTP_CONNECT: return T_CONNECT; + case AsyncWebRequestMethod::HTTP_OPTIONS: return T_OPTIONS; + case AsyncWebRequestMethod::HTTP_TRACE: return T_TRACE; + /* WebDAV */ + case AsyncWebRequestMethod::HTTP_COPY: return T_COPY; + case AsyncWebRequestMethod::HTTP_LOCK: return T_LOCK; + case AsyncWebRequestMethod::HTTP_MKCOL: return T_MKCOL; + case AsyncWebRequestMethod::HTTP_MOVE: return T_MOVE; + case AsyncWebRequestMethod::HTTP_PROPFIND: return T_PROPFIND; + case AsyncWebRequestMethod::HTTP_PROPPATCH: return T_PROPPATCH; + case AsyncWebRequestMethod::HTTP_SEARCH: return T_SEARCH; + case AsyncWebRequestMethod::HTTP_UNLOCK: return T_UNLOCK; + case AsyncWebRequestMethod::HTTP_BIND: return T_BIND; + case AsyncWebRequestMethod::HTTP_REBIND: return T_REBIND; + case AsyncWebRequestMethod::HTTP_UNBIND: return T_UNBIND; + case AsyncWebRequestMethod::HTTP_ACL: return T_ACL; + /* RFC-5789 */ + case AsyncWebRequestMethod::HTTP_PATCH: return T_PATCH; + case AsyncWebRequestMethod::HTTP_PURGE: return T_PURGE; + /* RFC-2068, section 19.6.1.2 */ + case AsyncWebRequestMethod::HTTP_LINK: return T_LINK; + case AsyncWebRequestMethod::HTTP_UNLINK: return T_UNLINK; + // Unsupported + default: return T_UNKNOWN; + } +} +} // namespace asyncsrv diff --git a/src/WebServer.cpp b/src/WebServer.cpp index 2ab5bcd..15fa701 100644 --- a/src/WebServer.cpp +++ b/src/WebServer.cpp @@ -158,7 +158,7 @@ AsyncCallbackWebHandler &AsyncWebServer::on( ) { AsyncCallbackWebHandler *handler = new AsyncCallbackWebHandler(); handler->setUri(std::move(uri)); - handler->setMethod(method); + handler->setMethod(std::move(method)); handler->onRequest(onRequest); handler->onUpload(onUpload); handler->onBody(onBody); @@ -169,7 +169,7 @@ AsyncCallbackWebHandler &AsyncWebServer::on( #if ASYNC_JSON_SUPPORT == 1 AsyncCallbackJsonWebHandler &AsyncWebServer::on(AsyncURIMatcher uri, WebRequestMethodComposite method, ArJsonRequestHandlerFunction onBody) { AsyncCallbackJsonWebHandler *handler = new AsyncCallbackJsonWebHandler(std::move(uri), onBody); - handler->setMethod(method); + handler->setMethod(std::move(method)); addHandler(handler); return *handler; } diff --git a/src/literals.h b/src/literals.h index 71cf127..7244afc 100644 --- a/src/literals.h +++ b/src/literals.h @@ -104,7 +104,6 @@ static constexpr const char T_WWW_AUTH[] = "WWW-Authenticate"; static constexpr const char T_X_Expected_Entity_Length[] = "X-Expected-Entity-Length"; // 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"; @@ -120,6 +119,16 @@ static constexpr const char T_MKCOL[] = "MKCOL"; static constexpr const char T_MOVE[] = "MOVE"; static constexpr const char T_COPY[] = "COPY"; static constexpr const char T_UNKNOWN[] = "UNKNOWN"; +static constexpr const char T_CONNECT[] = "CONNECT"; +static constexpr const char T_TRACE[] = "TRACE"; +static constexpr const char T_SEARCH[] = "SEARCH"; +static constexpr const char T_BIND[] = "BIND"; +static constexpr const char T_REBIND[] = "REBIND"; +static constexpr const char T_UNBIND[] = "UNBIND"; +static constexpr const char T_ACL[] = "ACL"; +static constexpr const char T_PURGE[] = "PURGE"; +static constexpr const char T_LINK[] = "LINK"; +static constexpr const char T_UNLINK[] = "UNLINK"; // Req content types static constexpr const char T_RCT_NOT_USED[] = "RCT_NOT_USED";