From 9f7e380ffdd1de6a851edb6c93c86f8afcbef898 Mon Sep 17 00:00:00 2001 From: Khoi Hoang <57012152+khoih-prog@users.noreply.github.com> Date: Wed, 5 Oct 2022 20:01:34 -0400 Subject: [PATCH] v1.5.0 to save heap when sending large data #### Releases v1.5.0 1. Support using `CString` to save heap to send `very large data`. Check [request->send(200, textPlainStr, jsonChartDataCharStr); - Without using String Class - to save heap #8](https://github.com/khoih-prog/Portenta_H7_AsyncWebServer/pull/8) and [All memmove() removed - string no longer destroyed #11](https://github.com/khoih-prog/Portenta_H7_AsyncWebServer/pull/11) 2. Add multiple examples to demo the new feature --- CONTRIBUTING.md | 4 +- changelog.md | 6 + examples/Async_AdvancedWebServer/defines.h | 3 +- ...bServer_MemoryIssues_SendArduinoString.ino | 303 ++++++++++++++++ .../defines.h | 74 ++++ ...cedWebServer_MemoryIssues_Send_CString.ino | 311 +++++++++++++++++ .../defines.h | 74 ++++ library.json | 4 +- library.properties | 6 +- ...ver_MemoryIssues_Send_CString_ENC28J60.png | Bin 0 -> 63171 bytes ...Server_MemoryIssues_Send_CString_W5500.png | Bin 0 -> 65834 bytes platformio/platformio.ini | 24 +- src/AsyncEventSource_Ethernet.cpp | 91 ++++- src/AsyncEventSource_Ethernet.h | 59 +++- src/AsyncJson_Ethernet.h | 83 ++++- src/AsyncWebAuthentication_Ethernet.cpp | 26 +- src/AsyncWebAuthentication_Ethernet.h | 15 +- src/AsyncWebHandlerImpl_Ethernet.h | 39 ++- src/AsyncWebHandlers_Ethernet.cpp | 28 +- src/AsyncWebRequest_Ethernet.cpp | 195 ++++++++++- src/AsyncWebResponseImpl_Ethernet.h | 81 ++++- src/AsyncWebResponses_Ethernet.cpp | 246 ++++++++++++- src/AsyncWebServer_Ethernet.cpp | 65 +++- src/AsyncWebServer_Ethernet.h | 10 +- src/AsyncWebServer_Ethernet.hpp | 286 +++++++++++---- src/AsyncWebServer_Ethernet_Debug.h | 6 +- src/AsyncWebServer_Ethernet_cbuf.hpp | 20 +- src/AsyncWebServer_Ethernet_cbuf_Impl.h | 43 ++- src/AsyncWebSocket_Ethernet.cpp | 325 +++++++++++++++--- src/AsyncWebSocket_Ethernet.h | 167 +++++++-- src/AsyncWebSynchronization_Ethernet.h | 69 ++-- src/StringArray_Ethernet.h | 204 ++++++----- src/libb64/cdecode.c | 10 +- src/libb64/cdecode.h | 10 +- src/libb64/cencode.c | 10 +- src/libb64/cencode.h | 10 +- 36 files changed, 2496 insertions(+), 411 deletions(-) create mode 100644 examples/Async_AdvancedWebServer_MemoryIssues_SendArduinoString/Async_AdvancedWebServer_MemoryIssues_SendArduinoString.ino create mode 100644 examples/Async_AdvancedWebServer_MemoryIssues_SendArduinoString/defines.h create mode 100644 examples/Async_AdvancedWebServer_MemoryIssues_Send_CString/Async_AdvancedWebServer_MemoryIssues_Send_CString.ino create mode 100644 examples/Async_AdvancedWebServer_MemoryIssues_Send_CString/defines.h create mode 100644 pics/Async_AdvancedWebServer_MemoryIssues_Send_CString_ENC28J60.png create mode 100644 pics/Async_AdvancedWebServer_MemoryIssues_Send_CString_W5500.png diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8f1dca5..8640589 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,10 +30,10 @@ Arduino IDE version: 1.8.19 ESP8266_NODEMCU_ESP12E using ESP8266_W5500 Ethernet ESP8266 core v3.0.2 OS: Ubuntu 20.04 LTS -Linux xy-Inspiron-3593 5.13.0-39-generic #44~20.04.1-Ubuntu SMP Thu Mar 24 16:43:35 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux +Linux xy-Inspiron-3593 5.15.0-48-generic #54~20.04.1-Ubuntu SMP Thu Sep 1 16:17:26 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux Context: -I encountered a crash while trying to use the Timer Interrupt. +I encountered a crash while using this library Steps to reproduce: 1. ... diff --git a/changelog.md b/changelog.md index eb8e534..e5f7cc7 100644 --- a/changelog.md +++ b/changelog.md @@ -11,6 +11,7 @@ ## Table of contents * [Changelog](#changelog) + * [Releases v1.5.0](#releases-v150) * [Releases v1.4.1](#releases-v141) --- @@ -18,6 +19,11 @@ ## Changelog +#### Releases v1.5.0 + +1. Support using `CString` to save heap to send `very large data`. Check [request->send(200, textPlainStr, jsonChartDataCharStr); - Without using String Class - to save heap #8](https://github.com/khoih-prog/Portenta_H7_AsyncWebServer/pull/8) and [All memmove() removed - string no longer destroyed #11](https://github.com/khoih-prog/Portenta_H7_AsyncWebServer/pull/11) +2. Add multiple examples to demo the new feature + #### Releases v1.4.1 1. Initial coding to port [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) to ESP8266 boards using W5x00 / ENC28J60 Ethernet. diff --git a/examples/Async_AdvancedWebServer/defines.h b/examples/Async_AdvancedWebServer/defines.h index 8ebfc9f..4137aff 100644 --- a/examples/Async_AdvancedWebServer/defines.h +++ b/examples/Async_AdvancedWebServer/defines.h @@ -30,7 +30,8 @@ #include -#define CSPIN 16 // 5 +// Using GPIO4, GPIO16, or GPIO5 +#define CSPIN 4 //16 // 5 #if USING_W5500 #include "W5500lwIP.h" diff --git a/examples/Async_AdvancedWebServer_MemoryIssues_SendArduinoString/Async_AdvancedWebServer_MemoryIssues_SendArduinoString.ino b/examples/Async_AdvancedWebServer_MemoryIssues_SendArduinoString/Async_AdvancedWebServer_MemoryIssues_SendArduinoString.ino new file mode 100644 index 0000000..6580df5 --- /dev/null +++ b/examples/Async_AdvancedWebServer_MemoryIssues_SendArduinoString/Async_AdvancedWebServer_MemoryIssues_SendArduinoString.ino @@ -0,0 +1,303 @@ +/**************************************************************************************************************************** + Async_AdvancedWebServer_MemoryIssues_SendArduinoString.ino + + For ESP8266 using W5x00/ENC8266 Ethernet + + AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + + Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Licensed under GPLv3 license + + Copyright (c) 2015, Majenko Technologies + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + Neither the name of Majenko Technologies nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************************************************************/ + +#include "defines.h" + +#include + +// In bytes +//#define STRING_SIZE 40000 +#define STRING_SIZE 12000 + +AsyncWebServer server(80); + +int reqCount = 0; // number of requests received + +#define BUFFER_SIZE 512 +char temp[BUFFER_SIZE]; + +void handleRoot(AsyncWebServerRequest *request) +{ + digitalWrite(LED_BUILTIN, LED_ON); + + int sec = millis() / 1000; + int min = sec / 60; + int hr = min / 60; + int day = hr / 24; + + snprintf(temp, BUFFER_SIZE - 1, + "\ +\ +\ +AsyncWebServer-%s\ +\ +\ +\ +

AsyncWebServer_Ethernet!

\ +

running on %s

\ +

Uptime: %d d %02d:%02d:%02d

\ +\ +\ +", BOARD_NAME, SHIELD_TYPE, day, hr % 24, min % 60, sec % 60); + + request->send(200, "text/html", temp); + + digitalWrite(LED_BUILTIN, LED_OFF); +} + +void handleNotFound(AsyncWebServerRequest *request) +{ + digitalWrite(LED_BUILTIN, LED_ON); + String message = "File Not Found\n\n"; + + message += "URI: "; + message += request->url(); + message += "\nMethod: "; + message += (request->method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += request->args(); + message += "\n"; + + for (uint8_t i = 0; i < request->args(); i++) + { + message += " " + request->argName(i) + ": " + request->arg(i) + "\n"; + } + + request->send(404, "text/plain", message); + digitalWrite(LED_BUILTIN, LED_OFF); +} + +void PrintHeapData(String hIn) +{ + // cores/esp8266/Esp.cpp + static uint32_t maxFreeHeap = 0xFFFFFFFF; + + // Get once at the beginning for comparison only + static uint32_t totalHeap = ESP.getFreeHeap(); + + uint32_t freeHeap = ESP.getFreeHeap(); + + // Print and update only when larger heap + if (maxFreeHeap > freeHeap) + { + maxFreeHeap = freeHeap; + + Serial.print("\nHEAP DATA - "); + Serial.print(hIn); + + Serial.print(" Free heap: "); + Serial.print(freeHeap); + Serial.print(" Used heap: "); + Serial.println(totalHeap - freeHeap); + } +} + +void PrintStringSize(String & out) +{ + static uint32_t count = 0; + + // Print only when cStr length too large and corrupting memory or every (20 * 5) s + if ( (out.length() >= STRING_SIZE) || (++count > 20) ) + { + Serial.print("\nOut String Length="); + Serial.println(out.length()); + + count = 0; + } +} + +void drawGraph(AsyncWebServerRequest *request) +{ + String out; + + out.reserve(STRING_SIZE); + char temp[70]; + + out += "\n"; + out += "\n"; + out += "\n"; + int y = rand() % 130; + + for (int x = 10; x < 1500; x += 10) + { + int y2 = rand() % 130; + sprintf(temp, "\n", x, 140 - y, x + 10, 140 - y2); + out += temp; + y = y2; + } + + out += "\n\n"; + + PrintHeapData("Pre Send"); + + PrintStringSize(out); + + request->send(200, "image/svg+xml", out); + + PrintHeapData("Post Send"); +} + +void initEthernet() +{ + SPI.begin(); + SPI.setClockDivider(SPI_CLOCK_DIV4); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + +#if !USING_DHCP + eth.config(localIP, gateway, netMask, gateway); +#endif + + eth.setDefault(); + + if (!eth.begin()) + { + Serial.println("No Ethernet hardware ... Stop here"); + + while (true) + { + delay(1000); + } + } + else + { + Serial.print("Connecting to network : "); + + while (!eth.connected()) + { + Serial.print("."); + delay(1000); + } + } + + Serial.println(); + +#if USING_DHCP + Serial.print("Ethernet DHCP IP address: "); +#else + Serial.print("Ethernet Static IP address: "); +#endif + + Serial.println(eth.localIP()); +} + +void setup() +{ + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LED_OFF); + + Serial.begin(115200); + while (!Serial && millis() < 5000); + + delay(200); + + Serial.print("\nStart Async_AdvancedWebServer_MemoryIssues_SendArduinoString on "); Serial.print(BOARD_NAME); + Serial.print(" with "); Serial.println(SHIELD_TYPE); + Serial.println(ASYNC_WEBSERVER_ETHERNET_VERSION); + + PrintHeapData("Start =>"); + + initEthernet(); + + /////////////////////////////////// + + server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) + { + handleRoot(request); + }); + + server.on("/test.svg", HTTP_GET, [](AsyncWebServerRequest * request) + { + drawGraph(request); + }); + + server.on("/inline", [](AsyncWebServerRequest * request) + { + request->send(200, "text/plain", "This works as well"); + }); + + server.onNotFound(handleNotFound); + + server.begin(); + + Serial.print(F("HTTP EthernetWebServer is @ IP : ")); + Serial.println(eth.localIP()); + + PrintHeapData("Pre Create Arduino String"); + +} + +void heartBeatPrint() +{ + static int num = 1; + + Serial.print(F(".")); + + if (num == 80) + { + Serial.println(); + num = 1; + } + else if (num++ % 10 == 0) + { + Serial.print(F(" ")); + } +} + +void check_status() +{ + static unsigned long checkstatus_timeout = 0; + +#define STATUS_CHECK_INTERVAL 10000L + + // Send status report every STATUS_REPORT_INTERVAL (60) seconds: we don't need to send updates frequently if there is no status change. + if ((millis() > checkstatus_timeout) || (checkstatus_timeout == 0)) + { + heartBeatPrint(); + checkstatus_timeout = millis() + STATUS_CHECK_INTERVAL; + } +} + +void loop() +{ + check_status(); +} diff --git a/examples/Async_AdvancedWebServer_MemoryIssues_SendArduinoString/defines.h b/examples/Async_AdvancedWebServer_MemoryIssues_SendArduinoString/defines.h new file mode 100644 index 0000000..4137aff --- /dev/null +++ b/examples/Async_AdvancedWebServer_MemoryIssues_SendArduinoString/defines.h @@ -0,0 +1,74 @@ +/**************************************************************************************************************************** + defines.h + + For ESP8266 using W5x00/ENC8266 Ethernet + + AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + + Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Licensed under GPLv3 license + ***************************************************************************************************************************************/ + +#ifndef defines_h +#define defines_h + +#if defined(ESP8266) + #define LED_ON LOW + #define LED_OFF HIGH +#else + #error Only ESP8266 +#endif + +#define _AWS_ETHERNET_LOGLEVEL_ 1 + +////////////////////////////////////////////////////////// + +#define USING_W5500 true +#define USING_W5100 false +#define USING_ENC28J60 false + +#include + +// Using GPIO4, GPIO16, or GPIO5 +#define CSPIN 4 //16 // 5 + +#if USING_W5500 + #include "W5500lwIP.h" + #define SHIELD_TYPE "ESP8266_W5500 Ethernet" + + Wiznet5500lwIP eth(CSPIN); + +#elif USING_W5100 + #include + #define SHIELD_TYPE "ESP8266_W5100 Ethernet" + + Wiznet5100lwIP eth(CSPIN); + +#elif USING_ENC28J60 + #include + #define SHIELD_TYPE "ESP8266_ENC28J60 Ethernet" + + ENC28J60lwIP eth(CSPIN); +#else + // default if none selected + #include "W5500lwIP.h" + + Wiznet5500lwIP eth(CSPIN); +#endif + +#include // WiFiClient (-> TCPClient) + +using TCPClient = WiFiClient; + +////////////////////////////////////////////////////////// + +#define USING_DHCP true + +#if !USING_DHCP + IPAddress localIP(192, 168, 2, 222); + IPAddress gateway(192, 168, 2, 1); + IPAddress netMask(255, 255, 255, 0); +#endif + +#endif //defines_h diff --git a/examples/Async_AdvancedWebServer_MemoryIssues_Send_CString/Async_AdvancedWebServer_MemoryIssues_Send_CString.ino b/examples/Async_AdvancedWebServer_MemoryIssues_Send_CString/Async_AdvancedWebServer_MemoryIssues_Send_CString.ino new file mode 100644 index 0000000..af57851 --- /dev/null +++ b/examples/Async_AdvancedWebServer_MemoryIssues_Send_CString/Async_AdvancedWebServer_MemoryIssues_Send_CString.ino @@ -0,0 +1,311 @@ +/**************************************************************************************************************************** + Async_AdvancedWebServer_MemoryIssues_Send_CString.ino + + For ESP8266 using W5x00/ENC8266 Ethernet + + AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + + Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Licensed under GPLv3 license + + Copyright (c) 2015, Majenko Technologies + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + Neither the name of Majenko Technologies nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************************************************************/ + +#include "defines.h" + +#include + +char *cStr; + +// In bytes +#define CSTRING_SIZE 12000 + +AsyncWebServer server(80); + +int reqCount = 0; // number of requests received + +#define BUFFER_SIZE 768 // a little larger in case required for header shift (destructive send) +char temp[BUFFER_SIZE]; + +void handleRoot(AsyncWebServerRequest *request) +{ + digitalWrite(LED_BUILTIN, LED_ON); + + int sec = millis() / 1000; + int min = sec / 60; + int hr = min / 60; + int day = hr / 24; + + snprintf(temp, BUFFER_SIZE - 1, + "\ +\ +\ +AsyncWebServer-%s\ +\ +\ +\ +

AsyncWebServer_Ethernet!

\ +

running on %s

\ +

Uptime: %d d %02d:%02d:%02d

\ +\ +\ +", BOARD_NAME, SHIELD_TYPE, day, hr % 24, min % 60, sec % 60); + + request->send(200, "text/html", temp); + + digitalWrite(LED_BUILTIN, LED_OFF); +} + +void handleNotFound(AsyncWebServerRequest *request) +{ + digitalWrite(LED_BUILTIN, LED_ON); + String message = "File Not Found\n\n"; + + message += "URI: "; + message += request->url(); + message += "\nMethod: "; + message += (request->method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += request->args(); + message += "\n"; + + for (uint8_t i = 0; i < request->args(); i++) + { + message += " " + request->argName(i) + ": " + request->arg(i) + "\n"; + } + + request->send(404, "text/plain", message); + digitalWrite(LED_BUILTIN, LED_OFF); +} + +void PrintHeapData(String hIn) +{ + // cores/esp8266/Esp.cpp + static uint32_t maxFreeHeap = 0xFFFFFFFF; + + // Get once at the beginning for comparison only + static uint32_t totalHeap = ESP.getFreeHeap(); + + uint32_t freeHeap = ESP.getFreeHeap(); + + // Print and update only when larger heap + if (maxFreeHeap > freeHeap) + { + maxFreeHeap = freeHeap; + + Serial.print("\nHEAP DATA - "); + Serial.print(hIn); + + Serial.print(" Free heap: "); + Serial.print(freeHeap); + Serial.print(" Used heap: "); + Serial.println(totalHeap - freeHeap); + } +} + +void PrintStringSize(const char* cStr) +{ + Serial.print("\nOut String Length="); + Serial.println(strlen(cStr)); +} + +void drawGraph(AsyncWebServerRequest *request) +{ + char temp[80]; + + cStr[0] = '\0'; + + strcat(cStr, "\n"); + strcat(cStr, "\n"); + strcat(cStr, "\n"); + int y = rand() % 130; + + for (int x = 10; x < 1500; x += 10) + { + int y2 = rand() % 130; + sprintf(temp, "\n", x, 140 - y, x + 10, 140 - y2); + strcat(cStr, temp); + y = y2; + } + + strcat(cStr, "\n\n"); + + PrintHeapData("Pre Send"); + + // Print only when cStr length too large and corrupting memory + if ( (strlen(cStr) >= CSTRING_SIZE)) + { + PrintStringSize(cStr); + } + + request->send(200, "image/svg+xml", cStr, false); + + PrintHeapData("Post Send"); +} + +void initEthernet() +{ + SPI.begin(); + SPI.setClockDivider(SPI_CLOCK_DIV4); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + +#if !USING_DHCP + eth.config(localIP, gateway, netMask, gateway); +#endif + + eth.setDefault(); + + if (!eth.begin()) + { + Serial.println("No Ethernet hardware ... Stop here"); + + while (true) + { + delay(1000); + } + } + else + { + Serial.print("Connecting to network : "); + + while (!eth.connected()) + { + Serial.print("."); + delay(1000); + } + } + + Serial.println(); + +#if USING_DHCP + Serial.print("Ethernet DHCP IP address: "); +#else + Serial.print("Ethernet Static IP address: "); +#endif + + Serial.println(eth.localIP()); +} + +void setup() +{ + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, LED_OFF); + + Serial.begin(115200); + while (!Serial && millis() < 5000); + + delay(200); + + Serial.print("\nStart Async_AdvancedWebServer_MemoryIssues_Send_CString on "); Serial.print(BOARD_NAME); + Serial.print(" with "); Serial.println(SHIELD_TYPE); + Serial.println(ASYNC_WEBSERVER_ETHERNET_VERSION); + + PrintHeapData("Start =>"); + + cStr = (char *) malloc(CSTRING_SIZE); // make a little larger than required + + if (cStr == NULL) + { + Serial.println("Unable top Allocate RAM"); + + for(;;); + } + + /////////////////////////////////// + + initEthernet(); + + /////////////////////////////////// + + server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) + { + handleRoot(request); + }); + + server.on("/test.svg", HTTP_GET, [](AsyncWebServerRequest * request) + { + drawGraph(request); + }); + + server.on("/inline", [](AsyncWebServerRequest * request) + { + request->send(200, "text/plain", "This works as well"); + }); + + server.onNotFound(handleNotFound); + + server.begin(); + + Serial.print(F("HTTP EthernetWebServer is @ IP : ")); + Serial.println(eth.localIP()); + + PrintHeapData("Pre Create Arduino String"); + +} + +void heartBeatPrint() +{ + static int num = 1; + + Serial.print(F(".")); + + if (num == 80) + { + //Serial.println(); + PrintStringSize(cStr); + num = 1; + } + else if (num++ % 10 == 0) + { + Serial.print(F(" ")); + } +} + +void check_status() +{ + static unsigned long checkstatus_timeout = 0; + +#define STATUS_CHECK_INTERVAL 10000L + + // Send status report every STATUS_REPORT_INTERVAL (60) seconds: we don't need to send updates frequently if there is no status change. + if ((millis() > checkstatus_timeout) || (checkstatus_timeout == 0)) + { + heartBeatPrint(); + checkstatus_timeout = millis() + STATUS_CHECK_INTERVAL; + } +} + +void loop() +{ + check_status(); +} diff --git a/examples/Async_AdvancedWebServer_MemoryIssues_Send_CString/defines.h b/examples/Async_AdvancedWebServer_MemoryIssues_Send_CString/defines.h new file mode 100644 index 0000000..5fe5d52 --- /dev/null +++ b/examples/Async_AdvancedWebServer_MemoryIssues_Send_CString/defines.h @@ -0,0 +1,74 @@ +/**************************************************************************************************************************** + defines.h + + For ESP8266 using W5x00/ENC8266 Ethernet + + AsyncWebServer_Ethernet is a library for the Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library + + Based on and modified from ESPAsyncWebServer (https://github.com/me-no-dev/ESPAsyncWebServer) + Built by Khoi Hoang https://github.com/khoih-prog/AsyncWebServer_Ethernet + Licensed under GPLv3 license + ***************************************************************************************************************************************/ + +#ifndef defines_h +#define defines_h + +#if defined(ESP8266) + #define LED_ON LOW + #define LED_OFF HIGH +#else + #error Only ESP8266 +#endif + +#define _AWS_ETHERNET_LOGLEVEL_ 1 + +////////////////////////////////////////////////////////// + +#define USING_W5500 false +#define USING_W5100 false +#define USING_ENC28J60 true + +#include + +// Using GPIO4, GPIO16, or GPIO5 +#define CSPIN 4 //16 // 5 + +#if USING_W5500 + #include "W5500lwIP.h" + #define SHIELD_TYPE "ESP8266_W5500 Ethernet" + + Wiznet5500lwIP eth(CSPIN); + +#elif USING_W5100 + #include + #define SHIELD_TYPE "ESP8266_W5100 Ethernet" + + Wiznet5100lwIP eth(CSPIN); + +#elif USING_ENC28J60 + #include + #define SHIELD_TYPE "ESP8266_ENC28J60 Ethernet" + + ENC28J60lwIP eth(CSPIN); +#else + // default if none selected + #include "W5500lwIP.h" + + Wiznet5500lwIP eth(CSPIN); +#endif + +#include // WiFiClient (-> TCPClient) + +using TCPClient = WiFiClient; + +////////////////////////////////////////////////////////// + +#define USING_DHCP true + +#if !USING_DHCP + IPAddress localIP(192, 168, 2, 222); + IPAddress gateway(192, 168, 2, 1); + IPAddress netMask(255, 255, 255, 0); +#endif + +#endif //defines_h diff --git a/library.json b/library.json index 18c64d3..4aabda2 100644 --- a/library.json +++ b/library.json @@ -1,7 +1,7 @@ { "name":"AsyncWebServer_Ethernet", - "version": "1.4.1", - "description":"Asynchronous HTTP and WebSocket Server Library for ESP8266 using W5x00 or ENC28J60 Ethernet. This is Asynchronous HTTP and WebSocket Server Library for ESP8266 using W5x00 or ENC28J60 Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library", + "version": "1.5.0", + "description":"Asynchronous HTTP and WebSocket Server Library for ESP8266 using W5x00 or ENC28J60 Ethernet. This is Asynchronous HTTP and WebSocket Server Library for ESP8266 using W5x00 or ENC28J60 Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library. Now supporting using CString to save heap to send very large data", "keywords":"http, async, async-webserver, websocket, webserver, esp8266, w5x00, enc28j60", "authors": [ diff --git a/library.properties b/library.properties index 0f48505..0a8e377 100644 --- a/library.properties +++ b/library.properties @@ -1,12 +1,12 @@ name=AsyncWebServer_Ethernet -version=1.4.1 +version=1.5.0 author=Hristo Gochkov,Khoi Hoang maintainer=Khoi Hoang sentence=Asynchronous HTTP and WebSocket Server Library for ESP8266 using W5x00 or ENC28J60 Ethernet -paragraph=This is Asynchronous HTTP and WebSocket Server Library for ESP8266 using W5x00 or ENC28J60 Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library +paragraph=This is Asynchronous HTTP and WebSocket Server Library for ESP8266 using W5x00 or ENC28J60 Ethernet with lwIP_5100, lwIP_5500 or lwIP_enc28j60 library. Now supporting using CString to save heap to send very large data category=Communication url=https://github.com/khoih-prog/AsyncWebServer_Ethernet license=GPLv3 architectures=esp8266 -depends=ESP AsyncTCP +depends=ESPAsyncTCP includes=AsyncWebServer_Ethernet.h diff --git a/pics/Async_AdvancedWebServer_MemoryIssues_Send_CString_ENC28J60.png b/pics/Async_AdvancedWebServer_MemoryIssues_Send_CString_ENC28J60.png new file mode 100644 index 0000000000000000000000000000000000000000..696b65b47c1a57f0759c666f2d8465b1fc30b9d9 GIT binary patch literal 63171 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 literal 0 HcmV?d00001 diff --git a/pics/Async_AdvancedWebServer_MemoryIssues_Send_CString_W5500.png b/pics/Async_AdvancedWebServer_MemoryIssues_Send_CString_W5500.png new file mode 100644 index 0000000000000000000000000000000000000000..29630312754d9117c8f112c4e78afced88073da4 GIT binary patch literal 65834 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.1.1 -; ESPAsyncTCP@>=1.2.2 - -; PlatformIO 5.x -; me-no-dev/AsyncTCP@>=1.1.1 - me-no-dev/ESPAsyncTCP@>=1.2.2 build_flags = ; set your debug output (default=Serial) @@ -54,6 +50,13 @@ build_flags = [env:ESP8266] platform = espressif8266 framework = arduino + +; PlatformIO 4.x +; ESPAsyncTCP@>=1.2.2 + +; PlatformIO 5.x + me-no-dev/ESPAsyncTCP@>=1.2.2 + ; ============================================================ ; Board configuration ; choose your board by uncommenting one of the following lines @@ -99,6 +102,13 @@ board = nodemcuv2 [env:ESP32] platform = espressif32 framework = arduino + +; PlatformIO 4.x +; AsyncTCP@>=1.1.1 + +; PlatformIO 5.x +; me-no-dev/AsyncTCP@>=1.1.1 + ; ============================================================ ; Board configuration ; choose your board by uncommenting one of the following lines diff --git a/src/AsyncEventSource_Ethernet.cpp b/src/AsyncEventSource_Ethernet.cpp index 23aece3..a6e7fe1 100644 --- a/src/AsyncEventSource_Ethernet.cpp +++ b/src/AsyncEventSource_Ethernet.cpp @@ -12,14 +12,16 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #define _AWS_ETHERNET_LOGLEVEL_ 1 @@ -29,6 +31,8 @@ #include "Arduino.h" #include "AsyncEventSource_Ethernet.h" +///////////////////////////////////////////////////////// + static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect) { String ev = ""; @@ -143,6 +147,9 @@ static String generateEventMessage(const char *message, const char *event, uint3 return ev; } +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + // Message AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len) @@ -161,15 +168,19 @@ AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len) } } +///////////////////////////////////////////////////////// + AsyncEventSourceMessage::~AsyncEventSourceMessage() { if (_data != NULL) free(_data); } +///////////////////////////////////////////////////////// + size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) { - (void)time; + AWS_ETHERNET_UNUSED(time); // If the whole message is now acked... if (_acked + len > _len) @@ -187,6 +198,8 @@ size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) return 0; } +///////////////////////////////////////////////////////// + size_t AsyncEventSourceMessage::send(AsyncClient *client) { const size_t len = _len - _sent; @@ -206,6 +219,9 @@ size_t AsyncEventSourceMessage::send(AsyncClient *client) return sent; } +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + // Client AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) @@ -225,13 +241,13 @@ AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, A _client->onError(NULL, NULL); _client->onAck([](void *r, AsyncClient * c, size_t len, uint32_t time) { - (void)c; + AWS_ETHERNET_UNUSED(c); ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this); _client->onPoll([](void *r, AsyncClient * c) { - (void)c; + AWS_ETHERNET_UNUSED(c); ((AsyncEventSourceClient*)(r))->_onPoll(); }, this); @@ -253,12 +269,16 @@ AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, A delete request; } +///////////////////////////////////////////////////////// + AsyncEventSourceClient::~AsyncEventSourceClient() { _messageQueue.free(); close(); } +///////////////////////////////////////////////////////// + void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage) { if (dataMessage == NULL) @@ -284,6 +304,8 @@ void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage) _runQueue(); } +///////////////////////////////////////////////////////// + void AsyncEventSourceClient::_onAck(size_t len, uint32_t time) { while (len && !_messageQueue.isEmpty()) @@ -297,6 +319,8 @@ void AsyncEventSourceClient::_onAck(size_t len, uint32_t time) _runQueue(); } +///////////////////////////////////////////////////////// + void AsyncEventSourceClient::_onPoll() { if (!_messageQueue.isEmpty()) @@ -305,36 +329,48 @@ void AsyncEventSourceClient::_onPoll() } } +///////////////////////////////////////////////////////// void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))) { _client->close(true); } +///////////////////////////////////////////////////////// + void AsyncEventSourceClient::_onDisconnect() { _client = NULL; _server->_handleDisconnect(this); } +///////////////////////////////////////////////////////// + void AsyncEventSourceClient::close() { if (_client != NULL) _client->close(); } +///////////////////////////////////////////////////////// + void AsyncEventSourceClient::write(const char * message, size_t len) { _queueMessage(new AsyncEventSourceMessage(message, len)); } +///////////////////////////////////////////////////////// + void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) { String ev = generateEventMessage(message, event, id, reconnect); _queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length())); } -void AsyncEventSourceClient::_runQueue() { +///////////////////////////////////////////////////////// + +void AsyncEventSourceClient::_runQueue() +{ while (!_messageQueue.isEmpty() && _messageQueue.front()->finished()) { _messageQueue.remove(_messageQueue.front()); @@ -347,6 +383,8 @@ void AsyncEventSourceClient::_runQueue() { } } +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// // Handler @@ -359,42 +397,39 @@ AsyncEventSource::AsyncEventSource(const String& url) , _connectcb(NULL) {} +///////////////////////////////////////////////////////// + AsyncEventSource::~AsyncEventSource() { close(); } +///////////////////////////////////////////////////////// + void AsyncEventSource::onConnect(ArEventHandlerFunction cb) { _connectcb = cb; } +///////////////////////////////////////////////////////// + void AsyncEventSource::_addClient(AsyncEventSourceClient * client) { - /*char * temp = (char *)malloc(2054); - if(temp != NULL){ - memset(temp+1,' ',2048); - temp[0] = ':'; - temp[2049] = '\r'; - temp[2050] = '\n'; - temp[2051] = '\r'; - temp[2052] = '\n'; - temp[2053] = 0; - client->write((const char *)temp, 2053); - free(temp); - }*/ - _clients.add(client); if (_connectcb) _connectcb(client); } +///////////////////////////////////////////////////////// + void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client) { _clients.remove(client); } +///////////////////////////////////////////////////////// + void AsyncEventSource::close() { for (const auto &c : _clients) @@ -404,6 +439,8 @@ void AsyncEventSource::close() } } +///////////////////////////////////////////////////////// + // pmb fix size_t AsyncEventSource::avgPacketsWaiting() const { @@ -425,6 +462,8 @@ size_t AsyncEventSource::avgPacketsWaiting() const return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up } +///////////////////////////////////////////////////////// + void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) { String ev = generateEventMessage(message, event, id, reconnect); @@ -438,6 +477,8 @@ void AsyncEventSource::send(const char *message, const char *event, uint32_t id, } } +///////////////////////////////////////////////////////// + size_t AsyncEventSource::count() const { return _clients.count_if([](AsyncEventSourceClient * c) @@ -446,6 +487,8 @@ size_t AsyncEventSource::count() const }); } +///////////////////////////////////////////////////////// + bool AsyncEventSource::canHandle(AsyncWebServerRequest *request) { if (request->method() != HTTP_GET || !request->url().equals(_url)) @@ -458,6 +501,8 @@ bool AsyncEventSource::canHandle(AsyncWebServerRequest *request) return true; } +///////////////////////////////////////////////////////// + void AsyncEventSource::handleRequest(AsyncWebServerRequest *request) { if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) @@ -466,6 +511,9 @@ void AsyncEventSource::handleRequest(AsyncWebServerRequest *request) request->send(new AsyncEventSourceResponse(this)); } +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + // Response AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server) @@ -478,6 +526,8 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server) addHeader("Connection", "keep-alive"); } +///////////////////////////////////////////////////////// + void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request) { String out = _assembleHead(request->version()); @@ -485,6 +535,8 @@ void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request) _state = RESPONSE_WAIT_ACK; } +///////////////////////////////////////////////////////// + size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))) { if (len) @@ -495,4 +547,5 @@ size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len return 0; } +///////////////////////////////////////////////////////// diff --git a/src/AsyncEventSource_Ethernet.h b/src/AsyncEventSource_Ethernet.h index bf32ffd..21139ae 100644 --- a/src/AsyncEventSource_Ethernet.h +++ b/src/AsyncEventSource_Ethernet.h @@ -12,14 +12,16 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once @@ -39,17 +41,23 @@ // Ethernet #include +///////////////////////////////////////////////////////// + #define SSE_MAX_QUEUED_MESSAGES 32 //#define SSE_MAX_QUEUED_MESSAGES 8 #define DEFAULT_MAX_SSE_CLIENTS 8 //#define DEFAULT_MAX_SSE_CLIENTS 4 +///////////////////////////////////////////////////////// + class AsyncEventSource; class AsyncEventSourceResponse; class AsyncEventSourceClient; typedef std::function ArEventHandlerFunction; +///////////////////////////////////////////////////////// + class AsyncEventSourceMessage { private: @@ -65,17 +73,24 @@ class AsyncEventSourceMessage size_t ack(size_t len, uint32_t time __attribute__((unused))); size_t send(AsyncClient *client); - bool finished() + ///////////////////////////////////////////////// + + inline bool finished() { return _acked == _len; } - bool sent() + ///////////////////////////////////////////////// + + inline bool sent() { return _sent == _len; } }; +///////////////////////////////////////////////// +///////////////////////////////////////////////// + class AsyncEventSourceClient { private: @@ -91,30 +106,42 @@ class AsyncEventSourceClient AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server); ~AsyncEventSourceClient(); - AsyncClient* client() + ///////////////////////////////////////////////// + + inline AsyncClient* client() { return _client; } + ///////////////////////////////////////////////// + void close(); void write(const char * message, size_t len); void send(const char *message, const char *event = NULL, uint32_t id = 0, uint32_t reconnect = 0); - bool connected() const + ///////////////////////////////////////////////// + + inline bool connected() const { return (_client != NULL) && _client->connected(); } - uint32_t lastId() const + ///////////////////////////////////////////////// + + inline uint32_t lastId() const { return _lastId; } - size_t packetsWaiting() const + ///////////////////////////////////////////////// + + inline size_t packetsWaiting() const { return _messageQueue.length(); } + ///////////////////////////////////////////////// + //system callbacks (do not call) void _onAck(size_t len, uint32_t time); void _onPoll(); @@ -122,6 +149,9 @@ class AsyncEventSourceClient void _onDisconnect(); }; +///////////////////////////////////////////////// +///////////////////////////////////////////////// + class AsyncEventSource: public AsyncWebHandler { private: @@ -133,11 +163,15 @@ class AsyncEventSource: public AsyncWebHandler AsyncEventSource(const String& url); ~AsyncEventSource(); - const char * url() const + ///////////////////////////////////////////////// + + inline const char * url() const { return _url.c_str(); } + ///////////////////////////////////////////////// + void close(); void onConnect(ArEventHandlerFunction cb); void send(const char *message, const char *event = NULL, uint32_t id = 0, uint32_t reconnect = 0); @@ -151,6 +185,9 @@ class AsyncEventSource: public AsyncWebHandler virtual void handleRequest(AsyncWebServerRequest *request) override final; }; +///////////////////////////////////////////////// +///////////////////////////////////////////////// + class AsyncEventSourceResponse: public AsyncWebServerResponse { private: @@ -162,7 +199,9 @@ class AsyncEventSourceResponse: public AsyncWebServerResponse void _respond(AsyncWebServerRequest *request); size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); - bool _sourceValid() const + ///////////////////////////////////////////////// + + inline bool _sourceValid() const { return true; } diff --git a/src/AsyncJson_Ethernet.h b/src/AsyncJson_Ethernet.h index 2c3aa65..495a1a2 100644 --- a/src/AsyncJson_Ethernet.h +++ b/src/AsyncJson_Ethernet.h @@ -12,14 +12,16 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ /* Async Response to use with ArduinoJson and AsyncWebServer @@ -60,19 +62,25 @@ #ifndef ASYNC_JSON_ETHERNET_H_ #define ASYNC_JSON_ETHERNET_H_ +///////////////////////////////////////////////////////// + #include #include #include +///////////////////////////////////////////////////////// + #if ARDUINOJSON_VERSION_MAJOR == 5 -#define ARDUINOJSON_5_COMPATIBILITY + #define ARDUINOJSON_5_COMPATIBILITY #else -#define DYNAMIC_JSON_DOCUMENT_SIZE 1024 + #define DYNAMIC_JSON_DOCUMENT_SIZE 1024 #endif constexpr const char* JSON_MIMETYPE = "application/json"; +///////////////////////////////////////////////// + /* Json Response * */ @@ -91,6 +99,8 @@ class ChunkPrint : public Print virtual ~ChunkPrint() {} + ///////////////////////////////////////////////// + size_t write(uint8_t c) { if (_to_skip > 0) @@ -110,12 +120,16 @@ class ChunkPrint : public Print return 0; } - size_t write(const uint8_t *buffer, size_t size) + ///////////////////////////////////////////////// + + inline size_t write(const uint8_t *buffer, size_t size) { return this->Print::write(buffer, size); } }; +///////////////////////////////////////////////// + class AsyncJsonResponse: public AsyncAbstractResponse { protected: @@ -131,6 +145,8 @@ class AsyncJsonResponse: public AsyncAbstractResponse public: + ///////////////////////////////////////////////// + #ifdef ARDUINOJSON_5_COMPATIBILITY AsyncJsonResponse(bool isArray = false): _isValid {false} { @@ -155,17 +171,25 @@ class AsyncJsonResponse: public AsyncAbstractResponse } #endif + ///////////////////////////////////////////////// + ~AsyncJsonResponse() {} + + ///////////////////////////////////////////////// - JsonVariant & getRoot() + inline JsonVariant & getRoot() { return _root; } + + ///////////////////////////////////////////////// - bool _sourceValid() const + inline bool _sourceValid() const { return _isValid; } + + ///////////////////////////////////////////////// size_t setLength() { @@ -184,11 +208,15 @@ class AsyncJsonResponse: public AsyncAbstractResponse return _contentLength; } - size_t getSize() + ///////////////////////////////////////////////// + + inline size_t getSize() { return _jsonBuffer.size(); } + ///////////////////////////////////////////////// + size_t _fillBuffer(uint8_t *data, size_t len) { ChunkPrint dest(data, _sentLength, len); @@ -200,8 +228,14 @@ class AsyncJsonResponse: public AsyncAbstractResponse #endif return len; } + + ///////////////////////////////////////////////// + }; +///////////////////////////////////////////////// +///////////////////////////////////////////////// + class PrettyAsyncJsonResponse: public AsyncJsonResponse { public: @@ -211,6 +245,8 @@ class PrettyAsyncJsonResponse: public AsyncJsonResponse PrettyAsyncJsonResponse (bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse {isArray, maxJsonBufferSize} {} #endif + ///////////////////////////////////////////////// + size_t setLength () { #ifdef ARDUINOJSON_5_COMPATIBILITY @@ -226,6 +262,8 @@ class PrettyAsyncJsonResponse: public AsyncJsonResponse return _contentLength; } + + ///////////////////////////////////////////////// size_t _fillBuffer (uint8_t *data, size_t len) { @@ -241,8 +279,13 @@ class PrettyAsyncJsonResponse: public AsyncJsonResponse } }; +///////////////////////////////////////////////// + typedef std::function ArJsonRequestHandlerFunction; +///////////////////////////////////////////////// +///////////////////////////////////////////////// + class AsyncCallbackJsonWebHandler: public AsyncWebHandler { private: @@ -259,6 +302,8 @@ class AsyncCallbackJsonWebHandler: public AsyncWebHandler size_t _maxContentLength; public: + + ///////////////////////////////////////////////// #ifdef ARDUINOJSON_5_COMPATIBILITY AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest) @@ -268,21 +313,29 @@ class AsyncCallbackJsonWebHandler: public AsyncWebHandler : _uri(uri), _method(HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {} #endif - void setMethod(WebRequestMethodComposite method) + ///////////////////////////////////////////////// + + inline void setMethod(WebRequestMethodComposite method) { _method = method; } + + ///////////////////////////////////////////////// - void setMaxContentLength(int maxContentLength) + inline void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; } + + ///////////////////////////////////////////////// - void onRequest(ArJsonRequestHandlerFunction fn) + inline void onRequest(ArJsonRequestHandlerFunction fn) { _onRequest = fn; } + ///////////////////////////////////////////////// + virtual bool canHandle(AsyncWebServerRequest *request) override final { if (!_onRequest) @@ -302,6 +355,8 @@ class AsyncCallbackJsonWebHandler: public AsyncWebHandler return true; } + ///////////////////////////////////////////////// + virtual void handleRequest(AsyncWebServerRequest *request) override final { if (_onRequest) @@ -337,10 +392,14 @@ class AsyncCallbackJsonWebHandler: public AsyncWebHandler request->send(500); } } + + ///////////////////////////////////////////////// virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final { } + + ///////////////////////////////////////////////// virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { @@ -359,6 +418,8 @@ class AsyncCallbackJsonWebHandler: public AsyncWebHandler } } } + + ///////////////////////////////////////////////// virtual bool isRequestHandlerTrivial() override final { diff --git a/src/AsyncWebAuthentication_Ethernet.cpp b/src/AsyncWebAuthentication_Ethernet.cpp index 16bec23..b6184c7 100644 --- a/src/AsyncWebAuthentication_Ethernet.cpp +++ b/src/AsyncWebAuthentication_Ethernet.cpp @@ -12,14 +12,16 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #define _AWS_ETHERNET_LOGLEVEL_ 1 @@ -34,6 +36,8 @@ #include "Crypto/bearssl_hash.h" #include "Crypto/Hash.h" +///////////////////////////////////////////////// + // Basic Auth hash = base64("username:password") bool checkBasicAuthentication(const char * hash, const char * username, const char * password) @@ -41,6 +45,7 @@ bool checkBasicAuthentication(const char * hash, const char * username, const ch if (username == NULL || password == NULL || hash == NULL) { LOGDEBUG("checkBasicAuthentication: Fail: NULL username/password/hash"); + return false; } @@ -70,6 +75,7 @@ bool checkBasicAuthentication(const char * hash, const char * username, const ch LOGDEBUG("checkBasicAuthentication: NULL encoded"); delete[] toencode; + return false; } @@ -81,6 +87,7 @@ bool checkBasicAuthentication(const char * hash, const char * username, const ch delete[] toencode; delete[] encoded; + return true; } @@ -88,9 +95,12 @@ bool checkBasicAuthentication(const char * hash, const char * username, const ch delete[] toencode; delete[] encoded; + return false; } +///////////////////////////////////////////////// + static bool getMD5(uint8_t * data, uint16_t len, char * output) { //33 bytes or more @@ -127,6 +137,8 @@ static bool getMD5(uint8_t * data, uint16_t len, char * output) return true; } +///////////////////////////////////////////////// + static String genRandomMD5() { // For Ethernet @@ -145,6 +157,8 @@ static String genRandomMD5() return res; } +///////////////////////////////////////////////// + static String stringMD5(const String& in) { char * out = (char*) malloc(33); @@ -160,6 +174,8 @@ static String stringMD5(const String& in) return res; } +///////////////////////////////////////////////// + String generateDigestHash(const char * username, const char * password, const char * realm) { if (username == NULL || password == NULL || realm == NULL) @@ -189,6 +205,8 @@ String generateDigestHash(const char * username, const char * password, const ch return res; } +///////////////////////////////////////////////// + String requestDigestAuthentication(const char * realm) { String header = "realm=\""; @@ -209,8 +227,10 @@ String requestDigestAuthentication(const char * realm) return header; } +///////////////////////////////////////////////// + bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, - const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri) + const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri) { if (username == NULL || password == NULL || header == NULL || method == NULL) { diff --git a/src/AsyncWebAuthentication_Ethernet.h b/src/AsyncWebAuthentication_Ethernet.h index 5ccbe09..b0006b0 100644 --- a/src/AsyncWebAuthentication_Ethernet.h +++ b/src/AsyncWebAuthentication_Ethernet.h @@ -12,14 +12,16 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once @@ -30,11 +32,18 @@ #include "Arduino.h" #include "AsyncWebServer_Ethernet_Debug.h" +///////////////////////////////////////////////// + bool checkBasicAuthentication(const char * header, const char * username, const char * password); + String requestDigestAuthentication(const char * realm); -bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri); + +bool checkDigestAuthentication(const char * header, const char * method, const char * username, const char * password, + const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri); //for storing hashed versions on the device that can be authenticated against String generateDigestHash(const char * username, const char * password, const char * realm); +///////////////////////////////////////////////// + #endif // ASYNCWEB_AUTHENTICATION_ETHERNET_H_ diff --git a/src/AsyncWebHandlerImpl_Ethernet.h b/src/AsyncWebHandlerImpl_Ethernet.h index 91d47f5..7b21a03 100644 --- a/src/AsyncWebHandlerImpl_Ethernet.h +++ b/src/AsyncWebHandlerImpl_Ethernet.h @@ -12,14 +12,16 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once @@ -36,6 +38,8 @@ #include "stddef.h" #include +///////////////////////////////////////////////// + class AsyncStaticWebHandler: public AsyncWebHandler { private: @@ -63,6 +67,8 @@ class AsyncStaticWebHandler: public AsyncWebHandler AsyncStaticWebHandler& setLastModified(time_t last_modified); AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated + ///////////////////////////////////////////////// + AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) { _callback = newCallback; @@ -84,31 +90,44 @@ class AsyncCallbackWebHandler: public AsyncWebHandler public: AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} - void setUri(const String& uri) + ///////////////////////////////////////////////// + + inline void setUri(const String& uri) { _uri = uri; _isRegex = uri.startsWith("^") && uri.endsWith("$"); } - void setMethod(WebRequestMethodComposite method) + ///////////////////////////////////////////////// + + inline void setMethod(WebRequestMethodComposite method) { _method = method; } - void onRequest(ArRequestHandlerFunction fn) + + ///////////////////////////////////////////////// + + inline void onRequest(ArRequestHandlerFunction fn) { _onRequest = fn; } - void onUpload(ArUploadHandlerFunction fn) + ///////////////////////////////////////////////// + + inline void onUpload(ArUploadHandlerFunction fn) { _onUpload = fn; } - void onBody(ArBodyHandlerFunction fn) + ///////////////////////////////////////////////// + + inline void onBody(ArBodyHandlerFunction fn) { _onBody = fn; } + ///////////////////////////////////////////////// + virtual bool canHandle(AsyncWebServerRequest *request) override final { if (!_onRequest) @@ -155,6 +174,8 @@ class AsyncCallbackWebHandler: public AsyncWebHandler return true; } + ///////////////////////////////////////////////// + virtual void handleRequest(AsyncWebServerRequest *request) override final { if (_onRequest) @@ -163,12 +184,16 @@ class AsyncCallbackWebHandler: public AsyncWebHandler request->send(500); } + ///////////////////////////////////////////////// + virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { if (_onBody) _onBody(request, data, len, index, total); } + ///////////////////////////////////////////////// + virtual bool isRequestHandlerTrivial() override final { return _onRequest ? false : true; diff --git a/src/AsyncWebHandlers_Ethernet.cpp b/src/AsyncWebHandlers_Ethernet.cpp index 547fa4c..2a0b032 100644 --- a/src/AsyncWebHandlers_Ethernet.cpp +++ b/src/AsyncWebHandlers_Ethernet.cpp @@ -12,23 +12,29 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ -#define _AWS_ETHERNET_LOGLEVEL_ 1 +#if !defined(_AWS_ETHERNET_LOGLEVEL_) + #define _AWS_ETHERNET_LOGLEVEL_ 1 +#endif #include "AsyncWebServer_Ethernet_Debug.h" #include "AsyncWebServer_Ethernet.hpp" #include "AsyncWebHandlerImpl_Ethernet.h" +///////////////////////////////////////////////// + AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, /*FS& fs,*/ const char* path, const char* cache_control) : _uri(uri), _path(path), _cache_control(cache_control), _last_modified(""), _callback(nullptr) { @@ -56,12 +62,16 @@ AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, /*FS& fs,*/ const _gzipStats = 0xF8; } +///////////////////////////////////////////////// + AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir) { _isDir = isDir; return *this; } +///////////////////////////////////////////////// + AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control) { _cache_control = String(cache_control); @@ -69,6 +79,8 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_ return *this; } +///////////////////////////////////////////////// + AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified) { _last_modified = String(last_modified); @@ -76,6 +88,8 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_m return *this; } +///////////////////////////////////////////////// + AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified) { char result[30]; @@ -85,6 +99,8 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_mo return setLastModified((const char *)result); } +///////////////////////////////////////////////// + // For Ethernet AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified) { @@ -101,6 +117,8 @@ AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified() return setLastModified(last_modified); } +///////////////////////////////////////////////// + bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request) { if (request->method() != HTTP_GET @@ -114,9 +132,13 @@ bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request) return false; } +///////////////////////////////////////////////// + // For Ethernet #define FILE_IS_REAL(f) (f == true) +///////////////////////////////////////////////// + uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const { uint8_t w = value; diff --git a/src/AsyncWebRequest_Ethernet.cpp b/src/AsyncWebRequest_Ethernet.cpp index 09fb00e..a446975 100644 --- a/src/AsyncWebRequest_Ethernet.cpp +++ b/src/AsyncWebRequest_Ethernet.cpp @@ -12,17 +12,21 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ -#define _AWS_ETHERNET_LOGLEVEL_ 1 +#if !defined(_AWS_ETHERNET_LOGLEVEL_) + #define _AWS_ETHERNET_LOGLEVEL_ 1 +#endif #include "AsyncWebServer_Ethernet_Debug.h" @@ -30,12 +34,16 @@ #include "AsyncWebResponseImpl_Ethernet.h" #include "AsyncWebAuthentication_Ethernet.h" +///////////////////////////////////////////////////////// + //static const String SharedEmptyString = String(); #define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '=')) enum { PARSE_REQ_START, PARSE_REQ_HEADERS, PARSE_REQ_BODY, PARSE_REQ_END, PARSE_REQ_FAIL }; +///////////////////////////////////////////////////////// + AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c) : _client(c), _server(s), _handler(NULL), _response(NULL), _temp(), _parseState(0) , _version(0), _method(HTTP_ANY), _url(), _host(), _contentType(), _boundary() @@ -58,14 +66,14 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c) { c->onError([](void *r, AsyncClient * c, int8_t error) { - (void)c; + AWS_ETHERNET_UNUSED(c); AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this); c->onAck([](void *r, AsyncClient * c, size_t len, uint32_t time) { - (void)c; + AWS_ETHERNET_UNUSED(c); AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this); @@ -79,26 +87,28 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c) c->onTimeout([](void *r, AsyncClient * c, uint32_t time) { - (void)c; + AWS_ETHERNET_UNUSED(c); AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this); c->onData([](void *r, AsyncClient * c, void *buf, size_t len) { - (void)c; + AWS_ETHERNET_UNUSED(c); AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this); c->onPoll([](void *r, AsyncClient * c) { - (void)c; + AWS_ETHERNET_UNUSED(c); AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this); } +///////////////////////////////////////////////////////// + AsyncWebServerRequest::~AsyncWebServerRequest() { _headers.free(); @@ -119,6 +129,8 @@ AsyncWebServerRequest::~AsyncWebServerRequest() } } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::_onData(void *buf, size_t len) { size_t i = 0; @@ -246,6 +258,8 @@ void AsyncWebServerRequest::_onData(void *buf, size_t len) } } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::_removeNotInterestingHeaders() { if (_interestingHeaders.containsIgnoreCase("ANY")) @@ -260,6 +274,8 @@ void AsyncWebServerRequest::_removeNotInterestingHeaders() } } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::_onPoll() { LOGDEBUG("onPoll"); @@ -270,6 +286,8 @@ void AsyncWebServerRequest::_onPoll() } } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::_onAck(size_t len, uint32_t time) { LOGDEBUG3("onAck: len =", len, ", time =", time); @@ -289,25 +307,33 @@ void AsyncWebServerRequest::_onAck(size_t len, uint32_t time) } } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::_onError(int8_t error) { - (void)error; + AWS_ETHERNET_UNUSED(error); } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::_onTimeout(uint32_t time) { - (void)time; + AWS_ETHERNET_UNUSED(time); LOGDEBUG3("TIMEOUT: time =", time, ", state =", _client->stateToString()); _client->close(); } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::onDisconnect (ArDisconnectHandler fn) { _onDisconnectfn = fn; } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::_onDisconnect() { LOGDEBUG("_onDisconnect"); @@ -320,16 +346,22 @@ void AsyncWebServerRequest::_onDisconnect() _server->_handleDisconnect(this); } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::_addParam(AsyncWebParameter *p) { _params.add(p); } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::_addPathParam(const char *p) { _pathParams.add(new String(p)); } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::_addGetParams(const String& params) { size_t start = 0; @@ -353,6 +385,8 @@ void AsyncWebServerRequest::_addGetParams(const String& params) } } +///////////////////////////////////////////////////////// + bool AsyncWebServerRequest::_parseReqHead() { // Split the head into method, url and version @@ -411,6 +445,8 @@ bool AsyncWebServerRequest::_parseReqHead() return true; } +///////////////////////////////////////////////////////// + bool strContains(String src, String find, bool mindcase = true) { int pos = 0, i = 0; @@ -442,6 +478,8 @@ bool strContains(String src, String find, bool mindcase = true) return false; } +///////////////////////////////////////////////////////// + bool AsyncWebServerRequest::_parseReqHeader() { int index = _temp.indexOf(':'); @@ -511,6 +549,8 @@ bool AsyncWebServerRequest::_parseReqHeader() return true; } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) { if (data && (char)data != '&') @@ -532,6 +572,8 @@ void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) } } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last) { _itemBuffer[_itemBufferIndex++] = data; @@ -546,6 +588,8 @@ void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last) } } +///////////////////////////////////////////////////////// + enum { EXPECT_BOUNDARY, @@ -561,6 +605,7 @@ enum PARSE_ERROR }; +///////////////////////////////////////////////////////// void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) { @@ -845,6 +890,8 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) } } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::_parseLine() { if (_parseState == PARSE_REQ_START) @@ -899,12 +946,15 @@ void AsyncWebServerRequest::_parseLine() } } +///////////////////////////////////////////////////////// size_t AsyncWebServerRequest::headers() const { return _headers.length(); } +///////////////////////////////////////////////////////// + bool AsyncWebServerRequest::hasHeader(const String& name) const { for (const auto& h : _headers) @@ -918,6 +968,8 @@ bool AsyncWebServerRequest::hasHeader(const String& name) const return false; } +///////////////////////////////////////////////////////// + AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) const { for (const auto& h : _headers) @@ -931,6 +983,8 @@ AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) const return nullptr; } +///////////////////////////////////////////////////////// + AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const { auto header = _headers.nth(num); @@ -938,11 +992,14 @@ AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const return (header ? *header : nullptr); } +///////////////////////////////////////////////////////// + size_t AsyncWebServerRequest::params() const { return _params.length(); } +///////////////////////////////////////////////////////// bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) const { @@ -957,6 +1014,8 @@ bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) c return false; } +///////////////////////////////////////////////////////// + AsyncWebParameter* AsyncWebServerRequest::getParam(const String& name, bool post, bool file) const { for (const auto& p : _params) @@ -970,6 +1029,8 @@ AsyncWebParameter* AsyncWebServerRequest::getParam(const String& name, bool post return nullptr; } +///////////////////////////////////////////////////////// + AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const { auto param = _params.nth(num); @@ -977,12 +1038,16 @@ AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const return (param ? *param : nullptr); } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::addInterestingHeader(const String& name) { if (!_interestingHeaders.containsIgnoreCase(name)) _interestingHeaders.add(name); } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::send(AsyncWebServerResponse *response) { _response = response; @@ -1008,32 +1073,65 @@ void AsyncWebServerRequest::send(AsyncWebServerResponse *response) } } -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback) +///////////////////////////////////////////////////////// + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, + AwsTemplateProcessor callback) { return new AsyncProgmemResponse(code, contentType, content, len, callback); } -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback) +///////////////////////////////////////////////////////// + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_P content, + AwsTemplateProcessor callback) { return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content), callback); } +///////////////////////////////////////////////////////// + +//RSMOD/////////////////////////////////////////////// + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const String& contentType, const char * content) +{ + return new AsyncBasicResponse(code, contentType, content); +} +///////////////////////////////////////////////// + AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const String& contentType, const String& content) { return new AsyncBasicResponse(code, contentType, content); } -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback) +///////////////////////////////////////////////// +// KH add for favicon +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const String& contentType, const uint8_t * content, size_t len, + AwsTemplateProcessor callback) +{ + return new AsyncProgmemResponse(code, contentType, content, len, callback); +} + +///////////////////////////////////////////////// + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len, + AwsTemplateProcessor callback) { return new AsyncStreamResponse(stream, contentType, len, callback); } -AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) +///////////////////////////////////////////////////////// + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, + AwsTemplateProcessor templateCallback) { return new AsyncCallbackResponse(contentType, len, callback, templateCallback); } -AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) +///////////////////////////////////////////////////////// + +AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, + AwsTemplateProcessor templateCallback) { if (_version) return new AsyncChunkedResponse(contentType, callback, templateCallback); @@ -1041,31 +1139,60 @@ AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const Strin return new AsyncCallbackResponse(contentType, 0, callback, templateCallback); } +///////////////////////////////////////////////////////// + AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize) { return new AsyncResponseStream(contentType, bufferSize); } +//RSMOD/////////////////////////////////////////////// + +void AsyncWebServerRequest::send(int code, const String& contentType, const char *content, bool nonCopyingSend) +{ + if (nonCopyingSend) + { + send(beginResponse(code, contentType, String(content))); // for backwards compatibility + } + else + { + send(beginResponse(code, contentType, content)); + } +} + +///////////////////////////////////////////////// + void AsyncWebServerRequest::send(int code, const String& contentType, const String& content) { send(beginResponse(code, contentType, content)); } -void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback) +///////////////////////////////////////////////////////// + +void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len, + AwsTemplateProcessor callback) { send(beginResponse(stream, contentType, len, callback)); } -void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) +///////////////////////////////////////////////////////// + +void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, + AwsTemplateProcessor templateCallback) { send(beginResponse(contentType, len, callback, templateCallback)); } -void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) +///////////////////////////////////////////////////////// + +void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, + AwsTemplateProcessor templateCallback) { send(beginChunkedResponse(contentType, callback, templateCallback)); } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::redirect(const String& url) { AsyncWebServerResponse * response = beginResponse(302); @@ -1073,6 +1200,8 @@ void AsyncWebServerRequest::redirect(const String& url) send(response); } +///////////////////////////////////////////////////////// + bool AsyncWebServerRequest::authenticate(const char * username, const char * password, const char * realm, bool passwordIsHash) { LOGDEBUG1("AsyncWebServerRequest::authenticate: auth-len =", _authorization.length()); @@ -1104,6 +1233,8 @@ bool AsyncWebServerRequest::authenticate(const char * username, const char * pas return false; } +///////////////////////////////////////////////////////// + bool AsyncWebServerRequest::authenticate(const char * hash) { if (!_authorization.length() || hash == NULL) @@ -1133,6 +1264,8 @@ bool AsyncWebServerRequest::authenticate(const char * hash) return (_authorization.equals(hash)); } +///////////////////////////////////////////////////////// + void AsyncWebServerRequest::requestAuthentication(const char * realm, bool isDigest) { AsyncWebServerResponse * r = beginResponse(401); @@ -1158,6 +1291,8 @@ void AsyncWebServerRequest::requestAuthentication(const char * realm, bool isDig send(r); } +///////////////////////////////////////////////////////// + bool AsyncWebServerRequest::hasArg(const char* name) const { for (const auto& arg : _params) @@ -1171,6 +1306,8 @@ bool AsyncWebServerRequest::hasArg(const char* name) const return false; } +///////////////////////////////////////////////////////// + const String& AsyncWebServerRequest::arg(const String& name) const { for (const auto& arg : _params) @@ -1184,16 +1321,22 @@ const String& AsyncWebServerRequest::arg(const String& name) const return SharedEmptyString; } +///////////////////////////////////////////////////////// + const String& AsyncWebServerRequest::arg(size_t i) const { return getParam(i)->value(); } +///////////////////////////////////////////////////////// + const String& AsyncWebServerRequest::argName(size_t i) const { return getParam(i)->name(); } +///////////////////////////////////////////////////////// + const String& AsyncWebServerRequest::pathArg(size_t i) const { auto param = _pathParams.nth(i); @@ -1201,6 +1344,8 @@ const String& AsyncWebServerRequest::pathArg(size_t i) const return (param ? **param : SharedEmptyString); } +///////////////////////////////////////////////////////// + const String& AsyncWebServerRequest::header(const char* name) const { AsyncWebHeader* h = getHeader(String(name)); @@ -1208,6 +1353,8 @@ const String& AsyncWebServerRequest::header(const char* name) const return (h ? h->value() : SharedEmptyString); } +///////////////////////////////////////////////////////// + const String& AsyncWebServerRequest::header(size_t i) const { AsyncWebHeader* h = getHeader(i); @@ -1215,6 +1362,8 @@ const String& AsyncWebServerRequest::header(size_t i) const return (h ? h->value() : SharedEmptyString); } +///////////////////////////////////////////////////////// + const String& AsyncWebServerRequest::headerName(size_t i) const { AsyncWebHeader* h = getHeader(i); @@ -1222,6 +1371,8 @@ const String& AsyncWebServerRequest::headerName(size_t i) const return (h ? h->name() : SharedEmptyString); } +///////////////////////////////////////////////////////// + String AsyncWebServerRequest::urlDecode(const String& text) const { char temp[] = "0x00"; @@ -1256,6 +1407,7 @@ String AsyncWebServerRequest::urlDecode(const String& text) const return decoded; } +///////////////////////////////////////////////////////// const char * AsyncWebServerRequest::methodToString() const { @@ -1279,6 +1431,8 @@ const char * AsyncWebServerRequest::methodToString() const return "UNKNOWN"; } +///////////////////////////////////////////////////////// + const char *AsyncWebServerRequest::requestedConnTypeToString() const { switch (_reqconntype) @@ -1298,7 +1452,10 @@ const char *AsyncWebServerRequest::requestedConnTypeToString() const } } -bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) +///////////////////////////////////////////////////////// + +bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, + RequestedConnectionType erct3) { bool res = false; diff --git a/src/AsyncWebResponseImpl_Ethernet.h b/src/AsyncWebResponseImpl_Ethernet.h index a7ebe01..14254a5 100644 --- a/src/AsyncWebResponseImpl_Ethernet.h +++ b/src/AsyncWebResponseImpl_Ethernet.h @@ -12,14 +12,16 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once @@ -34,24 +36,36 @@ #endif #include + // It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max. +///////////////////////////////////////////////// class AsyncBasicResponse: public AsyncWebServerResponse { private: String _content; + + char *_contentCstr; // RSMOD + String _partialHeader; public: AsyncBasicResponse(int code, const String& contentType = String(), const String& content = String()); + + AsyncBasicResponse(int code, const String& contentType, const char *content = nullptr); // RSMOD + void _respond(AsyncWebServerRequest *request); size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); - bool _sourceValid() const + ///////////////////////////////////////////////// + + inline bool _sourceValid() const { return true; } + + ///////////////////////////////////////////////// }; class AsyncAbstractResponse: public AsyncWebServerResponse @@ -74,23 +88,33 @@ class AsyncAbstractResponse: public AsyncWebServerResponse void _respond(AsyncWebServerRequest *request); size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); - bool _sourceValid() const + ///////////////////////////////////////////////// + + inline bool _sourceValid() const { return false; } - virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) + ///////////////////////////////////////////////// + + virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; } + + ///////////////////////////////////////////////// }; +///////////////////////////////////////////////// + #ifndef TEMPLATE_PLACEHOLDER #define TEMPLATE_PLACEHOLDER '%' #endif #define TEMPLATE_PARAM_NAME_LENGTH 32 +///////////////////////////////////////////////// + class AsyncStreamResponse: public AsyncAbstractResponse { private: @@ -99,14 +123,20 @@ class AsyncStreamResponse: public AsyncAbstractResponse public: AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr); - bool _sourceValid() const + ///////////////////////////////////////////////// + + inline bool _sourceValid() const { return !!(_content); } + ///////////////////////////////////////////////// + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; }; +///////////////////////////////////////////////// + class AsyncCallbackResponse: public AsyncAbstractResponse { private: @@ -116,14 +146,20 @@ class AsyncCallbackResponse: public AsyncAbstractResponse public: AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - bool _sourceValid() const + ///////////////////////////////////////////////// + + inline bool _sourceValid() const { return !!(_content); } + ///////////////////////////////////////////////// + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; }; +///////////////////////////////////////////////// + class AsyncChunkedResponse: public AsyncAbstractResponse { private: @@ -133,16 +169,24 @@ class AsyncChunkedResponse: public AsyncAbstractResponse public: AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - bool _sourceValid() const + ///////////////////////////////////////////////// + + inline bool _sourceValid() const { return !!(_content); } + ///////////////////////////////////////////////// + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; }; +///////////////////////////////////////////////// + class cbuf; +///////////////////////////////////////////////// + class AsyncProgmemResponse: public AsyncAbstractResponse { private: @@ -151,10 +195,21 @@ class AsyncProgmemResponse: public AsyncAbstractResponse public: AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); - bool _sourceValid() const { return true; } + + ///////////////////////////////////////////////// + + inline bool _sourceValid() const + { + return (_state < RESPONSE_END); + } + + ///////////////////////////////////////////////// + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; }; +///////////////////////////////////////////////// + class AsyncResponseStream: public AsyncAbstractResponse, public Print { private: @@ -164,15 +219,21 @@ class AsyncResponseStream: public AsyncAbstractResponse, public Print AsyncResponseStream(const String& contentType, size_t bufferSize); ~AsyncResponseStream(); - bool _sourceValid() const + ///////////////////////////////////////////////// + + inline bool _sourceValid() const { return (_state < RESPONSE_END); } + ///////////////////////////////////////////////// + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; size_t write(const uint8_t *data, size_t len); size_t write(uint8_t data); using Print::write; }; +///////////////////////////////////////////////// + #endif /* ASYNCWEBSERVERRESPONSEIMPL_ETHERNET_H_ */ diff --git a/src/AsyncWebResponses_Ethernet.cpp b/src/AsyncWebResponses_Ethernet.cpp index c88fa0f..ad7486c 100644 --- a/src/AsyncWebResponses_Ethernet.cpp +++ b/src/AsyncWebResponses_Ethernet.cpp @@ -12,17 +12,21 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ -#define _AWS_ETHERNET_LOGLEVEL_ 1 +#if !defined(_AWS_ETHERNET_LOGLEVEL_) + #define _AWS_ETHERNET_LOGLEVEL_ 1 +#endif #include "AsyncWebServer_Ethernet_Debug.h" @@ -208,12 +212,44 @@ void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request) size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) { - (void)request; - (void)len; - (void)time; + AWS_ETHERNET_UNUSED(request); + AWS_ETHERNET_UNUSED(len); + AWS_ETHERNET_UNUSED(time); + return 0; } +//////////////////////////////////////////////// +//////////////////////////////////////////////// + +//RSMOD/////////////////////////////////////////////// + +/* + String/Code Response + * */ +AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const char *content) +{ + _code = code; + _content = String(""); + _contentCstr = (char *)content; // RSMOD + _contentType = contentType; + _partialHeader = String(); + + int iLen; + + if ((iLen = strlen(_contentCstr))) + { + _contentLength = iLen; + + if (!_contentType.length()) + _contentType = "text/plain"; + } + + addHeader("Connection", "close"); +} + +///////////////////////////////////////////////// + /* String/Code Response * */ @@ -221,11 +257,16 @@ AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, cons { _code = code; _content = content; + + _contentCstr = nullptr; // RSMOD + _contentType = contentType; + _partialHeader = String(); if (_content.length()) { _contentLength = _content.length(); + if (!_contentType.length()) _contentType = "text/plain"; } @@ -233,6 +274,24 @@ AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, cons addHeader("Connection", "close"); } +///////////////////////////////////////////////// + +// KH add for favicon +#if 0 +AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const uint8_t * content, size_t len) +{ + _code = code; + _content = content; + _contentType = contentType; + + _contentLength = len; + + addHeader("Connection", "close"); +} +#endif + +///////////////////////////////////////////////// + void AsyncBasicResponse::_respond(AsyncWebServerRequest *request) { _state = RESPONSE_HEADERS; @@ -240,68 +299,215 @@ void AsyncBasicResponse::_respond(AsyncWebServerRequest *request) size_t outLen = out.length(); size_t space = request->client()->space(); + LOGDEBUG3("AsyncBasicResponse::_respond : Pre_respond, _contentLength =", _contentLength, ", out =", out ); + LOGDEBUG3("outLen =", outLen, ", _contentCstr =", _contentCstr); + if (!_contentLength && space >= outLen) { + LOGDEBUG("Step 1"); + _writtenLength += request->client()->write(out.c_str(), outLen); _state = RESPONSE_WAIT_ACK; } else if (_contentLength && space >= outLen + _contentLength) { + LOGDEBUG("Step 2"); + + if (_contentCstr) + { + _content = String(_contentCstr); // short _contentCstr - so just send as Arduino String - not much of a penalty - fall into below + } + out += _content; outLen += _contentLength; _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; } else if (space && space < outLen) { String partial = out.substring(0, space); - _content = out.substring(space) + _content; - _contentLength += outLen - space; + + LOGDEBUG("Step 3"); + + if (_contentCstr) + { + _partialHeader = out.substring(space); + } + else + { + _content = out.substring(space) + _content; + _contentLength += outLen - space; + } + + LOGDEBUG1("partial =", partial); + _writtenLength += request->client()->write(partial.c_str(), partial.length()); + _state = RESPONSE_CONTENT; } else if (space > outLen && space < (outLen + _contentLength)) { size_t shift = space - outLen; + + LOGDEBUG("Step 4"); + outLen += shift; _sentLength += shift; - out += _content.substring(0, shift); - _content = _content.substring(shift); + + if (_contentCstr) + { + char *s = (char *)malloc(shift + 1); + + if (s != nullptr) + { + strncpy(s, _contentCstr, shift); + s[shift] = '\0'; + out += String(s); + _contentCstr += shift; + + free(s); + } + else + { + LOGERROR("AsyncBasicResponse::_respond: Out of heap"); + + return; + } + } + else + { + out += _content.substring(0, shift); + _content = _content.substring(shift); + } + + LOGDEBUG1("out =", out); + _writtenLength += request->client()->write(out.c_str(), outLen); _state = RESPONSE_CONTENT; } else { - _content = out + _content; - _contentLength += outLen; + LOGDEBUG("Step 5"); + + if (_contentCstr) + { + _partialHeader = out; + } + else + { + _content = out + _content; + _contentLength += outLen; + } + _state = RESPONSE_CONTENT; } + + LOGDEBUG3("AsyncBasicResponse::_respond : Post_respond, _contentLength =", _contentLength, ", out =", out ); + LOGDEBUG3("outLen =", outLen, ", _contentCstr =", _contentCstr); } +///////////////////////////////////////////////// + size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) { - (void)time; + AWS_ETHERNET_UNUSED(time); + + LOGDEBUG1("AsyncBasicResponse::_ack : Pre_ack, _contentLength =", _contentLength); + _ackedLength += len; if (_state == RESPONSE_CONTENT) { + String out; size_t available = _contentLength - _sentLength; size_t space = request->client()->space(); + if (_partialHeader.length() > 0) + { + if (_partialHeader.length() > space) + { + // Header longer than space - send a piece of it, and make the _partialHeader = to what remains + String _subHeader; + String tmpString; + + _subHeader = _partialHeader.substring(0, space); + tmpString = _partialHeader.substring(space); + _partialHeader = tmpString; + + _writtenLength += request->client()->write(_subHeader.c_str(), space); + + return (_partialHeader.length()); + } + else + { + // _partialHeader is <= space length - therefore send the whole thing, and make the remaining length = to the _contrentLength + _writtenLength += request->client()->write(_partialHeader.c_str(), _partialHeader.length()); + + _partialHeader = String(); + + return (_contentLength); + } + } + + // if we are here - there is no _partialHJeader to send + + LOGDEBUG3("AsyncBasicResponse::_ack : available =", available, ", space =", space ); + //we can fit in this packet if (space > available) { - _writtenLength += request->client()->write(_content.c_str(), available); - _content = String(); + LOGDEBUG1("AsyncBasicResponse::_ack : Pre_ack, _contentLength =", _contentLength); + + if (_contentCstr) + { + LOGDEBUG1("In space>available : output =", _contentCstr); + + _writtenLength += request->client()->write(_contentCstr, available); + //_contentCstr[0] = '\0'; + } + else + { + _writtenLength += request->client()->write(_content.c_str(), available); + _content = String(); + } + _state = RESPONSE_WAIT_ACK; return available; } //send some data, the rest on ack - String out = _content.substring(0, space); - _content = _content.substring(space); + if (_contentCstr) + { + char *s = (char *)malloc(space + 1); + + if (s != nullptr) + { + strncpy(s, _contentCstr, space); + s[space] = '\0'; + out = String(s); + _contentCstr += space; + + free(s); + } + else + { + LOGERROR("AsyncBasicResponse::_ack: Out of heap"); + + return 0; + } + } + else + { + out = _content.substring(0, space); + _content = _content.substring(space); + } + _sentLength += space; + + LOGDEBUG1("In space>available : output =", out); + _writtenLength += request->client()->write(out.c_str(), space); return space; @@ -311,13 +517,15 @@ size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint if (_ackedLength >= _writtenLength) { _state = RESPONSE_END; - request->client()->close(true); /* Might it be required? */ } } + LOGDEBUG3("AsyncBasicResponse::_ack : Post_ack, _contentLength =", _contentLength, ", _contentCstr =", _contentCstr); + return 0; } +///////////////////////////////////////////////////////////////////////////////////////// /* Abstract Response @@ -344,7 +552,7 @@ void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request) size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) { - (void)time; + AWS_ETHERNET_UNUSED(time); if (!_sourceValid()) { diff --git a/src/AsyncWebServer_Ethernet.cpp b/src/AsyncWebServer_Ethernet.cpp index 211f1a0..db1078e 100644 --- a/src/AsyncWebServer_Ethernet.cpp +++ b/src/AsyncWebServer_Ethernet.cpp @@ -12,23 +12,31 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ -#define _AWS_ETHERNET_LOGLEVEL_ 1 +#if !defined(_AWS_ETHERNET_LOGLEVEL_) + #define _AWS_ETHERNET_LOGLEVEL_ 1 +#endif + +///////////////////////////////////////////////// #include "AsyncWebServer_Ethernet_Debug.h" #include "AsyncWebServer_Ethernet.hpp" #include "AsyncWebHandlerImpl_Ethernet.h" +///////////////////////////////////////////////// + AsyncWebServer::AsyncWebServer(uint16_t port) : _server(port), _rewrites(LinkedList([](AsyncWebRewrite * r) { @@ -49,7 +57,11 @@ AsyncWebServer::AsyncWebServer(uint16_t port) if (c == NULL) return; - c->setRxTimeout(3); + // KH set no RxTimeout for slower Firefox / network + //c->setRxTimeout(3); + c->setRxTimeout(0); + ////// + AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer*)s, c); if (r == NULL) @@ -61,6 +73,8 @@ AsyncWebServer::AsyncWebServer(uint16_t port) }, this); } +///////////////////////////////////////////////// + AsyncWebServer::~AsyncWebServer() { reset(); @@ -70,6 +84,8 @@ AsyncWebServer::~AsyncWebServer() delete _catchAllHandler; } +///////////////////////////////////////////////// + AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite) { _rewrites.add(rewrite); @@ -77,55 +93,75 @@ AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite) return *rewrite; } +///////////////////////////////////////////////// + bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite) { return _rewrites.remove(rewrite); } +///////////////////////////////////////////////// + AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to) { return addRewrite(new AsyncWebRewrite(from, to)); } +///////////////////////////////////////////////// + AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler) { _handlers.add(handler); return *handler; } +///////////////////////////////////////////////// + bool AsyncWebServer::removeHandler(AsyncWebHandler *handler) { return _handlers.remove(handler); } +///////////////////////////////////////////////// + void AsyncWebServer::begin() { _server.setNoDelay(true); _server.begin(); } +///////////////////////////////////////////////// + void AsyncWebServer::end() { _server.end(); } +///////////////////////////////////////////////// + #if ASYNC_TCP_SSL_ENABLED void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg) { _server.onSslFileRequest(cb, arg); } +///////////////////////////////////////////////// + void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password) { _server.beginSecure(cert, key, password); } #endif +///////////////////////////////////////////////// + void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request) { delete request; } +///////////////////////////////////////////////// + void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request) { for (const auto& r : _rewrites) @@ -138,6 +174,8 @@ void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request) } } +///////////////////////////////////////////////// + void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request) { for (const auto& h : _handlers) @@ -153,6 +191,7 @@ void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request) request->setHandler(_catchAllHandler); } +///////////////////////////////////////////////// AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody) @@ -169,7 +208,10 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom return *handler; } -AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload) +///////////////////////////////////////////////// + +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, + ArUploadHandlerFunction onUpload) { AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); handler->setUri(uri); @@ -181,6 +223,8 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom return *handler; } +///////////////////////////////////////////////// + AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest) { AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); @@ -192,6 +236,8 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodCom return *handler; } +///////////////////////////////////////////////// + AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest) { AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); @@ -202,16 +248,22 @@ AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFun return *handler; } +///////////////////////////////////////////////// + void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn) { _catchAllHandler->onRequest(fn); } +///////////////////////////////////////////////// + void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn) { _catchAllHandler->onBody(fn); } +///////////////////////////////////////////////// + void AsyncWebServer::reset() { _rewrites.free(); @@ -224,3 +276,6 @@ void AsyncWebServer::reset() _catchAllHandler->onBody(NULL); } } + +///////////////////////////////////////////////// + diff --git a/src/AsyncWebServer_Ethernet.h b/src/AsyncWebServer_Ethernet.h index d5b7468..1b366fc 100644 --- a/src/AsyncWebServer_Ethernet.h +++ b/src/AsyncWebServer_Ethernet.h @@ -12,23 +12,29 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #ifndef _ASYNC_WEBSERVER_ETHERNET_H_ #define _ASYNC_WEBSERVER_ETHERNET_H_ +///////////////////////////////////////////////////////// + #if defined(ESP8266) #include #endif +///////////////////////////////////////////////////////// + #include "AsyncWebServer_Ethernet.hpp" #include "AsyncWebResponseImpl_Ethernet.h" diff --git a/src/AsyncWebServer_Ethernet.hpp b/src/AsyncWebServer_Ethernet.hpp index 05b20e5..ae287c5 100644 --- a/src/AsyncWebServer_Ethernet.hpp +++ b/src/AsyncWebServer_Ethernet.hpp @@ -12,14 +12,16 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #ifndef _ASYNC_WEBSERVER_ETHERNET_HPP_ @@ -29,13 +31,17 @@ #error This code is curretly intended to run only on the ESP8266 platform #endif -#define ASYNC_WEBSERVER_ETHERNET_VERSION "AsyncWebServer_Ethernet v1.4.1" +///////////////////////////////////////////////////////// + +#define ASYNC_WEBSERVER_ETHERNET_VERSION "AsyncWebServer_Ethernet v1.5.0" #define ASYNC_WEBSERVER_ETHERNET_VERSION_MAJOR 1 -#define ASYNC_WEBSERVER_ETHERNET_VERSION_MINOR 4 -#define ASYNC_WEBSERVER_ETHERNET_VERSION_PATCH 1 +#define ASYNC_WEBSERVER_ETHERNET_VERSION_MINOR 5 +#define ASYNC_WEBSERVER_ETHERNET_VERSION_PATCH 0 + +#define ASYNC_WEBSERVER_ETHERNET_VERSION_INT 1005000 -#define ASYNC_WEBSERVER_ETHERNET_VERSION_INT 1004001 +///////////////////////////////////////////////////////// #ifndef BOARD_NAME #if defined(ARDUINO_BOARD) @@ -45,10 +51,14 @@ #endif #endif +///////////////////////////////////////////////////////// + #ifndef AWS_ETHERNET_UNUSED #define AWS_ETHERNET_UNUSED(x) (void)(x) #endif +///////////////////////////////////////////////////////// + #include "Arduino.h" #include @@ -59,6 +69,8 @@ #include "AsyncWebServer_Ethernet_Debug.h" #include "StringArray_Ethernet.h" +///////////////////////////////////////////////////////// + #ifdef ASYNCWEBSERVER_REGEX #warning Using ASYNCWEBSERVER_REGEX #define ASYNCWEBSERVER_REGEX_ATTRIBUTE @@ -81,6 +93,8 @@ class AsyncStaticWebHandler; class AsyncCallbackWebHandler; class AsyncResponseStream; +///////////////////////////////////////////////////////// + #ifndef WEBSERVER_H typedef enum { @@ -99,7 +113,10 @@ class AsyncResponseStream; #define RESPONSE_TRY_AGAIN 0xFFFFFFFF typedef uint8_t WebRequestMethodComposite; -typedef std::function ArDisconnectHandler; +typedef std::function ArDisconnectHandler; + +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// /* PARAMETER :: Chainable object to hold GET/POST and FILE parameters @@ -118,32 +135,47 @@ class AsyncWebParameter AsyncWebParameter(const String& name, const String& value, bool form = false, bool file = false, size_t size = 0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file) {} - const String& name() const + ///////////////////////////////////////////////// + + inline const String& name() const { return _name; } - const String& value() const + ///////////////////////////////////////////////// + + inline const String& value() const { return _value; } - size_t size() const + ///////////////////////////////////////////////// + + inline size_t size() const { return _size; } - bool isPost() const + ///////////////////////////////////////////////// + + inline bool isPost() const { return _isForm; } - bool isFile() const + ///////////////////////////////////////////////// + + inline bool isFile() const { return _isFile; } + + ///////////////////////////////////////////////// }; +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + /* HEADER :: Chainable object to hold the headers * */ @@ -157,38 +189,53 @@ class AsyncWebHeader public: AsyncWebHeader(const String& name, const String& value): _name(name), _value(value) {} - AsyncWebHeader(const String& data): _name(), _value() + ///////////////////////////////////////////////// + + AsyncWebHeader(const String& data): _name(), _value() { - if (!data) + if (!data) return; - + int index = data.indexOf(':'); - - if (index < 0) + + if (index < 0) return; - + _name = data.substring(0, index); _value = data.substring(index + 2); } + ///////////////////////////////////////////////// + ~AsyncWebHeader() {} - const String& name() const + ///////////////////////////////////////////////// + + inline const String& name() const { return _name; } - const String& value() const + ///////////////////////////////////////////////// + + inline const String& value() const { return _value; } - String toString() const + ///////////////////////////////////////////////// + + inline String toString() const { return String(_name + ": " + _value + "\r\n"); } + + ///////////////////////////////////////////////// }; +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + /* REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect * */ @@ -198,6 +245,9 @@ typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, typedef std::function AwsResponseFiller; typedef std::function AwsTemplateProcessor; +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + class AsyncWebServerRequest { friend class AsyncWebServer; @@ -277,55 +327,78 @@ class AsyncWebServerRequest AsyncWebServerRequest(AsyncWebServer*, AsyncClient*); ~AsyncWebServerRequest(); - AsyncClient* client() + ///////////////////////////////////////////////// + + inline AsyncClient* client() { return _client; } - uint8_t version() const + ///////////////////////////////////////////////// + + inline uint8_t version() const { return _version; } - WebRequestMethodComposite method() const + ///////////////////////////////////////////////// + + inline WebRequestMethodComposite method() const { return _method; } - const String& url() const + ///////////////////////////////////////////////// + + inline const String& url() const { return _url; } - const String& host() const + ///////////////////////////////////////////////// + + inline const String& host() const { return _host; } - const String& contentType() const + ///////////////////////////////////////////////// + + inline const String& contentType() const { return _contentType; } - size_t contentLength() const + ///////////////////////////////////////////////// + + inline size_t contentLength() const { return _contentLength; } - bool multipart() const + ///////////////////////////////////////////////// + + inline bool multipart() const { return _isMultipart; } + ///////////////////////////////////////////////// + const char * methodToString() const; const char * requestedConnTypeToString() const; - RequestedConnectionType requestedConnType() const + ///////////////////////////////////////////////// + + inline RequestedConnectionType requestedConnType() const { return _reqconntype; } - bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED); + ///////////////////////////////////////////////// + + bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, + RequestedConnectionType erct3 = RCT_NOT_USED); void onDisconnect (ArDisconnectHandler fn); //hash is the string representation of: @@ -335,32 +408,46 @@ class AsyncWebServerRequest bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false); void requestAuthentication(const char * realm = NULL, bool isDigest = true); - void setHandler(AsyncWebHandler *handler) + ///////////////////////////////////////////////// + + inline void setHandler(AsyncWebHandler *handler) { _handler = handler; } + ///////////////////////////////////////////////// + void addInterestingHeader(const String& name); void redirect(const String& url); void send(AsyncWebServerResponse *response); void send(int code, const String& contentType = String(), const String& content = String()); + void send(int code, const String& contentType, const char *content, bool nonCopyingSend = true); // RSMOD void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr); void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); AsyncWebServerResponse *beginResponse(int code, const String& contentType = String(), const String& content = String()); + AsyncWebServerResponse *beginResponse(int code, const String& contentType, const char * content = nullptr); // RSMOD + + // KH add + AsyncWebServerResponse *beginResponse(int code, const String& contentType, const uint8_t * content, size_t len, + AwsTemplateProcessor callback = nullptr); + ////// + AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr); AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, - size_t len, AwsTemplateProcessor callback=nullptr); - AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, + AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, + AwsTemplateProcessor callback=nullptr); AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback, - AwsTemplateProcessor templateCallback = nullptr); + AwsTemplateProcessor templateCallback = nullptr); + AsyncResponseStream *beginResponseStream(const String& contentType, size_t bufferSize = 1460); size_t headers() const; // get header count @@ -375,11 +462,15 @@ class AsyncWebServerRequest AsyncWebParameter* getParam(const String& name, bool post = false, bool file = false) const; AsyncWebParameter* getParam(size_t num) const; - size_t args() const + ///////////////////////////////////////////////// + + inline size_t args() const { return params(); // get arguments count } + ///////////////////////////////////////////////// + const String& arg(const String& name) const; // get request argument value by name const String& arg(size_t i) const; // get request argument value by number const String& argName(size_t i) const; // get request argument name by number @@ -393,6 +484,9 @@ class AsyncWebServerRequest String urlDecode(const String& text) const; }; +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + /* FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server) * */ @@ -403,6 +497,9 @@ bool ON_STA_FILTER(AsyncWebServerRequest *request); bool ON_AP_FILTER(AsyncWebServerRequest *request); +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + /* REWRITE :: One instance can be handle any Request (done by the Server) * */ @@ -416,51 +513,71 @@ class AsyncWebRewrite ArRequestFilterFunction _filter; public: - AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL) + + ///////////////////////////////////////////////// + + AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL) { int index = _toUrl.indexOf('?'); - - if (index > 0) + + if (index > 0) { _params = _toUrl.substring(index + 1); _toUrl = _toUrl.substring(0, index); } } + ///////////////////////////////////////////////// + virtual ~AsyncWebRewrite() {} - AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) + ///////////////////////////////////////////////// + + inline AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } - bool filter(AsyncWebServerRequest *request) const + ///////////////////////////////////////////////// + + inline bool filter(AsyncWebServerRequest *request) const { return _filter == NULL || _filter(request); } - const String& from(void) const + ///////////////////////////////////////////////// + + inline const String& from() const { return _from; } - const String& toUrl(void) const + ///////////////////////////////////////////////// + + inline const String& toUrl() const { return _toUrl; } - const String& params(void) const + ///////////////////////////////////////////////// + + inline const String& params() const { return _params; } - virtual bool match(AsyncWebServerRequest *request) + ///////////////////////////////////////////////// + + virtual bool match(AsyncWebServerRequest *request) { return from() == request->url() && filter(request); } }; +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + /* HANDLER :: One instance can be attached to any Request (done by the Server) * */ @@ -475,41 +592,61 @@ class AsyncWebHandler public: AsyncWebHandler(): _username(""), _password("") {} - AsyncWebHandler& setFilter(ArRequestFilterFunction fn) + ///////////////////////////////////////////////// + + inline AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } - AsyncWebHandler& setAuthentication(const char *username, const char *password) + ///////////////////////////////////////////////// + + inline AsyncWebHandler& setAuthentication(const char *username, const char *password) { _username = String(username); _password = String(password); return *this; }; - bool filter(AsyncWebServerRequest *request) + ///////////////////////////////////////////////// + + inline bool filter(AsyncWebServerRequest *request) { return _filter == NULL || _filter(request); } + ///////////////////////////////////////////////// + virtual ~AsyncWebHandler() {} - virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))) + ///////////////////////////////////////////////// + + virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))) { return false; } + ///////////////////////////////////////////////// + virtual void handleRequest(AsyncWebServerRequest *request __attribute__((unused))) {} - virtual void handleUpload(AsyncWebServerRequest *request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))) {} - virtual void handleBody(AsyncWebServerRequest *request __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))) {} + virtual void handleUpload(AsyncWebServerRequest *request __attribute__((unused)), const String& filename __attribute__((unused)), + size_t index __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), + bool final __attribute__((unused))) {} + virtual void handleBody(AsyncWebServerRequest *request __attribute__((unused)), uint8_t *data __attribute__((unused)), + size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))) {} - virtual bool isRequestHandlerTrivial() + ///////////////////////////////////////////////// + + virtual bool isRequestHandlerTrivial() { return true; } }; +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + /* RESPONSE :: One instance is created for each Request (attached by the Handler) * */ @@ -519,6 +656,8 @@ typedef enum RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED } WebResponseState; +///////////////////////////////////////////////////////// + class AsyncWebServerResponse { protected: @@ -551,6 +690,9 @@ class AsyncWebServerResponse virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); }; +///////////////////////////////////////////////////////// +///////////////////////////////////////////////////////// + /* SERVER :: One instance * */ @@ -559,6 +701,8 @@ typedef std::function ArRequestHandlerFunc typedef std::function ArUploadHandlerFunction; typedef std::function ArBodyHandlerFunction; +///////////////////////////////////////////////////////// + class AsyncWebServer { protected: @@ -588,8 +732,10 @@ class AsyncWebServer AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest); AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest); - AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload); - AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody); + AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, + ArUploadHandlerFunction onUpload); + AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, + ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody); void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request) @@ -601,46 +747,64 @@ class AsyncWebServer void _rewriteRequest(AsyncWebServerRequest *request); }; -class DefaultHeaders +///////////////////////////////////////////////////////// + +class DefaultHeaders { using headers_t = LinkedList; headers_t _headers; + ///////////////////////////////////////////////// + DefaultHeaders() - : _headers(headers_t([](AsyncWebHeader * h) + : _headers(headers_t([](AsyncWebHeader * h) { delete h; })) {} + ///////////////////////////////////////////////// + public: using ConstIterator = headers_t::ConstIterator; - void addHeader(const String& name, const String& value) + ///////////////////////////////////////////////// + + void addHeader(const String& name, const String& value) { _headers.add(new AsyncWebHeader(name, value)); } - ConstIterator begin() const + ///////////////////////////////////////////////// + + inline ConstIterator begin() const { return _headers.begin(); } - ConstIterator end() const + ///////////////////////////////////////////////// + + inline ConstIterator end() const { return _headers.end(); } + ///////////////////////////////////////////////// + DefaultHeaders(DefaultHeaders const &) = delete; DefaultHeaders &operator=(DefaultHeaders const &) = delete; - static DefaultHeaders &Instance() + ///////////////////////////////////////////////// + + static DefaultHeaders &Instance() { static DefaultHeaders instance; return instance; } }; +///////////////////////////////////////////////////////// + #include "AsyncWebResponseImpl_Ethernet.h" #include "AsyncWebHandlerImpl_Ethernet.h" #include "AsyncWebSocket_Ethernet.h" diff --git a/src/AsyncWebServer_Ethernet_Debug.h b/src/AsyncWebServer_Ethernet_Debug.h index ec8ce32..3b6ab7f 100644 --- a/src/AsyncWebServer_Ethernet_Debug.h +++ b/src/AsyncWebServer_Ethernet_Debug.h @@ -12,14 +12,16 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once diff --git a/src/AsyncWebServer_Ethernet_cbuf.hpp b/src/AsyncWebServer_Ethernet_cbuf.hpp index ab64862..74e71af 100644 --- a/src/AsyncWebServer_Ethernet_cbuf.hpp +++ b/src/AsyncWebServer_Ethernet_cbuf.hpp @@ -18,14 +18,16 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once @@ -37,6 +39,8 @@ #include #include +///////////////////////////////////////////////// + class cbuf { public: @@ -49,16 +53,22 @@ class cbuf size_t size(); size_t room() const; + + ///////////////////////////////////////////////// inline bool empty() const { return _begin == _end; } + + ///////////////////////////////////////////////// inline bool full() const { return wrap_if_bufend(_end + 1) == _begin; } + + ///////////////////////////////////////////////// int peek(); size_t peek(char *dst, size_t size); @@ -75,17 +85,21 @@ class cbuf cbuf *next; private: + + ///////////////////////////////////////////////// + inline char* wrap_if_bufend(char* ptr) const { return (ptr == _bufend) ? _buf : ptr; } + + ///////////////////////////////////////////////// size_t _size; char* _buf; const char* _bufend; char* _begin; char* _end; - }; #endif // _ASYNC_ETHERNET_CBUF_HPP_ diff --git a/src/AsyncWebServer_Ethernet_cbuf_Impl.h b/src/AsyncWebServer_Ethernet_cbuf_Impl.h index 39d5337..a96827d 100644 --- a/src/AsyncWebServer_Ethernet_cbuf_Impl.h +++ b/src/AsyncWebServer_Ethernet_cbuf_Impl.h @@ -18,14 +18,16 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once @@ -35,23 +37,31 @@ #include "AsyncWebServer_Ethernet_cbuf.hpp" +///////////////////////////////////////////////// + cbuf::cbuf(size_t size) : next(NULL), _size(size), _buf(new char[size]), _bufend(_buf + size), _begin(_buf), _end(_begin) { } +///////////////////////////////////////////////// + cbuf::~cbuf() { - delete[] _buf; + if (_buf != nullptr) + delete[] _buf; } +///////////////////////////////////////////////// + size_t cbuf::resizeAdd(size_t addSize) { return resize(_size + addSize); } +///////////////////////////////////////////////// + size_t cbuf::resize(size_t newSize) { - size_t bytes_available = available(); // not lose any data @@ -87,6 +97,8 @@ size_t cbuf::resize(size_t newSize) return _size; } +///////////////////////////////////////////////// + size_t cbuf::available() const { if (_end >= _begin) @@ -97,11 +109,15 @@ size_t cbuf::available() const return _size - (_begin - _end); } +///////////////////////////////////////////////// + size_t cbuf::size() { return _size; } +///////////////////////////////////////////////// + size_t cbuf::room() const { if (_end >= _begin) @@ -112,6 +128,8 @@ size_t cbuf::room() const return _begin - _end - 1; } +///////////////////////////////////////////////// + int cbuf::peek() { if (empty()) @@ -120,6 +138,8 @@ int cbuf::peek() return static_cast(*_begin); } +///////////////////////////////////////////////// + size_t cbuf::peek(char *dst, size_t size) { size_t bytes_available = available(); @@ -141,6 +161,8 @@ size_t cbuf::peek(char *dst, size_t size) return size_read; } +///////////////////////////////////////////////// + int cbuf::read() { if (empty()) @@ -152,6 +174,8 @@ int cbuf::read() return static_cast(result); } +///////////////////////////////////////////////// + size_t cbuf::read(char* dst, size_t size) { size_t bytes_available = available(); @@ -174,6 +198,8 @@ size_t cbuf::read(char* dst, size_t size) return size_read; } +///////////////////////////////////////////////// + size_t cbuf::write(char c) { if (full()) @@ -185,6 +211,8 @@ size_t cbuf::write(char c) return 1; } +///////////////////////////////////////////////// + size_t cbuf::write(const char* src, size_t size) { size_t bytes_available = room(); @@ -207,12 +235,16 @@ size_t cbuf::write(const char* src, size_t size) return size_written; } +///////////////////////////////////////////////// + void cbuf::flush() { _begin = _buf; _end = _buf; } +///////////////////////////////////////////////// + size_t cbuf::remove(size_t size) { size_t bytes_available = available(); @@ -220,6 +252,7 @@ size_t cbuf::remove(size_t size) if (size >= bytes_available) { flush(); + return 0; } @@ -237,4 +270,6 @@ size_t cbuf::remove(size_t size) return available(); } +///////////////////////////////////////////////// + #endif // _ASYNC_ETHERNET_CBUF_IMPL_H_ diff --git a/src/AsyncWebSocket_Ethernet.cpp b/src/AsyncWebSocket_Ethernet.cpp index 526891e..967c9ef 100644 --- a/src/AsyncWebSocket_Ethernet.cpp +++ b/src/AsyncWebSocket_Ethernet.cpp @@ -12,19 +12,23 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #include "Arduino.h" -#define _AWS_ETHERNET_LOGLEVEL_ 1 +#if !defined(_AWS_ETHERNET_LOGLEVEL_) + #define _AWS_ETHERNET_LOGLEVEL_ 1 +#endif #include "AsyncWebServer_Ethernet_Debug.h" @@ -36,8 +40,12 @@ #include "Crypto/sha1.h" #include "Crypto/Hash.h" +///////////////////////////////////////////////// + #define MAX_PRINTF_LEN 64 +///////////////////////////////////////////////// + char *ltrim(char *s) { while (isspace(*s)) @@ -46,6 +54,8 @@ char *ltrim(char *s) return s; } +///////////////////////////////////////////////// + char *rtrim(char *s) { char* back = s + strlen(s); @@ -57,11 +67,15 @@ char *rtrim(char *s) return s; } +///////////////////////////////////////////////// + char *trim(char *s) { return rtrim(ltrim(s)); } +///////////////////////////////////////////////// + size_t b64_encoded_size(size_t inlen) { size_t ret; @@ -76,6 +90,8 @@ size_t b64_encoded_size(size_t inlen) return ret; } +///////////////////////////////////////////////// + char * b64_encode(const unsigned char *in, size_t len, char * out) { const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; @@ -121,6 +137,8 @@ char * b64_encode(const unsigned char *in, size_t len, char * out) return out; } +///////////////////////////////////////////////// + size_t webSocketSendFrameWindow(AsyncClient *client) { if (!client->canSend()) @@ -134,6 +152,8 @@ size_t webSocketSendFrameWindow(AsyncClient *client) return (space - 8); } +///////////////////////////////////////////////// + size_t webSocketSendFrame(AsyncClient *client, bool final, uint8_t opcode, bool mask, uint8_t *data, size_t len) { if (!client->canSend()) @@ -233,19 +253,22 @@ size_t webSocketSendFrame(AsyncClient *client, bool final, uint8_t opcode, bool return len; } +///////////////////////////////////////////////// +///////////////////////////////////////////////// /* AsyncWebSocketMessageBuffer */ - +///////////////////////////////////////////////// AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer() : _data(nullptr), _len(0), _lock(false), _count(0) { - } +///////////////////////////////////////////////// + AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(uint8_t * data, size_t size) : _data(nullptr), _len(size), _lock(false), _count(0) { @@ -263,6 +286,7 @@ AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(uint8_t * data, size_t } } +///////////////////////////////////////////////// AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size) : _data(nullptr), _len(size), _lock(false), _count(0) @@ -273,9 +297,10 @@ AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size) { _data[_len] = 0; } - } +///////////////////////////////////////////////// + AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const AsyncWebSocketMessageBuffer & copy) : _data(nullptr), _len(0), _lock(false), _count(0) { @@ -294,9 +319,10 @@ AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const AsyncWebSocketMes memcpy(_data, copy._data, _len); _data[_len] = 0; } - } +///////////////////////////////////////////////// + AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer && copy) : _data(nullptr), _len(0), _lock(false), _count(0) { @@ -309,9 +335,10 @@ AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBu _data = copy._data; copy._data = nullptr; } - } +///////////////////////////////////////////////// + AsyncWebSocketMessageBuffer::~AsyncWebSocketMessageBuffer() { if (_data) @@ -320,6 +347,8 @@ AsyncWebSocketMessageBuffer::~AsyncWebSocketMessageBuffer() } } +///////////////////////////////////////////////// + bool AsyncWebSocketMessageBuffer::reserve(size_t size) { _len = size; @@ -342,9 +371,11 @@ bool AsyncWebSocketMessageBuffer::reserve(size_t size) { return false; } - } +///////////////////////////////////////////////// +///////////////////////////////////////////////// + /* Control Frame */ @@ -359,6 +390,9 @@ class AsyncWebSocketControl bool _finished; public: + + ///////////////////////////////////////////////// + AsyncWebSocketControl(uint8_t opcode, uint8_t *data = NULL, size_t len = 0, bool mask = false) : _opcode(opcode), _len(len), _mask(len && mask), _finished(false) { @@ -381,27 +415,37 @@ class AsyncWebSocketControl _data = NULL; } + ///////////////////////////////////////////////// + virtual ~AsyncWebSocketControl() { if (_data != NULL) free(_data); } + ///////////////////////////////////////////////// + virtual bool finished() const { return _finished; } - uint8_t opcode() + ///////////////////////////////////////////////// + + inline uint8_t opcode() { return _opcode; } - uint8_t len() + ///////////////////////////////////////////////// + + inline uint8_t len() { return _len + 2; } + ///////////////////////////////////////////////// + size_t send(AsyncClient *client) { _finished = true; @@ -409,6 +453,9 @@ class AsyncWebSocketControl } }; +///////////////////////////////////////////////// +///////////////////////////////////////////////// + /* Basic Buffered Message */ @@ -432,6 +479,8 @@ AsyncWebSocketBasicMessage::AsyncWebSocketBasicMessage(const char * data, size_t } } +///////////////////////////////////////////////// + AsyncWebSocketBasicMessage::AsyncWebSocketBasicMessage(uint8_t opcode, bool mask) : _len(0), _sent(0), _ack(0), _acked(0), _data(NULL) { @@ -439,6 +488,7 @@ AsyncWebSocketBasicMessage::AsyncWebSocketBasicMessage(uint8_t opcode, bool mask _mask = mask; } +///////////////////////////////////////////////// AsyncWebSocketBasicMessage::~AsyncWebSocketBasicMessage() { @@ -446,9 +496,12 @@ AsyncWebSocketBasicMessage::~AsyncWebSocketBasicMessage() free(_data); } +///////////////////////////////////////////////// + void AsyncWebSocketBasicMessage::ack(size_t len, uint32_t time) { - (void)time; + AWS_ETHERNET_UNUSED(time); + _acked += len; if (_sent == _len && _acked == _ack) @@ -457,6 +510,8 @@ void AsyncWebSocketBasicMessage::ack(size_t len, uint32_t time) } } +///////////////////////////////////////////////// + size_t AsyncWebSocketBasicMessage::send(AsyncClient *client) { if (_status != WS_MSG_SENDING) @@ -509,6 +564,8 @@ size_t AsyncWebSocketBasicMessage::send(AsyncClient *client) return sent; } +///////////////////////////////////////////////// + bool AsyncWebSocketBasicMessage::reserve(size_t size) { if (size) @@ -520,6 +577,7 @@ bool AsyncWebSocketBasicMessage::reserve(size_t size) memset(_data, 0, size); _len = size; _status = WS_MSG_SENDING; + return true; } } @@ -527,6 +585,9 @@ bool AsyncWebSocketBasicMessage::reserve(size_t size) return false; } +///////////////////////////////////////////////// +///////////////////////////////////////////////// + /* AsyncWebSocketMultiMessage Message */ @@ -551,9 +612,9 @@ AsyncWebSocketMultiMessage::AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuff { _status = WS_MSG_ERROR; } - } +///////////////////////////////////////////////// AsyncWebSocketMultiMessage::~AsyncWebSocketMultiMessage() { @@ -563,9 +624,12 @@ AsyncWebSocketMultiMessage::~AsyncWebSocketMultiMessage() } } +///////////////////////////////////////////////// + void AsyncWebSocketMultiMessage::ack(size_t len, uint32_t time) { - (void)time; + AWS_ETHERNET_UNUSED(time); + _acked += len; if (_sent >= _len && _acked >= _ack) @@ -576,6 +640,8 @@ void AsyncWebSocketMultiMessage::ack(size_t len, uint32_t time) LOGDEBUG1("ACK:", _len); } +///////////////////////////////////////////////// + size_t AsyncWebSocketMultiMessage::send(AsyncClient *client) { if (_status != WS_MSG_SENDING) @@ -589,6 +655,7 @@ size_t AsyncWebSocketMultiMessage::send(AsyncClient *client) if (_sent == _len) { _status = WS_MSG_SENT; + return 0; } @@ -631,6 +698,8 @@ size_t AsyncWebSocketMultiMessage::send(AsyncClient *client) return sent; } +///////////////////////////////////////////////// +///////////////////////////////////////////////// /* Async WebSocket Client @@ -638,6 +707,8 @@ size_t AsyncWebSocketMultiMessage::send(AsyncClient *client) const char * AWSC_PING_PAYLOAD = "ESPAsyncWebServer-PING"; const size_t AWSC_PING_PAYLOAD_LEN = 22; +///////////////////////////////////////////////// + AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server) : _controlQueue(LinkedList([](AsyncWebSocketControl * c) { @@ -659,13 +730,15 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, Async _client->onError([](void *r, AsyncClient * c, int8_t error) { - (void)c; + AWS_ETHERNET_UNUSED(c); + ((AsyncWebSocketClient*)(r))->_onError(error); }, this); _client->onAck([](void *r, AsyncClient * c, size_t len, uint32_t time) { - (void)c; + AWS_ETHERNET_UNUSED(c); + ((AsyncWebSocketClient*)(r))->_onAck(len, time); }, this); @@ -677,19 +750,22 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, Async _client->onTimeout([](void *r, AsyncClient * c, uint32_t time) { - (void)c; + AWS_ETHERNET_UNUSED(c); + ((AsyncWebSocketClient*)(r))->_onTimeout(time); }, this); _client->onData([](void *r, AsyncClient * c, void *buf, size_t len) { - (void)c; + AWS_ETHERNET_UNUSED(c); + ((AsyncWebSocketClient*)(r))->_onData(buf, len); }, this); _client->onPoll([](void *r, AsyncClient * c) { - (void)c; + AWS_ETHERNET_UNUSED(c); + ((AsyncWebSocketClient*)(r))->_onPoll(); }, this); @@ -699,6 +775,8 @@ AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, Async delete request; } +///////////////////////////////////////////////// + AsyncWebSocketClient::~AsyncWebSocketClient() { _messageQueue.free(); @@ -706,6 +784,8 @@ AsyncWebSocketClient::~AsyncWebSocketClient() _server->_handleEvent(this, WS_EVT_DISCONNECT, NULL, NULL, 0); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) { _lastMessageTime = millis(); @@ -739,6 +819,8 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) _runQueue(); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::_onPoll() { if (_client->canSend() && (!_controlQueue.isEmpty() || !_messageQueue.isEmpty())) { @@ -750,6 +832,8 @@ void AsyncWebSocketClient::_onPoll() } } +///////////////////////////////////////////////// + void AsyncWebSocketClient::_runQueue() { while (!_messageQueue.isEmpty() && _messageQueue.front()->finished()) @@ -768,6 +852,8 @@ void AsyncWebSocketClient::_runQueue() } } +///////////////////////////////////////////////// + bool AsyncWebSocketClient::queueIsFull() { if ((_messageQueue.length() >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED) ) @@ -776,6 +862,8 @@ bool AsyncWebSocketClient::queueIsFull() return false; } +///////////////////////////////////////////////// + void AsyncWebSocketClient::_queueMessage(AsyncWebSocketMessage *dataMessage) { if (dataMessage == NULL) @@ -801,6 +889,8 @@ void AsyncWebSocketClient::_queueMessage(AsyncWebSocketMessage *dataMessage) _runQueue(); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::_queueControl(AsyncWebSocketControl *controlMessage) { if (controlMessage == NULL) @@ -812,6 +902,8 @@ void AsyncWebSocketClient::_queueControl(AsyncWebSocketControl *controlMessage) _runQueue(); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::close(uint16_t code, const char * message) { if (_status != WS_CONNECTED) @@ -853,26 +945,37 @@ void AsyncWebSocketClient::close(uint16_t code, const char * message) _queueControl(new AsyncWebSocketControl(WS_DISCONNECT)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::ping(uint8_t *data, size_t len) { if (_status == WS_CONNECTED) _queueControl(new AsyncWebSocketControl(WS_PING, data, len)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::_onError(int8_t) {} +///////////////////////////////////////////////// + void AsyncWebSocketClient::_onTimeout(uint32_t time) { - (void)time; + AWS_ETHERNET_UNUSED(time); + _client->close(true); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::_onDisconnect() { _client = NULL; _server->_handleDisconnect(this); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) { _lastMessageTime = millis(); @@ -897,9 +1000,11 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) data += 2; plen -= 2; } - else if (_pinfo.len == 127) { + else if (_pinfo.len == 127) + { _pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 - | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56; + | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 + | (uint64_t)(fdata[2]) << 56; data += 8; plen -= 8; } @@ -927,7 +1032,8 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) if (_pinfo.index == 0) { - if (_pinfo.opcode) { + if (_pinfo.opcode) + { _pinfo.message_opcode = _pinfo.opcode; _pinfo.num = 0; } @@ -999,6 +1105,8 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) } } +///////////////////////////////////////////////// + size_t AsyncWebSocketClient::printf(const char *format, ...) { va_list arg; @@ -1008,6 +1116,7 @@ size_t AsyncWebSocketClient::printf(const char *format, ...) if (!temp) { va_end(arg); + return 0; } @@ -1022,6 +1131,7 @@ size_t AsyncWebSocketClient::printf(const char *format, ...) if (!buffer) { delete[] temp; + return 0; } @@ -1038,69 +1148,96 @@ size_t AsyncWebSocketClient::printf(const char *format, ...) } delete[] temp; + return len; } +///////////////////////////////////////////////// + void AsyncWebSocketClient::text(const char * message, size_t len) { _queueMessage(new AsyncWebSocketBasicMessage(message, len)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::text(const char * message) { text(message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::text(uint8_t * message, size_t len) { text((const char *)message, len); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::text(char * message) { text(message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::text(const String & message) { text(message.c_str(), message.length()); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::text(AsyncWebSocketMessageBuffer * buffer) { _queueMessage(new AsyncWebSocketMultiMessage(buffer)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::binary(const char * message, size_t len) { _queueMessage(new AsyncWebSocketBasicMessage(message, len, WS_BINARY)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::binary(const char * message) { binary(message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::binary(uint8_t * message, size_t len) { binary((const char *)message, len); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::binary(char * message) { binary(message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::binary(const String & message) { binary(message.c_str(), message.length()); } +///////////////////////////////////////////////// + void AsyncWebSocketClient::binary(AsyncWebSocketMessageBuffer * buffer) { _queueMessage(new AsyncWebSocketMultiMessage(buffer, WS_BINARY)); } +///////////////////////////////////////////////// + IPAddress AsyncWebSocketClient::remoteIP() { if (!_client) @@ -1112,6 +1249,8 @@ IPAddress AsyncWebSocketClient::remoteIP() return _client->remoteIP(); } +///////////////////////////////////////////////// + uint16_t AsyncWebSocketClient::remotePort() { if (!_client) @@ -1122,6 +1261,9 @@ uint16_t AsyncWebSocketClient::remotePort() return _client->remotePort(); } +///////////////////////////////////////////////// +///////////////////////////////////////////////// + /* Async Web Socket - Each separate socket location */ @@ -1140,8 +1282,12 @@ AsyncWebSocket::AsyncWebSocket(const String & url) _eventHandler = NULL; } +///////////////////////////////////////////////// + AsyncWebSocket::~AsyncWebSocket() {} +///////////////////////////////////////////////// + void AsyncWebSocket::_handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) { if (_eventHandler != NULL) @@ -1150,20 +1296,25 @@ void AsyncWebSocket::_handleEvent(AsyncWebSocketClient * client, AwsEventType ty } } +///////////////////////////////////////////////// + void AsyncWebSocket::_addClient(AsyncWebSocketClient * client) { _clients.add(client); } +///////////////////////////////////////////////// + void AsyncWebSocket::_handleDisconnect(AsyncWebSocketClient * client) { - _clients.remove_first([ = ](AsyncWebSocketClient * c) { return c->id() == client->id(); }); } +///////////////////////////////////////////////// + bool AsyncWebSocket::availableForWriteAll() { for (const auto& c : _clients) @@ -1175,6 +1326,8 @@ bool AsyncWebSocket::availableForWriteAll() return true; } +///////////////////////////////////////////////// + bool AsyncWebSocket::availableForWrite(uint32_t id) { for (const auto& c : _clients) @@ -1186,6 +1339,8 @@ bool AsyncWebSocket::availableForWrite(uint32_t id) return true; } +///////////////////////////////////////////////// + size_t AsyncWebSocket::count() const { return _clients.count_if([](AsyncWebSocketClient * c) @@ -1194,6 +1349,8 @@ size_t AsyncWebSocket::count() const }); } +///////////////////////////////////////////////// + AsyncWebSocketClient * AsyncWebSocket::client(uint32_t id) { for (const auto &c : _clients) @@ -1207,6 +1364,7 @@ AsyncWebSocketClient * AsyncWebSocket::client(uint32_t id) return nullptr; } +///////////////////////////////////////////////// void AsyncWebSocket::close(uint32_t id, uint16_t code, const char * message) { @@ -1216,6 +1374,8 @@ void AsyncWebSocket::close(uint32_t id, uint16_t code, const char * message) c->close(code, message); } +///////////////////////////////////////////////// + void AsyncWebSocket::closeAll(uint16_t code, const char * message) { for (const auto& c : _clients) @@ -1225,6 +1385,8 @@ void AsyncWebSocket::closeAll(uint16_t code, const char * message) } } +///////////////////////////////////////////////// + void AsyncWebSocket::cleanupClients(uint16_t maxClients) { if (count() > maxClients) @@ -1233,6 +1395,8 @@ void AsyncWebSocket::cleanupClients(uint16_t maxClients) } } +///////////////////////////////////////////////// + void AsyncWebSocket::ping(uint32_t id, uint8_t *data, size_t len) { AsyncWebSocketClient * c = client(id); @@ -1241,6 +1405,8 @@ void AsyncWebSocket::ping(uint32_t id, uint8_t *data, size_t len) c->ping(data, len); } +///////////////////////////////////////////////// + void AsyncWebSocket::pingAll(uint8_t *data, size_t len) { for (const auto& c : _clients) @@ -1250,6 +1416,8 @@ void AsyncWebSocket::pingAll(uint8_t *data, size_t len) } } +///////////////////////////////////////////////// + void AsyncWebSocket::text(uint32_t id, const char * message, size_t len) { AsyncWebSocketClient * c = client(id); @@ -1258,6 +1426,8 @@ void AsyncWebSocket::text(uint32_t id, const char * message, size_t len) c->text(message, len); } +///////////////////////////////////////////////// + void AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer * buffer) { if (!buffer) @@ -1277,6 +1447,7 @@ void AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer * buffer) _cleanBuffers(); } +///////////////////////////////////////////////// void AsyncWebSocket::textAll(const char * message, size_t len) { @@ -1284,6 +1455,8 @@ void AsyncWebSocket::textAll(const char * message, size_t len) textAll(WSBuffer); } +///////////////////////////////////////////////// + void AsyncWebSocket::binary(uint32_t id, const char * message, size_t len) { AsyncWebSocketClient * c = client(id); @@ -1292,12 +1465,16 @@ void AsyncWebSocket::binary(uint32_t id, const char * message, size_t len) c->binary(message, len); } +///////////////////////////////////////////////// + void AsyncWebSocket::binaryAll(const char * message, size_t len) { AsyncWebSocketMessageBuffer * buffer = makeBuffer((uint8_t *)message, len); binaryAll(buffer); } +///////////////////////////////////////////////// + void AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer * buffer) { if (!buffer) @@ -1315,6 +1492,8 @@ void AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer * buffer) _cleanBuffers(); } +///////////////////////////////////////////////// + void AsyncWebSocket::message(uint32_t id, AsyncWebSocketMessage * message) { AsyncWebSocketClient * c = client(id); @@ -1323,6 +1502,8 @@ void AsyncWebSocket::message(uint32_t id, AsyncWebSocketMessage * message) c->message(message); } +///////////////////////////////////////////////// + void AsyncWebSocket::messageAll(AsyncWebSocketMultiMessage * message) { for (const auto& c : _clients) @@ -1334,6 +1515,8 @@ void AsyncWebSocket::messageAll(AsyncWebSocketMultiMessage * message) _cleanBuffers(); } +///////////////////////////////////////////////// + size_t AsyncWebSocket::printf(uint32_t id, const char *format, ...) { AsyncWebSocketClient * c = client(id); @@ -1344,12 +1527,15 @@ size_t AsyncWebSocket::printf(uint32_t id, const char *format, ...) va_start(arg, format); size_t len = c->printf(format, arg); va_end(arg); + return len; } return 0; } +///////////////////////////////////////////////// + size_t AsyncWebSocket::printfAll(const char *format, ...) { va_list arg; @@ -1377,97 +1563,134 @@ size_t AsyncWebSocket::printfAll(const char *format, ...) va_end(arg); textAll(buffer); + return len; } +///////////////////////////////////////////////// + void AsyncWebSocket::text(uint32_t id, const char * message) { text(id, message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocket::text(uint32_t id, uint8_t * message, size_t len) { text(id, (const char *)message, len); } +///////////////////////////////////////////////// + void AsyncWebSocket::text(uint32_t id, char * message) { text(id, message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocket::text(uint32_t id, const String & message) { text(id, message.c_str(), message.length()); } +///////////////////////////////////////////////// + void AsyncWebSocket::textAll(const char * message) { textAll(message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocket::textAll(uint8_t * message, size_t len) { textAll((const char *)message, len); } +///////////////////////////////////////////////// + void AsyncWebSocket::textAll(char * message) { textAll(message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocket::textAll(const String & message) { textAll(message.c_str(), message.length()); } +///////////////////////////////////////////////// + void AsyncWebSocket::binary(uint32_t id, const char * message) { binary(id, message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocket::binary(uint32_t id, uint8_t * message, size_t len) { binary(id, (const char *)message, len); } +///////////////////////////////////////////////// + void AsyncWebSocket::binary(uint32_t id, char * message) { binary(id, message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocket::binary(uint32_t id, const String & message) { binary(id, message.c_str(), message.length()); } +///////////////////////////////////////////////// + void AsyncWebSocket::binaryAll(const char * message) { binaryAll(message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocket::binaryAll(uint8_t * message, size_t len) { binaryAll((const char *)message, len); } +///////////////////////////////////////////////// + void AsyncWebSocket::binaryAll(char * message) { binaryAll(message, strlen(message)); } +///////////////////////////////////////////////// + void AsyncWebSocket::binaryAll(const String & message) { binaryAll(message.c_str(), message.length()); } +///////////////////////////////////////////////// + const char * WS_STR_CONNECTION = "Connection"; -const char * WS_STR_UPGRADE = "Upgrade"; -const char * WS_STR_ORIGIN = "Origin"; -const char * WS_STR_VERSION = "Sec-WebSocket-Version"; -const char * WS_STR_KEY = "Sec-WebSocket-Key"; -const char * WS_STR_PROTOCOL = "Sec-WebSocket-Protocol"; -const char * WS_STR_ACCEPT = "Sec-WebSocket-Accept"; -const char * WS_STR_UUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; +const char * WS_STR_UPGRADE = "Upgrade"; +const char * WS_STR_ORIGIN = "Origin"; +const char * WS_STR_VERSION = "Sec-WebSocket-Version"; +const char * WS_STR_KEY = "Sec-WebSocket-Key"; +const char * WS_STR_PROTOCOL = "Sec-WebSocket-Protocol"; +const char * WS_STR_ACCEPT = "Sec-WebSocket-Accept"; +const char * WS_STR_UUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + +///////////////////////////////////////////////// bool AsyncWebSocket::canHandle(AsyncWebServerRequest * request) { @@ -1487,6 +1710,8 @@ bool AsyncWebSocket::canHandle(AsyncWebServerRequest * request) return true; } +///////////////////////////////////////////////// + void AsyncWebSocket::handleRequest(AsyncWebServerRequest * request) { if (!request->hasHeader(WS_STR_VERSION) || !request->hasHeader(WS_STR_KEY)) @@ -1496,7 +1721,8 @@ void AsyncWebSocket::handleRequest(AsyncWebServerRequest * request) return; } - if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) { + if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) + { return request->requestAuthentication(); } @@ -1524,6 +1750,8 @@ void AsyncWebSocket::handleRequest(AsyncWebServerRequest * request) request->send(response); } +///////////////////////////////////////////////// + AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(size_t size) { AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(size); @@ -1537,6 +1765,8 @@ AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(size_t size) return buffer; } +///////////////////////////////////////////////// + AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(uint8_t * data, size_t size) { AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(data, size); @@ -1550,6 +1780,8 @@ AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(uint8_t * data, size_t return buffer; } +///////////////////////////////////////////////// + void AsyncWebSocket::_cleanBuffers() { AsyncWebLockGuard l(_lock); @@ -1563,28 +1795,21 @@ void AsyncWebSocket::_cleanBuffers() } } +///////////////////////////////////////////////// + AsyncWebSocket::AsyncWebSocketClientLinkedList AsyncWebSocket::getClients() const { return _clients; } +///////////////////////////////////////////////// +///////////////////////////////////////////////// + /* Response to Web Socket request - sends the authorization and detaches the TCP Client from the web server Authentication code from https://github.com/Links2004/arduinoWebSockets/blob/master/src/WebSockets.cpp#L480 */ -/*static */ -/*void acceptKey(char * skey, char * ckey) { - char sha1HashBin[22] = { 0 }; - //String key = base64_encode(sha1HashBin, 20); - __disable_irq(); - char buf[256]; - sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", skey); - SHA1(sha1HashBin, (const char *)buf, strlen(buf)); - b64_encode((const unsigned char *)sha1HashBin, 20, buf); - sprintf(ckey, "%s", trim(buf)); - __enable_irq(); -} -*/ + AsyncWebSocketResponse::AsyncWebSocketResponse(const String & key, AsyncWebSocket * server) { _server = server; @@ -1596,6 +1821,7 @@ AsyncWebSocketResponse::AsyncWebSocketResponse(const String & key, AsyncWebSocke if (hash == NULL) { _state = RESPONSE_FAILED; + return; } @@ -1605,6 +1831,7 @@ AsyncWebSocketResponse::AsyncWebSocketResponse(const String & key, AsyncWebSocke { free(hash); _state = RESPONSE_FAILED; + return; } @@ -1630,22 +1857,28 @@ AsyncWebSocketResponse::AsyncWebSocketResponse(const String & key, AsyncWebSocke free(hash); } +///////////////////////////////////////////////// + void AsyncWebSocketResponse::_respond(AsyncWebServerRequest * request) { if (_state == RESPONSE_FAILED) { request->client()->close(true); + return; } String out = _assembleHead(request->version()); request->client()->write(out.c_str(), _headLength); + _state = RESPONSE_WAIT_ACK; } +///////////////////////////////////////////////// + size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest * request, size_t len, uint32_t time) { - (void)time; + AWS_ETHERNET_UNUSED(time); if (len) { diff --git a/src/AsyncWebSocket_Ethernet.h b/src/AsyncWebSocket_Ethernet.h index 27940ef..78c6592 100644 --- a/src/AsyncWebSocket_Ethernet.h +++ b/src/AsyncWebSocket_Ethernet.h @@ -12,14 +12,16 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once @@ -42,12 +44,14 @@ #include "AsyncWebSynchronization_Ethernet.h" +///////////////////////////////////////////////// + class AsyncWebSocket; class AsyncWebSocketResponse; class AsyncWebSocketClient; class AsyncWebSocketControl; - +///////////////////////////////////////////////// typedef struct { @@ -74,6 +78,8 @@ typedef struct uint64_t index; } AwsFrameInfo; +///////////////////////////////////////////////// + typedef enum { WS_DISCONNECTED, @@ -107,6 +113,8 @@ typedef enum WS_EVT_DATA } AwsEventType; +///////////////////////////////////////////////// + class AsyncWebSocketMessageBuffer { private: @@ -123,15 +131,20 @@ class AsyncWebSocketMessageBuffer AsyncWebSocketMessageBuffer(AsyncWebSocketMessageBuffer &&); ~AsyncWebSocketMessageBuffer(); + ///////////////////////////////////////////////// + void operator ++(int i) { - (void)i; + AWS_ETHERNET_UNUSED(i); + _count++; } + ///////////////////////////////////////////////// + void operator --(int i) { - (void)i; + AWS_ETHERNET_UNUSED(i); if (_count > 0) { @@ -139,42 +152,60 @@ class AsyncWebSocketMessageBuffer } ; } + ///////////////////////////////////////////////// + bool reserve(size_t size); - void lock() + ///////////////////////////////////////////////// + + inline void lock() { _lock = true; } - void unlock() + ///////////////////////////////////////////////// + + inline void unlock() { _lock = false; } - uint8_t * get() + ///////////////////////////////////////////////// + + inline uint8_t * get() { return _data; } - size_t length() + ///////////////////////////////////////////////// + + inline size_t length() { return _len; } - uint32_t count() + ///////////////////////////////////////////////// + + inline uint32_t count() { return _count; } - bool canDelete() + ///////////////////////////////////////////////// + + inline bool canDelete() { return (!_count && !_lock); } + ///////////////////////////////////////////////// + friend AsyncWebSocket; }; +///////////////////////////////////////////////// + class AsyncWebSocketMessage { protected: @@ -187,22 +218,30 @@ class AsyncWebSocketMessage virtual ~AsyncWebSocketMessage() {} virtual void ack(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))) {} - virtual size_t send(AsyncClient *client __attribute__((unused))) + ///////////////////////////////////////////////// + + virtual size_t send(AsyncClient *client __attribute__((unused))) { return 0; } - virtual bool finished() + ///////////////////////////////////////////////// + + virtual bool finished() { return _status != WS_MSG_SENDING; } - virtual bool betweenFrames() const + ///////////////////////////////////////////////// + + virtual bool betweenFrames() const { return false; } }; +///////////////////////////////////////////////// + class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage { private: @@ -217,17 +256,23 @@ class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage AsyncWebSocketBasicMessage(uint8_t opcode = WS_TEXT, bool mask = false); virtual ~AsyncWebSocketBasicMessage() override; - virtual bool betweenFrames() const override + ///////////////////////////////////////////////// + + virtual bool betweenFrames() const override { return _acked == _ack; } + ///////////////////////////////////////////////// + virtual void ack(size_t len, uint32_t time) override; virtual size_t send(AsyncClient *client) override; virtual bool reserve(size_t size); }; +///////////////////////////////////////////////// + class AsyncWebSocketMultiMessage: public AsyncWebSocketMessage { private: @@ -242,15 +287,21 @@ class AsyncWebSocketMultiMessage: public AsyncWebSocketMessage AsyncWebSocketMultiMessage(AsyncWebSocketMessageBuffer * buffer, uint8_t opcode = WS_TEXT, bool mask = false); virtual ~AsyncWebSocketMultiMessage() override; - virtual bool betweenFrames() const override + ///////////////////////////////////////////////// + + virtual bool betweenFrames() const override { return _acked == _ack; } + ///////////////////////////////////////////////// + virtual void ack(size_t len, uint32_t time) override ; virtual size_t send(AsyncClient *client) override ; }; +///////////////////////////////////////////////// + class AsyncWebSocketClient { private: @@ -278,32 +329,44 @@ class AsyncWebSocketClient AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server); ~AsyncWebSocketClient(); + ////////////////////////////////////////////////// + //client id increments for the given server - uint32_t id() + inline uint32_t id() { return _clientId; } - AwsClientStatus status() + ///////////////////////////////////////////////// + + inline AwsClientStatus status() { return _status; } - AsyncClient* client() + ///////////////////////////////////////////////// + + inline AsyncClient* client() { return _client; } - AsyncWebSocket *server() + ///////////////////////////////////////////////// + + inline AsyncWebSocket *server() { return _server; } - AwsFrameInfo const &pinfo() const + ///////////////////////////////////////////////// + + inline AwsFrameInfo const &pinfo() const { return _pinfo; } + ///////////////////////////////////////////////// + IPAddress remoteIP(); uint16_t remotePort(); @@ -311,23 +374,31 @@ class AsyncWebSocketClient void close(uint16_t code = 0, const char * message = NULL); void ping(uint8_t *data = NULL, size_t len = 0); + ///////////////////////////////////////////////// + //set auto-ping period in seconds. disabled if zero (default) - void keepAlivePeriod(uint16_t seconds) + inline void keepAlivePeriod(uint16_t seconds) { _keepAlivePeriod = seconds * 1000; } - uint16_t keepAlivePeriod() + ///////////////////////////////////////////////// + + inline uint16_t keepAlivePeriod() { return (uint16_t)(_keepAlivePeriod / 1000); } + ///////////////////////////////////////////////// + //data packets - void message(AsyncWebSocketMessage *message) + inline void message(AsyncWebSocketMessage *message) { _queueMessage(message); } + ///////////////////////////////////////////////// + bool queueIsFull(); size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3))); @@ -346,11 +417,15 @@ class AsyncWebSocketClient void binary(const String &message); void binary(AsyncWebSocketMessageBuffer *buffer); - bool canSend() + ///////////////////////////////////////////////// + + inline bool canSend() { return _messageQueue.length() < WS_MAX_QUEUED_MESSAGES; } + ///////////////////////////////////////////////// + //system callbacks (do not call) void _onAck(size_t len, uint32_t time); void _onError(int8_t); @@ -360,8 +435,12 @@ class AsyncWebSocketClient void _onData(void *pbuf, size_t plen); }; +///////////////////////////////////////////////// + typedef std::function AwsEventHandler; +///////////////////////////////////////////////// + //WebServer Handler implementation that plays the role of a socket server class AsyncWebSocket: public AsyncWebHandler { @@ -380,32 +459,44 @@ class AsyncWebSocket: public AsyncWebHandler AsyncWebSocket(const String& url); ~AsyncWebSocket(); - const char * url() const + ///////////////////////////////////////////////// + + inline const char * url() const { return _url.c_str(); } - void enable(bool e) + ///////////////////////////////////////////////// + + inline void enable(bool e) { _enabled = e; } - bool enabled() const + ///////////////////////////////////////////////// + + inline bool enabled() const { return _enabled; } + ///////////////////////////////////////////////// + bool availableForWriteAll(); bool availableForWrite(uint32_t id); size_t count() const; AsyncWebSocketClient * client(uint32_t id); - bool hasClient(uint32_t id) + ///////////////////////////////////////////////// + + inline bool hasClient(uint32_t id) { return client(id) != NULL; } + ///////////////////////////////////////////////// + void close(uint32_t id, uint16_t code = 0, const char * message = NULL); void closeAll(uint16_t code = 0, const char * message = NULL); void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS); @@ -445,25 +536,30 @@ class AsyncWebSocket: public AsyncWebHandler size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4))); size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3))); + ///////////////////////////////////////////////// + //event listener - void onEvent(AwsEventHandler handler) + inline void onEvent(AwsEventHandler handler) { _eventHandler = handler; } + ///////////////////////////////////////////////// + //system callbacks (do not call) - uint32_t _getNextId() + inline uint32_t _getNextId() { return _cNextId++; } + ///////////////////////////////////////////////// + void _addClient(AsyncWebSocketClient * client); void _handleDisconnect(AsyncWebSocketClient * client); void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); virtual bool canHandle(AsyncWebServerRequest *request) override final; virtual void handleRequest(AsyncWebServerRequest *request) override final; - // messagebuffer functions/objects. AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0); AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size); @@ -473,6 +569,8 @@ class AsyncWebSocket: public AsyncWebHandler AsyncWebSocketClientLinkedList getClients() const; }; +///////////////////////////////////////////////// + //WebServer response to authenticate the socket and detach the tcp client from the web server request class AsyncWebSocketResponse: public AsyncWebServerResponse { @@ -485,11 +583,14 @@ class AsyncWebSocketResponse: public AsyncWebServerResponse void _respond(AsyncWebServerRequest *request); size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); - bool _sourceValid() const + ///////////////////////////////////////////////// + + inline bool _sourceValid() const { return true; } }; +///////////////////////////////////////////////// #endif /* ASYNCWEBSOCKET_ETHERNET_H_ */ diff --git a/src/AsyncWebSynchronization_Ethernet.h b/src/AsyncWebSynchronization_Ethernet.h index feff0be..54dc57e 100644 --- a/src/AsyncWebSynchronization_Ethernet.h +++ b/src/AsyncWebSynchronization_Ethernet.h @@ -12,14 +12,16 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once @@ -31,44 +33,59 @@ #include +///////////////////////////////////////////////// + // This is the Ethernet version of the Sync Lock which is currently unimplemented class AsyncWebLock { -public: - AsyncWebLock() { - } + public: + AsyncWebLock() {} + + ~AsyncWebLock() {} - ~AsyncWebLock() { - } + ///////////////////////////////////////////////// + + inline bool lock() const + { + return false; + } - bool lock() const { - return false; - } + ///////////////////////////////////////////////// - void unlock() const { - } + inline void unlock() const {} }; class AsyncWebLockGuard { -private: - const AsyncWebLock *_lock; - -public: - AsyncWebLockGuard(const AsyncWebLock &l) { - if (l.lock()) { - _lock = &l; - } else { - _lock = NULL; + private: + const AsyncWebLock *_lock; + + public: + + ///////////////////////////////////////////////// + + AsyncWebLockGuard(const AsyncWebLock &l) + { + if (l.lock()) + { + _lock = &l; + } + else + { + _lock = NULL; + } } - } - ~AsyncWebLockGuard() { - if (_lock) { - _lock->unlock(); + ///////////////////////////////////////////////// + + ~AsyncWebLockGuard() + { + if (_lock) + { + _lock->unlock(); + } } - } }; #endif // ASYNCWEBSYNCHRONIZATION_ETHERNET_H_ diff --git a/src/StringArray_Ethernet.h b/src/StringArray_Ethernet.h index 79dc80e..d14d9a8 100644 --- a/src/StringArray_Ethernet.h +++ b/src/StringArray_Ethernet.h @@ -12,14 +12,16 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . + You should have received a copy of the GNU General Public License along with this program. + If not, see . - Version: 1.4.1 + Version: 1.5.0 Version Modified By Date Comments ------- ----------- ---------- ----------- 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + 1.5.0 K Hoang 05/10/2022 Option to use non-destroyed cString instead of String to save Heap *****************************************************************************************************************************/ #pragma once @@ -30,6 +32,8 @@ #include "stddef.h" #include "WString.h" +///////////////////////////////////////////////// + template class LinkedListNode { @@ -40,17 +44,24 @@ class LinkedListNode LinkedListNode(const T val): _value(val), next(nullptr) {} ~LinkedListNode() {} - const T& value() const + ///////////////////////////////////////////////// + + inline const T& value() const { return _value; }; - - T& value() + + ///////////////////////////////////////////////// + + inline T& value() { return _value; } }; +///////////////////////////////////////////////// +///////////////////////////////////////////////// + template class Item = LinkedListNode> class LinkedList { @@ -71,23 +82,31 @@ class LinkedList Iterator(ItemType* current = nullptr) : _node(current) {} Iterator(const Iterator& i) : _node(i._node) {} - Iterator& operator ++() + ///////////////////////////////////////////////// + + inline Iterator& operator ++() { _node = _node->next; return *this; } - - bool operator != (const Iterator& i) const + + ///////////////////////////////////////////////// + + inline bool operator != (const Iterator& i) const { return _node != i._node; } - - const T& operator * () const + + ///////////////////////////////////////////////// + + inline const T& operator * () const { return _node->value(); } - - const T* operator -> () const + + ///////////////////////////////////////////////// + + inline const T* operator -> () const { return &_node->value(); } @@ -96,118 +115,139 @@ class LinkedList public: typedef const Iterator ConstIterator; - ConstIterator begin() const + ///////////////////////////////////////////////// + + inline ConstIterator begin() const { return ConstIterator(_root); } - - ConstIterator end() const + + ///////////////////////////////////////////////// + + inline ConstIterator end() const { return ConstIterator(nullptr); } + ///////////////////////////////////////////////// + LinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {} ~LinkedList() {} - - void add(const T& t) + + ///////////////////////////////////////////////// + + void add(const T& t) { auto it = new ItemType(t); - - if (!_root) + + if (!_root) { _root = it; - } - else + } + else { auto i = _root; - - while (i->next) + + while (i->next) i = i->next; - + i->next = it; } } - - T& front() const + + ///////////////////////////////////////////////// + + inline T& front() const { return _root->value(); } - bool isEmpty() const + ///////////////////////////////////////////////// + + inline bool isEmpty() const { return _root == nullptr; } - - size_t length() const + + ///////////////////////////////////////////////// + + size_t length() const { size_t i = 0; auto it = _root; - - while (it) + + while (it) { i++; it = it->next; } - + return i; } - - size_t count_if(Predicate predicate) const + + ///////////////////////////////////////////////// + + size_t count_if(Predicate predicate) const { size_t i = 0; auto it = _root; - - while (it) + + while (it) { - if (!predicate) + if (!predicate) { i++; } - else if (predicate(it->value())) + else if (predicate(it->value())) { i++; } - + it = it->next; } + return i; } - - const T* nth(size_t N) const + + ///////////////////////////////////////////////// + + const T* nth(size_t N) const { size_t i = 0; auto it = _root; - while (it) + while (it) { if (i++ == N) return &(it->value()); - + it = it->next; } - + return nullptr; } - - bool remove(const T& t) + + ///////////////////////////////////////////////// + + bool remove(const T& t) { auto it = _root; auto pit = _root; - - while (it) + + while (it) { - if (it->value() == t) + if (it->value() == t) { - if (it == _root) + if (it == _root) { _root = _root->next; - } - else + } + else { pit->next = it->next; } - if (_onRemove) + if (_onRemove) { _onRemove(it->value()); } @@ -215,67 +255,73 @@ class LinkedList delete it; return true; } - + pit = it; it = it->next; } - + return false; } - - bool remove_first(Predicate predicate) + + ///////////////////////////////////////////////// + + bool remove_first(Predicate predicate) { auto it = _root; auto pit = _root; - - while (it) + + while (it) { - if (predicate(it->value())) + if (predicate(it->value())) { - if (it == _root) + if (it == _root) { _root = _root->next; - } - else + } + else { pit->next = it->next; } - - if (_onRemove) + + if (_onRemove) { _onRemove(it->value()); } - + delete it; return true; } - + pit = it; it = it->next; } - + return false; } - void free() + ///////////////////////////////////////////////// + + void free() { - while (_root != nullptr) + while (_root != nullptr) { auto it = _root; _root = _root->next; - - if (_onRemove) + + if (_onRemove) { _onRemove(it->value()); } - + delete it; } - + _root = nullptr; } }; +///////////////////////////////////////////////// +///////////////////////////////////////////////// class StringArray : public LinkedList { @@ -283,16 +329,18 @@ class StringArray : public LinkedList StringArray() : LinkedList(nullptr) {} - bool containsIgnoreCase(const String& str) + ///////////////////////////////////////////////// + + bool containsIgnoreCase(const String& str) { - for (const auto& s : *this) + for (const auto& s : *this) { - if (str.equalsIgnoreCase(s)) + if (str.equalsIgnoreCase(s)) { return true; } } - + return false; } }; diff --git a/src/libb64/cdecode.c b/src/libb64/cdecode.c index 9a1c8c2..9f2a1c6 100644 --- a/src/libb64/cdecode.c +++ b/src/libb64/cdecode.c @@ -17,14 +17,8 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . - - Version: 1.4.1 - - Version Modified By Date Comments - ------- ----------- ---------- ----------- - 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. - Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + You should have received a copy of the GNU General Public License along with this program. + If not, see *****************************************************************************************************************************/ #include "cdecode.h" diff --git a/src/libb64/cdecode.h b/src/libb64/cdecode.h index 6da8e7c..91fdceb 100644 --- a/src/libb64/cdecode.h +++ b/src/libb64/cdecode.h @@ -17,14 +17,8 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . - - Version: 1.4.1 - - Version Modified By Date Comments - ------- ----------- ---------- ----------- - 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. - Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + You should have received a copy of the GNU General Public License along with this program. + If not, see *****************************************************************************************************************************/ #pragma once diff --git a/src/libb64/cencode.c b/src/libb64/cencode.c index a46744b..7405704 100644 --- a/src/libb64/cencode.c +++ b/src/libb64/cencode.c @@ -17,14 +17,8 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . - - Version: 1.4.1 - - Version Modified By Date Comments - ------- ----------- ---------- ----------- - 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. - Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + You should have received a copy of the GNU General Public License along with this program. + If not, see *****************************************************************************************************************************/ #include "cencode.h" diff --git a/src/libb64/cencode.h b/src/libb64/cencode.h index b05fed6..8972390 100644 --- a/src/libb64/cencode.h +++ b/src/libb64/cencode.h @@ -17,14 +17,8 @@ as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . - - Version: 1.4.1 - - Version Modified By Date Comments - ------- ----------- ---------- ----------- - 1.4.1 K Hoang 18/03/2022 Initial coding for ESP8266 using W5x00/ENC8266 Ethernet. - Bump up version to v1.4.1 to sync with AsyncWebServer_STM32 v1.4.1 + You should have received a copy of the GNU General Public License along with this program. + If not, see *****************************************************************************************************************************/ #pragma once