diff --git a/lib/ESPAsyncWebServer/LICENSE b/lib/ESPAsyncWebServer/LICENSE deleted file mode 100644 index 153d416..0000000 --- a/lib/ESPAsyncWebServer/LICENSE +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. \ No newline at end of file diff --git a/lib/ESPAsyncWebServer/README.md b/lib/ESPAsyncWebServer/README.md deleted file mode 100644 index 6ca1d1e..0000000 --- a/lib/ESPAsyncWebServer/README.md +++ /dev/null @@ -1,126 +0,0 @@ -# ESPAsyncWebServer - -[![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/) -[![Continuous Integration](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml/badge.svg)](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml) -[![PlatformIO Registry](https://badges.registry.platformio.org/packages/mathieucarbou/library/ESPAsyncWebServer.svg)](https://registry.platformio.org/libraries/mathieucarbou/ESPAsyncWebServer) - -Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040 -Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc. - -This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) and includes all the concurrency fixes. - -## Coordinate and dependencies: - -**WARNING** The library name was changed from `ESP Async WebServer` to `ESPAsyncWebServer` as per the Arduino Lint recommendations. - -``` -mathieucarbou/ESPAsyncWebServer @ 3.1.5 -``` - -Dependency: - -- **ESP32**: `mathieucarbou/AsyncTCP @ 3.2.4` (Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.2.4](https://github.com/mathieucarbou/AsyncTCP/releases/tag/v3.2.0)) -- **ESP8266**: `esphome/ESPAsyncTCP-esphome @ 2.0.0` (Arduino IDE: [https://github.com/mathieucarbou/esphome-ESPAsyncTCP#v2.0.0](https://github.com/mathieucarbou/esphome-ESPAsyncTCP/releases/tag/v2.0.0)) -- **RP2040**: `khoih-prog/AsyncTCP_RP2040W @ 1.2.0` (Arduino IDE: [https://github.com/khoih-prog/AsyncTCP_RP2040W#v1.2.0](https://github.com/khoih-prog/AsyncTCP_RP2040W/releases/tag/v1.2.0)) - -## Changes in this fork - -- [@ayushsharma82](https://github.com/ayushsharma82) and [@mathieucarbou](https://github.com/mathieucarbou): Add RP2040 support ([#31](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/31)) -- [@mathieucarbou](https://github.com/mathieucarbou): `SSE_MAX_QUEUED_MESSAGES` to control the maximum number of messages that can be queued for a SSE client -- [@mathieucarbou](https://github.com/mathieucarbou): `write()` function public in `AsyncEventSource.h` -- [@mathieucarbou](https://github.com/mathieucarbou): `WS_MAX_QUEUED_MESSAGES`: control the maximum number of messages that can be queued for a Websocket client -- [@mathieucarbou](https://github.com/mathieucarbou): Added `setAuthentication(const String& username, const String& password)` -- [@mathieucarbou](https://github.com/mathieucarbou): Added `setCloseClientOnQueueFull(bool)` which can be set on a client to either close the connection or discard messages but not close the connection when the queue is full -- [@mathieucarbou](https://github.com/mathieucarbou): Added `StreamConcat` example to show how to stream multiple files in one response -- [@mathieucarbou](https://github.com/mathieucarbou): Added all flavors of `binary()`, `text()`, `binaryAll()` and `textAll()` in `AsyncWebSocket` -- [@mathieucarbou](https://github.com/mathieucarbou): Arduino 3 / ESP-IDF 5.1 compatibility -- [@mathieucarbou](https://github.com/mathieucarbou): Arduino Json 7 compatibility and backward compatible with 6 and 6 (changes in `AsyncJson.h`). The API to use Json has not changed. These are only internal changes. -- [@mathieucarbou](https://github.com/mathieucarbou): CI -- [@mathieucarbou](https://github.com/mathieucarbou): Depends on `mathieucarbou/AsyncTCP @ 3.2.4` -- [@mathieucarbou](https://github.com/mathieucarbou): Deployed in PlatformIO registry and Arduino IDE library manager -- [@mathieucarbou](https://github.com/mathieucarbou): Firmware size optimization: remove mbedtls dependency (accounts for 33KB in firmware) -- [@mathieucarbou](https://github.com/mathieucarbou): Made DEFAULT_MAX_SSE_CLIENTS customizable -- [@mathieucarbou](https://github.com/mathieucarbou): Made DEFAULT_MAX_WS_CLIENTS customizable -- [@mathieucarbou](https://github.com/mathieucarbou): MessagePack Support ([#62](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/62)) -- [@mathieucarbou](https://github.com/mathieucarbou): Remove filename after inline in Content-Disposition header according to RFC2183 -- [@mathieucarbou](https://github.com/mathieucarbou): Removed SPIFFSEditor to reduce library size and maintainance. SPIFF si also deprecated. If you need it, please copy the files from the original repository in your project. This fork focus on maintaining the server part and the SPIFFEditor is an application which has nothing to do inside a server library. -- [@mathieucarbou](https://github.com/mathieucarbou): Resurrected `AsyncWebSocketMessageBuffer` and `makeBuffer()` in order to make the fork API-compatible with the original library from me-no-dev regarding WebSocket. -- [@mathieucarbou](https://github.com/mathieucarbou): Some code cleanup -- [@mathieucarbou](https://github.com/mathieucarbou): Use `-D DEFAULT_MAX_WS_CLIENTS` to change the number of allows WebSocket clients and use `cleanupClients()` to help cleanup resources about dead clients -- [@nilo85](https://github.com/nilo85): Add support for Auth & GET requests in AsyncCallbackJsonWebHandler ([#14](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/14)) -- [@p0p-x](https://github.com/p0p-x): ESP IDF Compatibility (added back CMakeLists.txt) ([#32](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/32)) -- [@tueddy](https://github.com/tueddy): Compile with Arduino 3 (ESP-IDF 5.1) ([#13](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/13)) -- [@vortigont](https://github.com/vortigont): Set real "Last-Modified" header based on file's LastWrite time ([#5](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/5)) -- [@vortigont](https://github.com/vortigont): Some websocket code cleanup ([#29](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/29)) -- [@vortigont](https://github.com/vortigont): Refactor code - replace DYI structs with STL objects ([#39](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/39)) - -## Documentation - -Usage and API stays the same as the original library. -Please look at the original libraries for more examples and documentation. - -- [https://github.com/me-no-dev/ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) (original library) -- [https://github.com/yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) (fork of the original library) - -## `AsyncWebSocketMessageBuffer` and `makeBuffer()` - -The fork from `yubox-node-org` introduces some breaking API changes compared to the original library, especially regarding the use of `std::shared_ptr>` for WebSocket. - -This fork is compatible with the original library from `me-no-dev` regarding WebSocket, and wraps the optimizations done by `yubox-node-org` in the `AsyncWebSocketMessageBuffer` class. -So you have the choice of which API to use. - -Here are examples for serializing a Json document in a websocket message buffer: - -```cpp -void send(JsonDocument& doc) { - const size_t len = measureJson(doc); - - // original API from me-no-dev - AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len); - assert(buffer); // up to you to keep or remove this - serializeJson(doc, buffer->get(), len); - _ws->textAll(buffer); -} -``` - -```cpp -void send(JsonDocument& doc) { - const size_t len = measureJson(doc); - - // this fork (originally from yubox-node-org), uses another API with shared pointer - auto buffer = std::make_shared>(len); - assert(buffer); // up to you to keep or remove this - serializeJson(doc, buffer->data(), len); - _ws->textAll(std::move(buffer)); -} -``` - -I recommend to use the official API `AsyncWebSocketMessageBuffer` to retain further compatibility. - -## Important recommendations - -Most of the crashes are caused by improper configuration of the library for the project. -Here are some recommendations to avoid them. - -1. Set the running core to be on the same core of your application (usually core 1) `-D CONFIG_ASYNC_TCP_RUNNING_CORE=1` -2. Set the stack size appropriately with `-D CONFIG_ASYNC_TCP_STACK_SIZE=16384`. - The default value of `16384` might be too much for your project. - You can look at the [MycilaTaskMonitor](https://oss.carbou.me/MycilaTaskMonitor) project to monitor the stack usage. -3. You can change **if you know what you are doing** the task priority with `-D CONFIG_ASYNC_TCP_PRIORITY=10`. - Default is `10`. -4. You can increase the queue size with `-D CONFIG_ASYNC_TCP_QUEUE_SIZE=128`. - Default is `64`. -5. You can decrease the maximum ack time `-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000`. - Default is `5000`. - -I personally use the following configuration in my projects because my WS messages can be big (up to 4k). -If you have smaller messages, you can increase `WS_MAX_QUEUED_MESSAGES` to 128. - -```c++ - -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000 - -D CONFIG_ASYNC_TCP_PRIORITY=10 - -D CONFIG_ASYNC_TCP_QUEUE_SIZE=128 - -D CONFIG_ASYNC_TCP_RUNNING_CORE=1 - -D CONFIG_ASYNC_TCP_STACK_SIZE=4096 - -D WS_MAX_QUEUED_MESSAGES=64 -``` diff --git a/lib/ESPAsyncWebServer/docs/_config.yml b/lib/ESPAsyncWebServer/docs/_config.yml deleted file mode 100644 index 3636597..0000000 --- a/lib/ESPAsyncWebServer/docs/_config.yml +++ /dev/null @@ -1,8 +0,0 @@ -# bundle exec jekyll serve --host=0.0.0.0 - -title: ESPAsyncWebServer -description: "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040" -remote_theme: pages-themes/cayman@v0.2.0 -plugins: - - jekyll-remote-theme - \ No newline at end of file diff --git a/lib/ESPAsyncWebServer/docs/index.md b/lib/ESPAsyncWebServer/docs/index.md deleted file mode 100644 index 6ca1d1e..0000000 --- a/lib/ESPAsyncWebServer/docs/index.md +++ /dev/null @@ -1,126 +0,0 @@ -# ESPAsyncWebServer - -[![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/) -[![Continuous Integration](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml/badge.svg)](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml) -[![PlatformIO Registry](https://badges.registry.platformio.org/packages/mathieucarbou/library/ESPAsyncWebServer.svg)](https://registry.platformio.org/libraries/mathieucarbou/ESPAsyncWebServer) - -Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040 -Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc. - -This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) and includes all the concurrency fixes. - -## Coordinate and dependencies: - -**WARNING** The library name was changed from `ESP Async WebServer` to `ESPAsyncWebServer` as per the Arduino Lint recommendations. - -``` -mathieucarbou/ESPAsyncWebServer @ 3.1.5 -``` - -Dependency: - -- **ESP32**: `mathieucarbou/AsyncTCP @ 3.2.4` (Arduino IDE: [https://github.com/mathieucarbou/AsyncTCP#v3.2.4](https://github.com/mathieucarbou/AsyncTCP/releases/tag/v3.2.0)) -- **ESP8266**: `esphome/ESPAsyncTCP-esphome @ 2.0.0` (Arduino IDE: [https://github.com/mathieucarbou/esphome-ESPAsyncTCP#v2.0.0](https://github.com/mathieucarbou/esphome-ESPAsyncTCP/releases/tag/v2.0.0)) -- **RP2040**: `khoih-prog/AsyncTCP_RP2040W @ 1.2.0` (Arduino IDE: [https://github.com/khoih-prog/AsyncTCP_RP2040W#v1.2.0](https://github.com/khoih-prog/AsyncTCP_RP2040W/releases/tag/v1.2.0)) - -## Changes in this fork - -- [@ayushsharma82](https://github.com/ayushsharma82) and [@mathieucarbou](https://github.com/mathieucarbou): Add RP2040 support ([#31](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/31)) -- [@mathieucarbou](https://github.com/mathieucarbou): `SSE_MAX_QUEUED_MESSAGES` to control the maximum number of messages that can be queued for a SSE client -- [@mathieucarbou](https://github.com/mathieucarbou): `write()` function public in `AsyncEventSource.h` -- [@mathieucarbou](https://github.com/mathieucarbou): `WS_MAX_QUEUED_MESSAGES`: control the maximum number of messages that can be queued for a Websocket client -- [@mathieucarbou](https://github.com/mathieucarbou): Added `setAuthentication(const String& username, const String& password)` -- [@mathieucarbou](https://github.com/mathieucarbou): Added `setCloseClientOnQueueFull(bool)` which can be set on a client to either close the connection or discard messages but not close the connection when the queue is full -- [@mathieucarbou](https://github.com/mathieucarbou): Added `StreamConcat` example to show how to stream multiple files in one response -- [@mathieucarbou](https://github.com/mathieucarbou): Added all flavors of `binary()`, `text()`, `binaryAll()` and `textAll()` in `AsyncWebSocket` -- [@mathieucarbou](https://github.com/mathieucarbou): Arduino 3 / ESP-IDF 5.1 compatibility -- [@mathieucarbou](https://github.com/mathieucarbou): Arduino Json 7 compatibility and backward compatible with 6 and 6 (changes in `AsyncJson.h`). The API to use Json has not changed. These are only internal changes. -- [@mathieucarbou](https://github.com/mathieucarbou): CI -- [@mathieucarbou](https://github.com/mathieucarbou): Depends on `mathieucarbou/AsyncTCP @ 3.2.4` -- [@mathieucarbou](https://github.com/mathieucarbou): Deployed in PlatformIO registry and Arduino IDE library manager -- [@mathieucarbou](https://github.com/mathieucarbou): Firmware size optimization: remove mbedtls dependency (accounts for 33KB in firmware) -- [@mathieucarbou](https://github.com/mathieucarbou): Made DEFAULT_MAX_SSE_CLIENTS customizable -- [@mathieucarbou](https://github.com/mathieucarbou): Made DEFAULT_MAX_WS_CLIENTS customizable -- [@mathieucarbou](https://github.com/mathieucarbou): MessagePack Support ([#62](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/62)) -- [@mathieucarbou](https://github.com/mathieucarbou): Remove filename after inline in Content-Disposition header according to RFC2183 -- [@mathieucarbou](https://github.com/mathieucarbou): Removed SPIFFSEditor to reduce library size and maintainance. SPIFF si also deprecated. If you need it, please copy the files from the original repository in your project. This fork focus on maintaining the server part and the SPIFFEditor is an application which has nothing to do inside a server library. -- [@mathieucarbou](https://github.com/mathieucarbou): Resurrected `AsyncWebSocketMessageBuffer` and `makeBuffer()` in order to make the fork API-compatible with the original library from me-no-dev regarding WebSocket. -- [@mathieucarbou](https://github.com/mathieucarbou): Some code cleanup -- [@mathieucarbou](https://github.com/mathieucarbou): Use `-D DEFAULT_MAX_WS_CLIENTS` to change the number of allows WebSocket clients and use `cleanupClients()` to help cleanup resources about dead clients -- [@nilo85](https://github.com/nilo85): Add support for Auth & GET requests in AsyncCallbackJsonWebHandler ([#14](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/14)) -- [@p0p-x](https://github.com/p0p-x): ESP IDF Compatibility (added back CMakeLists.txt) ([#32](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/32)) -- [@tueddy](https://github.com/tueddy): Compile with Arduino 3 (ESP-IDF 5.1) ([#13](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/13)) -- [@vortigont](https://github.com/vortigont): Set real "Last-Modified" header based on file's LastWrite time ([#5](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/5)) -- [@vortigont](https://github.com/vortigont): Some websocket code cleanup ([#29](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/29)) -- [@vortigont](https://github.com/vortigont): Refactor code - replace DYI structs with STL objects ([#39](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/39)) - -## Documentation - -Usage and API stays the same as the original library. -Please look at the original libraries for more examples and documentation. - -- [https://github.com/me-no-dev/ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer) (original library) -- [https://github.com/yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) (fork of the original library) - -## `AsyncWebSocketMessageBuffer` and `makeBuffer()` - -The fork from `yubox-node-org` introduces some breaking API changes compared to the original library, especially regarding the use of `std::shared_ptr>` for WebSocket. - -This fork is compatible with the original library from `me-no-dev` regarding WebSocket, and wraps the optimizations done by `yubox-node-org` in the `AsyncWebSocketMessageBuffer` class. -So you have the choice of which API to use. - -Here are examples for serializing a Json document in a websocket message buffer: - -```cpp -void send(JsonDocument& doc) { - const size_t len = measureJson(doc); - - // original API from me-no-dev - AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len); - assert(buffer); // up to you to keep or remove this - serializeJson(doc, buffer->get(), len); - _ws->textAll(buffer); -} -``` - -```cpp -void send(JsonDocument& doc) { - const size_t len = measureJson(doc); - - // this fork (originally from yubox-node-org), uses another API with shared pointer - auto buffer = std::make_shared>(len); - assert(buffer); // up to you to keep or remove this - serializeJson(doc, buffer->data(), len); - _ws->textAll(std::move(buffer)); -} -``` - -I recommend to use the official API `AsyncWebSocketMessageBuffer` to retain further compatibility. - -## Important recommendations - -Most of the crashes are caused by improper configuration of the library for the project. -Here are some recommendations to avoid them. - -1. Set the running core to be on the same core of your application (usually core 1) `-D CONFIG_ASYNC_TCP_RUNNING_CORE=1` -2. Set the stack size appropriately with `-D CONFIG_ASYNC_TCP_STACK_SIZE=16384`. - The default value of `16384` might be too much for your project. - You can look at the [MycilaTaskMonitor](https://oss.carbou.me/MycilaTaskMonitor) project to monitor the stack usage. -3. You can change **if you know what you are doing** the task priority with `-D CONFIG_ASYNC_TCP_PRIORITY=10`. - Default is `10`. -4. You can increase the queue size with `-D CONFIG_ASYNC_TCP_QUEUE_SIZE=128`. - Default is `64`. -5. You can decrease the maximum ack time `-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000`. - Default is `5000`. - -I personally use the following configuration in my projects because my WS messages can be big (up to 4k). -If you have smaller messages, you can increase `WS_MAX_QUEUED_MESSAGES` to 128. - -```c++ - -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000 - -D CONFIG_ASYNC_TCP_PRIORITY=10 - -D CONFIG_ASYNC_TCP_QUEUE_SIZE=128 - -D CONFIG_ASYNC_TCP_RUNNING_CORE=1 - -D CONFIG_ASYNC_TCP_STACK_SIZE=4096 - -D WS_MAX_QUEUED_MESSAGES=64 -``` diff --git a/lib/ESPAsyncWebServer/examples/CaptivePortal/CaptivePortal.ino b/lib/ESPAsyncWebServer/examples/CaptivePortal/CaptivePortal.ino deleted file mode 100644 index 2d0de89..0000000 --- a/lib/ESPAsyncWebServer/examples/CaptivePortal/CaptivePortal.ino +++ /dev/null @@ -1,57 +0,0 @@ -#include -#ifdef ESP32 - #include - #include -#elif defined(ESP8266) - #include - #include -#elif defined(TARGET_RP2040) - #include - #include -#endif -#include "ESPAsyncWebServer.h" - -DNSServer dnsServer; -AsyncWebServer server(80); - -class CaptiveRequestHandler : public AsyncWebHandler { - public: - CaptiveRequestHandler() {} - virtual ~CaptiveRequestHandler() {} - - bool canHandle(__unused AsyncWebServerRequest* request) { - // request->addInterestingHeader("ANY"); - return true; - } - - void handleRequest(AsyncWebServerRequest* request) { - AsyncResponseStream* response = request->beginResponseStream("text/html"); - response->print("Captive Portal"); - response->print("

This is out captive portal front page.

"); - response->printf("

You were trying to reach: http://%s%s

", request->host().c_str(), request->url().c_str()); - response->printf("

Try opening this link instead

", WiFi.softAPIP().toString().c_str()); - response->print(""); - request->send(response); - } -}; - -void setup() { - Serial.begin(115200); - Serial.println(); - Serial.println("Configuring access point..."); - - if (!WiFi.softAP("esp-captive")) { - Serial.println("Soft AP creation failed."); - while (1) - ; - } - - dnsServer.start(53, "*", WiFi.softAPIP()); - server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER); // only when requested from AP - // more handlers... - server.begin(); -} - -void loop() { - dnsServer.processNextRequest(); -} diff --git a/lib/ESPAsyncWebServer/examples/Draft/Draft.ino b/lib/ESPAsyncWebServer/examples/Draft/Draft.ino deleted file mode 100644 index f10a9e7..0000000 --- a/lib/ESPAsyncWebServer/examples/Draft/Draft.ino +++ /dev/null @@ -1,37 +0,0 @@ -#include "mbedtls/md5.h" -#include -#include - -void setup() { - Serial.begin(115200); - delay(2000); - - const char* data = "Hello World"; - - { - uint8_t md5[16]; - mbedtls_md5_context _ctx; - mbedtls_md5_init(&_ctx); - mbedtls_md5_starts(&_ctx); - mbedtls_md5_update(&_ctx, (const unsigned char*)data, strlen(data)); - mbedtls_md5_finish(&_ctx, md5); - char output[33]; - for (int i = 0; i < 16; i++) { - sprintf_P(output + (i * 2), PSTR("%02x"), md5[i]); - } - Serial.println(String(output)); - } - - { - MD5Builder md5; - md5.begin(); - md5.add(data, strlen(data); - md5.calculate(); - char output[33]; - md5.getChars(output); - Serial.println(String(output)); - } -} - -void loop() { -} diff --git a/lib/ESPAsyncWebServer/examples/Filters/Filters.ino b/lib/ESPAsyncWebServer/examples/Filters/Filters.ino deleted file mode 100644 index f031a1f..0000000 --- a/lib/ESPAsyncWebServer/examples/Filters/Filters.ino +++ /dev/null @@ -1,111 +0,0 @@ -// Reproduced issue https://github.com/mathieucarbou/ESPAsyncWebServer/issues/26 - -#include -#ifdef ESP32 - #include - #include -#elif defined(ESP8266) - #include - #include -#elif defined(TARGET_RP2040) - #include - #include -#endif -#include "ESPAsyncWebServer.h" - -DNSServer dnsServer; -AsyncWebServer server(80); - -class CaptiveRequestHandler : public AsyncWebHandler { - public: - CaptiveRequestHandler() {} - virtual ~CaptiveRequestHandler() {} - - bool canHandle(__unused AsyncWebServerRequest* request) { - // request->addInterestingHeader("ANY"); - return true; - } - - void handleRequest(AsyncWebServerRequest* request) { - AsyncResponseStream* response = request->beginResponseStream("text/html"); - response->print("Captive Portal"); - response->print("

This is out captive portal front page.

"); - response->printf("

You were trying to reach: http://%s%s

", request->host().c_str(), request->url().c_str()); - response->printf("

Try opening this link instead

", WiFi.softAPIP().toString().c_str()); - response->print(""); - request->send(response); - } -}; - -bool hit1 = false; -bool hit2 = false; - -void setup() { - Serial.begin(115200); - - server - .on("/", HTTP_GET, [](AsyncWebServerRequest* request) { - Serial.println("Captive portal request..."); - Serial.println("WiFi.localIP(): " + WiFi.localIP().toString()); - Serial.println("request->client()->localIP(): " + request->client()->localIP().toString()); -#if ESP_IDF_VERSION_MAJOR >= 5 - Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type())); - Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type())); -#endif - Serial.println(WiFi.localIP() == request->client()->localIP() ? "should be: ON_STA_FILTER" : "should be: ON_AP_FILTER"); - Serial.println(WiFi.localIP() == request->client()->localIP()); - Serial.println(WiFi.localIP().toString() == request->client()->localIP().toString()); - request->send(200, "text/plain", "This is the captive portal"); - hit1 = true; - }) - .setFilter(ON_AP_FILTER); - - server - .on("/", HTTP_GET, [](AsyncWebServerRequest* request) { - Serial.println("Website request..."); - Serial.println("WiFi.localIP(): " + WiFi.localIP().toString()); - Serial.println("request->client()->localIP(): " + request->client()->localIP().toString()); -#if ESP_IDF_VERSION_MAJOR >= 5 - Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type())); - Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type())); -#endif - Serial.println(WiFi.localIP() == request->client()->localIP() ? "should be: ON_STA_FILTER" : "should be: ON_AP_FILTER"); - Serial.println(WiFi.localIP() == request->client()->localIP()); - Serial.println(WiFi.localIP().toString() == request->client()->localIP().toString()); - request->send(200, "text/plain", "This is the website"); - hit2 = true; - }) - .setFilter(ON_STA_FILTER); - - // assert(WiFi.softAP("esp-captive-portal")); - // dnsServer.start(53, "*", WiFi.softAPIP()); - // server.begin(); - // Serial.println("Captive portal started!"); - - // while (!hit1) { - // dnsServer.processNextRequest(); - // yield(); - // } - // delay(1000); // Wait for the client to process the response - - // Serial.println("Captive portal opened, stopping it and connecting to WiFi..."); - // dnsServer.stop(); - // WiFi.softAPdisconnect(); - - WiFi.persistent(false); - WiFi.begin("IoT"); - while (WiFi.status() != WL_CONNECTED) { - delay(500); - } - Serial.println("Connected to WiFi with IP address: " + WiFi.localIP().toString()); - server.begin(); - - // while (!hit2) { - // delay(10); - // } - // delay(1000); // Wait for the client to process the response - // ESP.restart(); -} - -void loop() { -} diff --git a/lib/ESPAsyncWebServer/examples/SimpleServer/SimpleServer.ino b/lib/ESPAsyncWebServer/examples/SimpleServer/SimpleServer.ino deleted file mode 100644 index f3a1605..0000000 --- a/lib/ESPAsyncWebServer/examples/SimpleServer/SimpleServer.ino +++ /dev/null @@ -1,134 +0,0 @@ -// -// A simple server implementation showing how to: -// * serve static messages -// * read GET and POST parameters -// * handle missing pages / 404s -// - -#include -#ifdef ESP32 - #include - #include -#elif defined(ESP8266) - #include - #include -#elif defined(TARGET_RP2040) - #include - #include -#endif - -#include - -#include -#include -#include - -AsyncWebServer server(80); - -const char* PARAM_MESSAGE = "message"; - -void notFound(AsyncWebServerRequest* request) { - request->send(404, "text/plain", "Not found"); -} - -AsyncCallbackJsonWebHandler* jsonHandler = new AsyncCallbackJsonWebHandler("/json2"); -AsyncCallbackMessagePackWebHandler* msgPackHandler = new AsyncCallbackMessagePackWebHandler("/msgpack2"); - -void setup() { - - Serial.begin(115200); - - // WiFi.mode(WIFI_STA); - // WiFi.begin("YOUR_SSID", "YOUR_PASSWORD"); - // if (WiFi.waitForConnectResult() != WL_CONNECTED) { - // Serial.printf("WiFi Failed!\n"); - // return; - // } - // Serial.print("IP Address: "); - // Serial.println(WiFi.localIP()); - - WiFi.mode(WIFI_AP); - WiFi.softAP("esp-captive"); - - server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { - request->send(200, "text/plain", "Hello, world"); - }); - - // Send a GET request to /get?message= - server.on("/get", HTTP_GET, [](AsyncWebServerRequest* request) { - String message; - if (request->hasParam(PARAM_MESSAGE)) { - message = request->getParam(PARAM_MESSAGE)->value(); - } else { - message = "No message sent"; - } - request->send(200, "text/plain", "Hello, GET: " + message); - }); - - // Send a POST request to /post with a form field message set to - server.on("/post", HTTP_POST, [](AsyncWebServerRequest* request) { - String message; - if (request->hasParam(PARAM_MESSAGE, true)) { - message = request->getParam(PARAM_MESSAGE, true)->value(); - } else { - message = "No message sent"; - } - request->send(200, "text/plain", "Hello, POST: " + message); - }); - - // JSON - - // receives JSON and sends JSON - jsonHandler->onRequest([](AsyncWebServerRequest* request, JsonVariant& json) { - JsonObject jsonObj = json.as(); - // ... - - AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot().to(); - root["hello"] = "world"; - response->setLength(); - request->send(response); - }); - - // sends JSON - server.on("/json1", HTTP_GET, [](AsyncWebServerRequest* request) { - AsyncJsonResponse* response = new AsyncJsonResponse(); - JsonObject root = response->getRoot().to(); - root["hello"] = "world"; - response->setLength(); - request->send(response); - }); - - // MessagePack - - // receives MessagePack and sends MessagePack - msgPackHandler->onRequest([](AsyncWebServerRequest* request, JsonVariant& json) { - JsonObject jsonObj = json.as(); - // ... - - AsyncMessagePackResponse* response = new AsyncMessagePackResponse(); - JsonObject root = response->getRoot().to(); - root["hello"] = "world"; - response->setLength(); - request->send(response); - }); - - // sends MessagePack - server.on("/msgpack1", HTTP_GET, [](AsyncWebServerRequest* request) { - AsyncMessagePackResponse* response = new AsyncMessagePackResponse(); - JsonObject root = response->getRoot().to(); - root["hello"] = "world"; - response->setLength(); - request->send(response); - }); - - server.addHandler(jsonHandler); - server.addHandler(msgPackHandler); - - server.onNotFound(notFound); - - server.begin(); -} - -void loop() { -} \ No newline at end of file diff --git a/lib/ESPAsyncWebServer/examples/StreamFiles/StreamConcat.h b/lib/ESPAsyncWebServer/examples/StreamFiles/StreamConcat.h deleted file mode 100644 index c1e1927..0000000 --- a/lib/ESPAsyncWebServer/examples/StreamFiles/StreamConcat.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include - -class StreamConcat : public Stream { - public: - StreamConcat(Stream* s1, Stream* s2) : _s1(s1), _s2(s2) {} - - size_t write(__unused const uint8_t* p, __unused size_t n) override { return 0; } - size_t write(__unused uint8_t c) override { return 0; } - void flush() override {} - - int available() override { return _s1->available() + _s2->available(); } - - int read() override { - int c = _s1->read(); - return c != -1 ? c : _s2->read(); - } - -#if defined(TARGET_RP2040) - size_t readBytes(char* buffer, size_t length) { -#else - size_t readBytes(char* buffer, size_t length) override { -#endif - size_t count = _s1->readBytes(buffer, length); - return count > 0 ? count : _s2->readBytes(buffer, length); - } - - int peek() override { - int c = _s1->peek(); - return c != -1 ? c : _s2->peek(); - } - - private: - Stream* _s1; - Stream* _s2; -}; diff --git a/lib/ESPAsyncWebServer/examples/StreamFiles/StreamFiles.ino b/lib/ESPAsyncWebServer/examples/StreamFiles/StreamFiles.ino deleted file mode 100644 index 2a2c1b6..0000000 --- a/lib/ESPAsyncWebServer/examples/StreamFiles/StreamFiles.ino +++ /dev/null @@ -1,84 +0,0 @@ -#include -#include -#ifdef ESP32 - #include - #include -#elif defined(ESP8266) - #include - #include -#elif defined(TARGET_RP2040) - #include - #include -#endif -#include "StreamConcat.h" -#include "StreamString.h" -#include -#include - -DNSServer dnsServer; -AsyncWebServer server(80); - -void setup() { - Serial.begin(115200); - - LittleFS.begin(); - - WiFi.mode(WIFI_AP); - WiFi.softAP("esp-captive"); - dnsServer.start(53, "*", WiFi.softAPIP()); - - File file1 = LittleFS.open("/header.html", "w"); - file1.print("ESP Captive Portal"); - file1.close(); - - File file2 = LittleFS.open("/body.html", "w"); - file2.print("

Welcome to ESP Captive Portal

"); - file2.close(); - - File file3 = LittleFS.open("/footer.html", "w"); - file3.print(""); - file3.close(); - - server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { - File header = LittleFS.open("/header.html", "r"); - File body = LittleFS.open("/body.html", "r"); - StreamConcat stream1(&header, &body); - - StreamString content; -#if defined(TARGET_RP2040) - content.printf("FreeHeap: %d", rp2040.getFreeHeap()); -#else - content.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap()); -#endif - StreamConcat stream2 = StreamConcat(&stream1, &content); - - File footer = LittleFS.open("/footer.html", "r"); - StreamConcat stream3 = StreamConcat(&stream2, &footer); - - request->send(stream3, "text/html", stream3.available()); - header.close(); - body.close(); - footer.close(); - }); - - server.onNotFound([](AsyncWebServerRequest* request) { - request->send(404, "text/plain", "Not found"); - }); - - server.begin(); -} - -uint32_t last = 0; - -void loop() { - // dnsServer.processNextRequest(); - - if (millis() - last > 2000) { -#if defined(TARGET_RP2040) - Serial.printf("FreeHeap: %d", rp2040.getFreeHeap()); -#else - Serial.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap()); -#endif - last = millis(); - } -} \ No newline at end of file diff --git a/lib/ESPAsyncWebServer/examples/StreamFiles/StreamString.h b/lib/ESPAsyncWebServer/examples/StreamFiles/StreamString.h deleted file mode 100644 index a6e0655..0000000 --- a/lib/ESPAsyncWebServer/examples/StreamFiles/StreamString.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include - -class StreamString : public Stream { - public: - size_t write(const uint8_t* p, size_t n) override { return _buffer.concat(reinterpret_cast(p), n) ? n : 0; } - size_t write(uint8_t c) override { return _buffer.concat(static_cast(c)) ? 1 : 0; } - void flush() override {} - - int available() override { return static_cast(_buffer.length()); } - - int read() override { - if (_buffer.length() == 0) - return -1; - char c = _buffer[0]; - _buffer.remove(0, 1); - return c; - } - -#if defined(TARGET_RP2040) - size_t readBytes(char* buffer, size_t length) { -#else - size_t readBytes(char* buffer, size_t length) override { -#endif - if (length > _buffer.length()) - length = _buffer.length(); - // Don't use _str.ToCharArray() because it inserts a terminator - memcpy(buffer, _buffer.c_str(), length); - _buffer.remove(0, static_cast(length)); - return length; - } - - int peek() override { return _buffer.length() > 0 ? _buffer[0] : -1; } - - const String& buffer() const { return _buffer; } - - private: - String _buffer; -}; diff --git a/lib/ESPAsyncWebServer/examples/issues/Issue14/Issue14.ino b/lib/ESPAsyncWebServer/examples/issues/Issue14/Issue14.ino deleted file mode 100644 index f62084a..0000000 --- a/lib/ESPAsyncWebServer/examples/issues/Issue14/Issue14.ino +++ /dev/null @@ -1,107 +0,0 @@ -#include -#ifdef ESP32 - #include - #include -#elif defined(ESP8266) - #include - #include -#elif defined(TARGET_RP2040) - #include - #include -#endif - -#include "ESPAsyncWebServer.h" - -const char appWebPage[] PROGMEM = R"rawliteral( - - - - -)rawliteral"; - -AsyncWebServer server(80); -AsyncEventSource events("/events"); - -const uint32_t interval = 1000; -const int button1Pin = 4; - -uint32_t lastSend = 0; - -void prepareJson(String& buffer) { - buffer.reserve(512); - buffer.concat("{\"button1\":"); - buffer.concat(digitalRead(button1Pin) == LOW); - buffer.concat(",\"1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij1234567890abcdefghij\":"); - buffer.concat(random(0, 999999999)); - buffer.concat("}"); -} - -void setup() { - Serial.begin(115200); -#if ARDUINO_USB_CDC_ON_BOOT - Serial.setTxTimeoutMs(0); - delay(100); -#else - while (!Serial) - yield(); -#endif - - randomSeed(micros()); - - pinMode(button1Pin, OUTPUT); - digitalWrite(button1Pin, HIGH); - - WiFi.softAP("esp-captive"); - - server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { - request->send(200, "text/html", appWebPage); - }); - - server.on("/button1", HTTP_GET, [](AsyncWebServerRequest* request) { - request->send(200, "text/plain", "OK"); - digitalWrite(button1Pin, digitalRead(button1Pin) == LOW ? HIGH : LOW); - - String buffer; - prepareJson(buffer); - ESP_LOGI("async_tcp", "Sending from handler..."); - events.send(buffer.c_str(), "state", millis()); - ESP_LOGI("async_tcp", "Sent from handler!"); - }); - - events.onConnect([](AsyncEventSourceClient* client) { - String buffer; - prepareJson(buffer); - ESP_LOGI("async_tcp", "Sending from onConnect..."); - client->send(buffer.c_str(), "state", millis(), 5000); - ESP_LOGI("async_tcp", "Sent from onConnect!"); - }); - - server.addHandler(&events); - DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); - - server.begin(); -} - -void loop() { - if (millis() - lastSend >= interval) { - String buffer; - prepareJson(buffer); - ESP_LOGI("loop", "Sending..."); - events.send(buffer.c_str(), "state", millis()); - ESP_LOGI("loop", "Sent!"); - lastSend = millis(); - } -} diff --git a/lib/ESPAsyncWebServer/library.json b/lib/ESPAsyncWebServer/library.json deleted file mode 100644 index eb946c6..0000000 --- a/lib/ESPAsyncWebServer/library.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "name": "ESPAsyncWebServer", - "version": "3.1.5", - "description": "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.", - "keywords": "http,async,websocket,webserver", - "homepage": "https://github.com/mathieucarbou/ESPAsyncWebServer", - "repository": { - "type": "git", - "url": "https://github.com/mathieucarbou/ESPAsyncWebServer.git" - }, - "authors": [ - { - "name": "Hristo Gochkov" - }, - { - "name": "Mathieu Carbou", - "maintainer": true - } - ], - "license": "LGPL-3.0", - "frameworks": "arduino", - "platforms": [ - "espressif32", - "espressif8266", - "raspberrypi" - ], - "dependencies": [ - { - "owner": "mathieucarbou", - "name": "AsyncTCP", - "version": "^3.2.4", - "platforms": "espressif32" - }, - { - "owner": "esphome", - "name": "ESPAsyncTCP-esphome", - "version": "^2.0.0", - "platforms": "espressif8266" - }, - { - "name": "Hash", - "platforms": "espressif8266" - }, - { - "owner": "khoih-prog", - "name": "AsyncTCP_RP2040W", - "version": "^1.2.0", - "platforms": "raspberrypi" - } - ], - "export": { - "include": [ - "examples", - "src", - "library.json", - "library.properties", - "LICENSE", - "README.md" - ] - }, - "build": { - "libCompatMode": "strict" - } -} \ No newline at end of file diff --git a/lib/ESPAsyncWebServer/library.properties b/lib/ESPAsyncWebServer/library.properties deleted file mode 100644 index 3036044..0000000 --- a/lib/ESPAsyncWebServer/library.properties +++ /dev/null @@ -1,10 +0,0 @@ -name=ESPAsyncWebServer -version=3.1.5 -author=Me-No-Dev -maintainer=Mathieu Carbou -sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040 -paragraph=Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc -category=Other -url=https://github.com/mathieucarbou/ESPAsyncWebServer -architectures=* -license=LGPL-3.0 diff --git a/lib/ESPAsyncWebServer/platformio.ini b/lib/ESPAsyncWebServer/platformio.ini deleted file mode 100644 index 6f68d0d..0000000 --- a/lib/ESPAsyncWebServer/platformio.ini +++ /dev/null @@ -1,80 +0,0 @@ -[env] -framework = arduino -build_flags = - -Wall -Wextra - -D CONFIG_ARDUHAL_LOG_COLORS - -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE - -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000 - -D CONFIG_ASYNC_TCP_PRIORITY=10 - -D CONFIG_ASYNC_TCP_QUEUE_SIZE=128 - -D CONFIG_ASYNC_TCP_RUNNING_CORE=1 - -D CONFIG_ASYNC_TCP_STACK_SIZE=4096 -upload_protocol = esptool -monitor_speed = 115200 -monitor_filters = esp32_exception_decoder, log2file - -[platformio] -lib_dir = . -; src_dir = examples/CaptivePortal -src_dir = examples/SimpleServer -; src_dir = examples/StreamFiles -; src_dir = examples/Filters -; src_dir = examples/Draft -; src_dir = examples/issues/Issue14 - -[env:arduino] -platform = espressif32 -board = esp32dev -lib_deps = - bblanchon/ArduinoJson @ 7.1.0 - mathieucarbou/AsyncTCP @ 3.2.4 - -[env:arduino-2] -platform = espressif32@6.8.1 -board = esp32dev -lib_deps = - bblanchon/ArduinoJson @ 7.1.0 - mathieucarbou/AsyncTCP @ 3.2.4 - -[env:arduino-3] -platform = espressif32 -platform_packages= - platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.4 - platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.4/esp32-arduino-libs-3.0.4.zip -board = esp32dev -lib_deps = - bblanchon/ArduinoJson @ 7.1.0 - mathieucarbou/AsyncTCP @ 3.2.4 - -[env:esp8266] -platform = espressif8266 -board = huzzah -lib_deps = - bblanchon/ArduinoJson @ 7.1.0 - esphome/ESPAsyncTCP-esphome @ 2.0.0 - -; PlatformIO support for Raspberry Pi Pico is not official -; https://github.com/platformio/platform-raspberrypi/pull/36 -; https://github.com/earlephilhower/arduino-pico/blob/master/docs/platformio.rst -; board settings: https://github.com/earlephilhower/arduino-pico/blob/master/tools/json/rpipico.json -[env:rpipicow] -upload_protocol = picotool -platform = https://github.com/maxgerhardt/platform-raspberrypi.git -board = rpipicow -lib_deps = - bblanchon/ArduinoJson @ 7.1.0 - khoih-prog/AsyncTCP_RP2040W @ 1.2.0 - -[env:pioarduino-esp32dev] -platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.04/platform-espressif32.zip -board = esp32dev -lib_deps = - bblanchon/ArduinoJson @ 7.1.0 - mathieucarbou/AsyncTCP @ 3.2.4 - -[env:pioarduino-c6] -platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.04/platform-espressif32.zip -board = esp32-c6-devkitc-1 -lib_deps = - bblanchon/ArduinoJson @ 7.1.0 - mathieucarbou/AsyncTCP @ 3.2.4 diff --git a/lib/ESPAsyncWebServer/src/AsyncEventSource.cpp b/lib/ESPAsyncWebServer/src/AsyncEventSource.cpp deleted file mode 100644 index 639fd56..0000000 --- a/lib/ESPAsyncWebServer/src/AsyncEventSource.cpp +++ /dev/null @@ -1,405 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "Arduino.h" -#if defined(ESP32) - #include -#endif -#include "AsyncEventSource.h" -#include "literals.h" - -using namespace asyncsrv; - -static String generateEventMessage(const char* message, const char* event, uint32_t id, uint32_t reconnect) { - String ev; - - if (reconnect) { - ev += T_retry_; - ev += reconnect; - ev += T_rn; - } - - if (id) { - ev += T_id__; - ev += id; - ev += T_rn; - } - - if (event != NULL) { - ev += T_event_; - ev += event; - ev += T_rn; - } - - if (message != NULL) { - size_t messageLen = strlen(message); - char* lineStart = (char*)message; - char* lineEnd; - do { - char* nextN = strchr(lineStart, '\n'); - char* nextR = strchr(lineStart, '\r'); - if (nextN == NULL && nextR == NULL) { - size_t llen = ((char*)message + messageLen) - lineStart; - char* ldata = (char*)malloc(llen + 1); - if (ldata != NULL) { - memcpy(ldata, lineStart, llen); - ldata[llen] = 0; - ev += T_data_; - ev += ldata; - ev += T_rnrn; - free(ldata); - } - lineStart = (char*)message + messageLen; - } else { - char* nextLine = NULL; - if (nextN != NULL && nextR != NULL) { - if (nextR < nextN) { - lineEnd = nextR; - if (nextN == (nextR + 1)) - nextLine = nextN + 1; - else - nextLine = nextR + 1; - } else { - lineEnd = nextN; - if (nextR == (nextN + 1)) - nextLine = nextR + 1; - else - nextLine = nextN + 1; - } - } else if (nextN != NULL) { - lineEnd = nextN; - nextLine = nextN + 1; - } else { - lineEnd = nextR; - nextLine = nextR + 1; - } - - size_t llen = lineEnd - lineStart; - char* ldata = (char*)malloc(llen + 1); - if (ldata != NULL) { - memcpy(ldata, lineStart, llen); - ldata[llen] = 0; - ev += T_data_; - ev += ldata; - ev += T_rn; - free(ldata); - } - lineStart = nextLine; - if (lineStart == ((char*)message + messageLen)) - ev += T_rn; - } - } while (lineStart < ((char*)message + messageLen)); - } - - return ev; -} - -// Message - -AsyncEventSourceMessage::AsyncEventSourceMessage(const char* data, size_t len) - : _data(nullptr), _len(len), _sent(0), _acked(0) { - _data = (uint8_t*)malloc(_len + 1); - if (_data == nullptr) { - _len = 0; - } else { - memcpy(_data, data, len); - _data[_len] = 0; - } -} - -AsyncEventSourceMessage::~AsyncEventSourceMessage() { - if (_data != NULL) - free(_data); -} - -size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) { - (void)time; - // If the whole message is now acked... - if (_acked + len > _len) { - // Return the number of extra bytes acked (they will be carried on to the next message) - const size_t extra = _acked + len - _len; - _acked = _len; - return extra; - } - // Return that no extra bytes left. - _acked += len; - return 0; -} - -// This could also return void as the return value is not used. -// Leaving as-is for compatibility... -size_t AsyncEventSourceMessage::send(AsyncClient* client) { - if (_sent >= _len) { - return 0; - } - const size_t len_to_send = _len - _sent; - auto position = reinterpret_cast(_data + _sent); - const size_t sent_now = client->write(position, len_to_send); - _sent += sent_now; - return sent_now; -} - -// Client - -AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server) { - _client = request->client(); - _server = server; - _lastId = 0; - if (request->hasHeader(T_Last_Event_ID)) - _lastId = atoi(request->getHeader(T_Last_Event_ID)->value().c_str()); - - _client->setRxTimeout(0); - _client->onError(NULL, NULL); - _client->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this); - _client->onPoll([](void* r, AsyncClient* c) { (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this); - _client->onData(NULL, NULL); - _client->onTimeout([this](void* r, AsyncClient* c __attribute__((unused)), uint32_t time) { ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this); - _client->onDisconnect([this](void* r, AsyncClient* c) { ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this); - - _server->_addClient(this); - delete request; -} - -AsyncEventSourceClient::~AsyncEventSourceClient() { -#ifdef ESP32 - std::lock_guard lock(_lockmq); -#endif - _messageQueue.clear(); - close(); -} - -void AsyncEventSourceClient::_queueMessage(const char* message, size_t len) { -#ifdef ESP32 - // length() is not thread-safe, thus acquiring the lock before this call.. - std::lock_guard lock(_lockmq); -#endif - - if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) { -#ifdef ESP8266 - ets_printf(String(F("ERROR: Too many messages queued\n")).c_str()); -#elif defined(ESP32) - log_e("Too many messages queued: deleting message"); -#endif - return; - } - - _messageQueue.emplace_back(message, len); - // runqueue trigger when new messages added - if (_client->canSend()) { - _runQueue(); - } -} - -void AsyncEventSourceClient::_onAck(size_t len, uint32_t time) { -#ifdef ESP32 - // Same here, acquiring the lock early - std::lock_guard lock(_lockmq); -#endif - while (len && _messageQueue.size()) { - len = _messageQueue.front().ack(len, time); - if (_messageQueue.front().finished()) - _messageQueue.pop_front(); - } - _runQueue(); -} - -void AsyncEventSourceClient::_onPoll() { -#ifdef ESP32 - // Same here, acquiring the lock early - std::lock_guard lock(_lockmq); -#endif - if (_messageQueue.size()) { - _runQueue(); - } -} - -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) { - if (!connected()) - return; - _queueMessage(message, len); -} - -void AsyncEventSourceClient::send(const char* message, const char* event, uint32_t id, uint32_t reconnect) { - if (!connected()) - return; - String ev = generateEventMessage(message, event, id, reconnect); - _queueMessage(ev.c_str(), ev.length()); -} - -size_t AsyncEventSourceClient::packetsWaiting() const { -#ifdef ESP32 - std::lock_guard lock(_lockmq); -#endif - return _messageQueue.size(); -} - -void AsyncEventSourceClient::_runQueue() { - // Calls to this private method now already protected by _lockmq acquisition - // so no extra call of _lockmq.lock() here.. - for (auto& i : _messageQueue) { - if (!i.sent()) - i.send(_client); - } -} - -// Handler -void AsyncEventSource::onConnect(ArEventHandlerFunction cb) { - _connectcb = cb; -} - -void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) { - _authorizeConnectHandler = cb; -} - -void AsyncEventSource::_addClient(AsyncEventSourceClient* client) { - if (!client) - return; -#ifdef ESP32 - std::lock_guard lock(_client_queue_lock); -#endif - _clients.emplace_back(client); - if (_connectcb) - _connectcb(client); -} - -void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient* client) { -#ifdef ESP32 - std::lock_guard lock(_client_queue_lock); -#endif - for (auto i = _clients.begin(); i != _clients.end(); ++i) { - if (i->get() == client) - _clients.erase(i); - } -} - -void AsyncEventSource::close() { - // While the whole loop is not done, the linked list is locked and so the - // iterator should remain valid even when AsyncEventSource::_handleDisconnect() - // is called very early -#ifdef ESP32 - std::lock_guard lock(_client_queue_lock); -#endif - for (const auto& c : _clients) { - if (c->connected()) - c->close(); - } -} - -// pmb fix -size_t AsyncEventSource::avgPacketsWaiting() const { - size_t aql = 0; - uint32_t nConnectedClients = 0; -#ifdef ESP32 - std::lock_guard lock(_client_queue_lock); -#endif - if (!_clients.size()) - return 0; - - for (const auto& c : _clients) { - if (c->connected()) { - aql += c->packetsWaiting(); - ++nConnectedClients; - } - } - return ((aql) + (nConnectedClients / 2)) / (nConnectedClients); // round up -} - -void AsyncEventSource::send( - const char* message, const char* event, uint32_t id, uint32_t reconnect) { - String ev = generateEventMessage(message, event, id, reconnect); -#ifdef ESP32 - std::lock_guard lock(_client_queue_lock); -#endif - for (const auto& c : _clients) { - if (c->connected()) { - c->write(ev.c_str(), ev.length()); - } - } -} - -size_t AsyncEventSource::count() const { -#ifdef ESP32 - std::lock_guard lock(_client_queue_lock); -#endif - size_t n_clients{0}; - for (const auto& i : _clients) - if (i->connected()) - ++n_clients; - - return n_clients; -} - -bool AsyncEventSource::canHandle(AsyncWebServerRequest* request) { - if (request->method() != HTTP_GET || !request->url().equals(_url)) { - return false; - } - request->addInterestingHeader(T_Last_Event_ID); - request->addInterestingHeader(T_Cookie); - return true; -} - -void AsyncEventSource::handleRequest(AsyncWebServerRequest* request) { - if ((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) { - return request->requestAuthentication(); - } - if (_authorizeConnectHandler != NULL) { - if (!_authorizeConnectHandler(request)) { - return request->send(401); - } - } - request->send(new AsyncEventSourceResponse(this)); -} - -// Response - -AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource* server) { - _server = server; - _code = 200; - _contentType = T_text_event_stream; - _sendContentLength = false; - addHeader(T_Cache_Control, T_no_cache); - addHeader(T_Connection, T_keep_alive); -} - -void AsyncEventSourceResponse::_respond(AsyncWebServerRequest* request) { - String out = _assembleHead(request->version()); - request->client()->write(out.c_str(), _headLength); - _state = RESPONSE_WAIT_ACK; -} - -size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time __attribute__((unused))) { - if (len) { - new AsyncEventSourceClient(request, _server); - } - return 0; -} diff --git a/lib/ESPAsyncWebServer/src/AsyncEventSource.h b/lib/ESPAsyncWebServer/src/AsyncEventSource.h deleted file mode 100644 index 0289ebf..0000000 --- a/lib/ESPAsyncWebServer/src/AsyncEventSource.h +++ /dev/null @@ -1,158 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#ifndef ASYNCEVENTSOURCE_H_ -#define ASYNCEVENTSOURCE_H_ - -#include -#include -#ifdef ESP32 - #include - #include - #ifndef SSE_MAX_QUEUED_MESSAGES - #define SSE_MAX_QUEUED_MESSAGES 32 - #endif -#elif defined(ESP8266) - #include - #ifndef SSE_MAX_QUEUED_MESSAGES - #define SSE_MAX_QUEUED_MESSAGES 8 - #endif -#elif defined(TARGET_RP2040) - #include - #ifndef SSE_MAX_QUEUED_MESSAGES - #define SSE_MAX_QUEUED_MESSAGES 32 - #endif -#endif - -#include - -#ifdef ESP8266 - #include - #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library - #include <../src/Hash.h> - #endif -#endif - -#ifndef DEFAULT_MAX_SSE_CLIENTS - #ifdef ESP32 - #define DEFAULT_MAX_SSE_CLIENTS 8 - #else - #define DEFAULT_MAX_SSE_CLIENTS 4 - #endif -#endif - -class AsyncEventSource; -class AsyncEventSourceResponse; -class AsyncEventSourceClient; -using ArEventHandlerFunction = std::function; -using ArAuthorizeConnectHandler = std::function; - -class AsyncEventSourceMessage { - private: - uint8_t* _data; - size_t _len; - size_t _sent; - // size_t _ack; - size_t _acked; - - public: - AsyncEventSourceMessage(const char* data, size_t len); - ~AsyncEventSourceMessage(); - size_t ack(size_t len, uint32_t time __attribute__((unused))); - size_t send(AsyncClient* client); - bool finished() { return _acked == _len; } - bool sent() { return _sent == _len; } -}; - -class AsyncEventSourceClient { - private: - AsyncClient* _client; - AsyncEventSource* _server; - uint32_t _lastId; - std::list _messageQueue; -#ifdef ESP32 - mutable std::mutex _lockmq; -#endif - void _queueMessage(const char* message, size_t len); - void _runQueue(); - - public: - AsyncEventSourceClient(AsyncWebServerRequest* request, AsyncEventSource* server); - ~AsyncEventSourceClient(); - - 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 { return (_client != NULL) && _client->connected(); } - uint32_t lastId() const { return _lastId; } - size_t packetsWaiting() const; - - // system callbacks (do not call) - void _onAck(size_t len, uint32_t time); - void _onPoll(); - void _onTimeout(uint32_t time); - void _onDisconnect(); -}; - -class AsyncEventSource : public AsyncWebHandler { - private: - String _url; - std::list> _clients; -#ifdef ESP32 - // Same as for individual messages, protect mutations of _clients list - // since simultaneous access from different tasks is possible - mutable std::mutex _client_queue_lock; -#endif - ArEventHandlerFunction _connectcb{nullptr}; - ArAuthorizeConnectHandler _authorizeConnectHandler; - - public: - AsyncEventSource(const String& url) : _url(url){}; - ~AsyncEventSource() { close(); }; - - const char* url() const { return _url.c_str(); } - void close(); - void onConnect(ArEventHandlerFunction cb); - void authorizeConnect(ArAuthorizeConnectHandler cb); - void send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0); - // number of clients connected - size_t count() const; - size_t avgPacketsWaiting() const; - - // system callbacks (do not call) - void _addClient(AsyncEventSourceClient* client); - void _handleDisconnect(AsyncEventSourceClient* client); - virtual bool canHandle(AsyncWebServerRequest* request) override final; - virtual void handleRequest(AsyncWebServerRequest* request) override final; -}; - -class AsyncEventSourceResponse : public AsyncWebServerResponse { - private: - String _content; - AsyncEventSource* _server; - - public: - AsyncEventSourceResponse(AsyncEventSource* server); - void _respond(AsyncWebServerRequest* request); - size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time); - bool _sourceValid() const { return true; } -}; - -#endif /* ASYNCEVENTSOURCE_H_ */ diff --git a/lib/ESPAsyncWebServer/src/AsyncJson.h b/lib/ESPAsyncWebServer/src/AsyncJson.h deleted file mode 100644 index bca3f24..0000000 --- a/lib/ESPAsyncWebServer/src/AsyncJson.h +++ /dev/null @@ -1,255 +0,0 @@ -// AsyncJson.h -/* - Async Response to use with ArduinoJson and AsyncWebServer - Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon. - - Example of callback in use - - server.on("/json", HTTP_ANY, [](AsyncWebServerRequest * request) { - - AsyncJsonResponse * response = new AsyncJsonResponse(); - JsonObject& root = response->getRoot(); - root["key1"] = "key number one"; - JsonObject& nested = root.createNestedObject("nested"); - nested["key1"] = "key number one"; - - response->setLength(); - request->send(response); - }); - - -------------------- - - Async Request to use with ArduinoJson and AsyncWebServer - Written by Arsène von Wyss (avonwyss) - - Example - - AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint"); - handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) { - JsonObject jsonObj = json.as(); - // ... - }); - server.addHandler(handler); - -*/ -#ifndef ASYNC_JSON_H_ -#define ASYNC_JSON_H_ -#include -#include - -#include "ChunkPrint.h" - -#if ARDUINOJSON_VERSION_MAJOR == 6 - #ifndef DYNAMIC_JSON_DOCUMENT_SIZE - #define DYNAMIC_JSON_DOCUMENT_SIZE 1024 - #endif -#endif - -constexpr const char* JSON_MIMETYPE = "application/json"; - -/* - * Json Response - * */ - -class AsyncJsonResponse : public AsyncAbstractResponse { - protected: -#if ARDUINOJSON_VERSION_MAJOR == 5 - DynamicJsonBuffer _jsonBuffer; -#elif ARDUINOJSON_VERSION_MAJOR == 6 - DynamicJsonDocument _jsonBuffer; -#else - JsonDocument _jsonBuffer; -#endif - - JsonVariant _root; - bool _isValid; - - public: -#if ARDUINOJSON_VERSION_MAJOR == 5 - AsyncJsonResponse(bool isArray = false) : _isValid{false} { - _code = 200; - _contentType = JSON_MIMETYPE; - if (isArray) - _root = _jsonBuffer.createArray(); - else - _root = _jsonBuffer.createObject(); - } -#elif ARDUINOJSON_VERSION_MAJOR == 6 - AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : _jsonBuffer(maxJsonBufferSize), _isValid{false} { - _code = 200; - _contentType = JSON_MIMETYPE; - if (isArray) - _root = _jsonBuffer.createNestedArray(); - else - _root = _jsonBuffer.createNestedObject(); - } -#else - AsyncJsonResponse(bool isArray = false) : _isValid{false} { - _code = 200; - _contentType = JSON_MIMETYPE; - if (isArray) - _root = _jsonBuffer.add(); - else - _root = _jsonBuffer.add(); - } -#endif - - JsonVariant& getRoot() { return _root; } - bool _sourceValid() const { return _isValid; } - size_t setLength() { - -#if ARDUINOJSON_VERSION_MAJOR == 5 - _contentLength = _root.measureLength(); -#else - _contentLength = measureJson(_root); -#endif - - if (_contentLength) { - _isValid = true; - } - return _contentLength; - } - - size_t getSize() const { return _jsonBuffer.size(); } - -#if ARDUINOJSON_VERSION_MAJOR >= 6 - bool overflowed() const { return _jsonBuffer.overflowed(); } -#endif - - size_t _fillBuffer(uint8_t* data, size_t len) { - ChunkPrint dest(data, _sentLength, len); - -#if ARDUINOJSON_VERSION_MAJOR == 5 - _root.printTo(dest); -#else - serializeJson(_root, dest); -#endif - return len; - } -}; - -class PrettyAsyncJsonResponse : public AsyncJsonResponse { - public: -#if ARDUINOJSON_VERSION_MAJOR == 6 - PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse{isArray, maxJsonBufferSize} {} -#else - PrettyAsyncJsonResponse(bool isArray = false) : AsyncJsonResponse{isArray} {} -#endif - size_t setLength() { -#if ARDUINOJSON_VERSION_MAJOR == 5 - _contentLength = _root.measurePrettyLength(); -#else - _contentLength = measureJsonPretty(_root); -#endif - if (_contentLength) { - _isValid = true; - } - return _contentLength; - } - size_t _fillBuffer(uint8_t* data, size_t len) { - ChunkPrint dest(data, _sentLength, len); -#if ARDUINOJSON_VERSION_MAJOR == 5 - _root.prettyPrintTo(dest); -#else - serializeJsonPretty(_root, dest); -#endif - return len; - } -}; - -typedef std::function ArJsonRequestHandlerFunction; - -class AsyncCallbackJsonWebHandler : public AsyncWebHandler { - private: - protected: - const String _uri; - WebRequestMethodComposite _method; - ArJsonRequestHandlerFunction _onRequest; - size_t _contentLength; -#if ARDUINOJSON_VERSION_MAJOR == 6 - const size_t maxJsonBufferSize; -#endif - size_t _maxContentLength; - - public: -#if ARDUINOJSON_VERSION_MAJOR == 6 - AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest = nullptr, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) - : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {} -#else - AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest = nullptr) - : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {} -#endif - - void setMethod(WebRequestMethodComposite method) { _method = method; } - void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; } - void onRequest(ArJsonRequestHandlerFunction fn) { _onRequest = fn; } - - virtual bool canHandle(AsyncWebServerRequest* request) override final { - if (!_onRequest) - return false; - - WebRequestMethodComposite request_method = request->method(); - if (!(_method & request_method)) - return false; - - if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) - return false; - - if (request_method != HTTP_GET && !request->contentType().equalsIgnoreCase(JSON_MIMETYPE)) - return false; - - request->addInterestingHeader("ANY"); - return true; - } - - virtual void handleRequest(AsyncWebServerRequest* request) override final { - if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) - return request->requestAuthentication(); - if (_onRequest) { - if (request->method() == HTTP_GET) { - JsonVariant json; - _onRequest(request, json); - return; - } else if (request->_tempObject != NULL) { - -#if ARDUINOJSON_VERSION_MAJOR == 5 - DynamicJsonBuffer jsonBuffer; - JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject)); - if (json.success()) { -#elif ARDUINOJSON_VERSION_MAJOR == 6 - DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); - DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); - if (!error) { - JsonVariant json = jsonBuffer.as(); -#else - JsonDocument jsonBuffer; - DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); - if (!error) { - JsonVariant json = jsonBuffer.as(); -#endif - - _onRequest(request, json); - return; - } - } - request->send(_contentLength > _maxContentLength ? 413 : 400); - } else { - request->send(500); - } - } - virtual void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) override final { - } - virtual void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final { - if (_onRequest) { - _contentLength = total; - if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { - request->_tempObject = malloc(total); - } - if (request->_tempObject != NULL) { - memcpy((uint8_t*)(request->_tempObject) + index, data, len); - } - } - } - virtual bool isRequestHandlerTrivial() override final { return _onRequest ? false : true; } -}; -#endif diff --git a/lib/ESPAsyncWebServer/src/AsyncMessagePack.h b/lib/ESPAsyncWebServer/src/AsyncMessagePack.h deleted file mode 100644 index 57a8824..0000000 --- a/lib/ESPAsyncWebServer/src/AsyncMessagePack.h +++ /dev/null @@ -1,145 +0,0 @@ -#pragma once - -/* - server.on("/msg_pack", HTTP_ANY, [](AsyncWebServerRequest * request) { - AsyncMessagePackResponse * response = new AsyncMessagePackResponse(); - JsonObject& root = response->getRoot(); - root["key1"] = "key number one"; - JsonObject& nested = root.createNestedObject("nested"); - nested["key1"] = "key number one"; - response->setLength(); - request->send(response); - }); - - -------------------- - - AsyncCallbackMessagePackWebHandler* handler = new AsyncCallbackMessagePackWebHandler("/msg_pack/endpoint"); - handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) { - JsonObject jsonObj = json.as(); - // ... - }); - server.addHandler(handler); -*/ - -#include -#include - -#include "ChunkPrint.h" -#include "literals.h" - -class AsyncMessagePackResponse : public AsyncAbstractResponse { - protected: - JsonDocument _jsonBuffer; - JsonVariant _root; - bool _isValid; - - public: - AsyncMessagePackResponse(bool isArray = false) : _isValid{false} { - _code = 200; - _contentType = asyncsrv::T_application_msgpack; - if (isArray) - _root = _jsonBuffer.add(); - else - _root = _jsonBuffer.add(); - } - - JsonVariant& getRoot() { return _root; } - - bool _sourceValid() const { return _isValid; } - - size_t setLength() { - _contentLength = measureMsgPack(_root); - if (_contentLength) { - _isValid = true; - } - return _contentLength; - } - - size_t getSize() const { return _jsonBuffer.size(); } - - size_t _fillBuffer(uint8_t* data, size_t len) { - ChunkPrint dest(data, _sentLength, len); - serializeMsgPack(_root, dest); - return len; - } -}; - -class AsyncCallbackMessagePackWebHandler : public AsyncWebHandler { - public: - typedef std::function ArJsonRequestHandlerFunction; - - protected: - const String _uri; - WebRequestMethodComposite _method; - ArJsonRequestHandlerFunction _onRequest; - size_t _contentLength; - size_t _maxContentLength; - - public: - AsyncCallbackMessagePackWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest = nullptr) - : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {} - - void setMethod(WebRequestMethodComposite method) { _method = method; } - void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; } - void onRequest(ArJsonRequestHandlerFunction fn) { _onRequest = fn; } - - virtual bool canHandle(AsyncWebServerRequest* request) override final { - if (!_onRequest) - return false; - - WebRequestMethodComposite request_method = request->method(); - if (!(_method & request_method)) - return false; - - if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) - return false; - - if (request_method != HTTP_GET && !request->contentType().equalsIgnoreCase(asyncsrv::T_application_msgpack)) - return false; - - request->addInterestingHeader("ANY"); - return true; - } - - virtual void handleRequest(AsyncWebServerRequest* request) override final { - if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) - return request->requestAuthentication(); - - if (_onRequest) { - if (request->method() == HTTP_GET) { - JsonVariant json; - _onRequest(request, json); - return; - - } else if (request->_tempObject != NULL) { - JsonDocument jsonBuffer; - DeserializationError error = deserializeMsgPack(jsonBuffer, (uint8_t*)(request->_tempObject)); - - if (!error) { - JsonVariant json = jsonBuffer.as(); - _onRequest(request, json); - return; - } - } - request->send(_contentLength > _maxContentLength ? 413 : 400); - } else { - request->send(500); - } - } - - virtual void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) override final {} - - virtual void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final { - if (_onRequest) { - _contentLength = total; - if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { - request->_tempObject = malloc(total); - } - if (request->_tempObject != NULL) { - memcpy((uint8_t*)(request->_tempObject) + index, data, len); - } - } - } - - virtual bool isRequestHandlerTrivial() override final { return _onRequest ? false : true; } -}; diff --git a/lib/ESPAsyncWebServer/src/AsyncWebSocket.cpp b/lib/ESPAsyncWebServer/src/AsyncWebSocket.cpp deleted file mode 100644 index 88d88ec..0000000 --- a/lib/ESPAsyncWebServer/src/AsyncWebSocket.cpp +++ /dev/null @@ -1,1207 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "AsyncWebSocket.h" -#include "Arduino.h" - -#include - -#include - -#if defined(ESP32) - #if ESP_IDF_VERSION_MAJOR < 5 - #include "./port/SHA1Builder.h" - #else - #include - #endif - #include -#elif defined(TARGET_RP2040) || defined(ESP8266) - #include -#endif - -#define MAX_PRINTF_LEN 64 - -using namespace asyncsrv; - - -size_t webSocketSendFrameWindow(AsyncClient* client) { - if (!client->canSend()) - return 0; - size_t space = client->space(); - if (space < 9) - return 0; - return space - 8; -} - -size_t webSocketSendFrame(AsyncClient* client, bool final, uint8_t opcode, bool mask, uint8_t* data, size_t len) { - if (!client->canSend()) { - // Serial.println("SF 1"); - return 0; - } - size_t space = client->space(); - if (space < 2) { - // Serial.println("SF 2"); - return 0; - } - uint8_t mbuf[4] = {0, 0, 0, 0}; - uint8_t headLen = 2; - if (len && mask) { - headLen += 4; - mbuf[0] = rand() % 0xFF; - mbuf[1] = rand() % 0xFF; - mbuf[2] = rand() % 0xFF; - mbuf[3] = rand() % 0xFF; - } - if (len > 125) - headLen += 2; - if (space < headLen) { - // Serial.println("SF 2"); - return 0; - } - space -= headLen; - - if (len > space) - len = space; - - uint8_t* buf = (uint8_t*)malloc(headLen); - if (buf == NULL) { - // os_printf("could not malloc %u bytes for frame header\n", headLen); - // Serial.println("SF 3"); - return 0; - } - - buf[0] = opcode & 0x0F; - if (final) - buf[0] |= 0x80; - if (len < 126) - buf[1] = len & 0x7F; - else { - buf[1] = 126; - buf[2] = (uint8_t)((len >> 8) & 0xFF); - buf[3] = (uint8_t)(len & 0xFF); - } - if (len && mask) { - buf[1] |= 0x80; - memcpy(buf + (headLen - 4), mbuf, 4); - } - if (client->add((const char*)buf, headLen) != headLen) { - // os_printf("error adding %lu header bytes\n", headLen); - free(buf); - // Serial.println("SF 4"); - return 0; - } - free(buf); - - if (len) { - if (len && mask) { - size_t i; - for (i = 0; i < len; i++) - data[i] = data[i] ^ mbuf[i % 4]; - } - if (client->add((const char*)data, len) != len) { - // os_printf("error adding %lu data bytes\n", len); - // Serial.println("SF 5"); - return 0; - } - } - if (!client->send()) { - // os_printf("error sending frame: %lu\n", headLen+len); - // Serial.println("SF 6"); - return 0; - } - // Serial.println("SF"); - return len; -} - -/* - * AsyncWebSocketMessageBuffer - */ - -AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size) - : _buffer(std::make_shared>(size)) { - if (_buffer->capacity() < size) { - _buffer->reserve(size); - } else { - std::memcpy(_buffer->data(), data, size); - } -} - -AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size) - : _buffer(std::make_shared>(size)) { - if (_buffer->capacity() < size) { - _buffer->reserve(size); - } -} - -bool AsyncWebSocketMessageBuffer::reserve(size_t size) { - if (_buffer->capacity() >= size) - return true; - _buffer->reserve(size); - return _buffer->capacity() >= size; -} - -/* - * Control Frame - */ - -class AsyncWebSocketControl { - private: - uint8_t _opcode; - uint8_t* _data; - size_t _len; - bool _mask; - bool _finished; - - public: - AsyncWebSocketControl(uint8_t opcode, const uint8_t* data = NULL, size_t len = 0, bool mask = false) - : _opcode(opcode), _len(len), _mask(len && mask), _finished(false) { - if (data == NULL) - _len = 0; - if (_len) { - if (_len > 125) - _len = 125; - - _data = (uint8_t*)malloc(_len); - - if (_data == NULL) - _len = 0; - else - memcpy(_data, data, len); - } else - _data = NULL; - } - - virtual ~AsyncWebSocketControl() { - if (_data != NULL) - free(_data); - } - - virtual bool finished() const { return _finished; } - uint8_t opcode() { return _opcode; } - uint8_t len() { return _len + 2; } - size_t send(AsyncClient* client) { - _finished = true; - return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len); - } -}; - -/* - * AsyncWebSocketMessage Message - */ - -AsyncWebSocketMessage::AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask) : _WSbuffer{buffer}, - _opcode(opcode & 0x07), - _mask{mask}, - _status{_WSbuffer ? WS_MSG_SENDING : WS_MSG_ERROR} { -} - -void AsyncWebSocketMessage::ack(size_t len, uint32_t time) { - (void)time; - _acked += len; - if (_sent >= _WSbuffer->size() && _acked >= _ack) { - _status = WS_MSG_SENT; - } - // ets_printf("A: %u\n", len); -} - -size_t AsyncWebSocketMessage::send(AsyncClient* client) { - if (_status != WS_MSG_SENDING) - return 0; - if (_acked < _ack) { - return 0; - } - if (_sent == _WSbuffer->size()) { - if (_acked == _ack) - _status = WS_MSG_SENT; - return 0; - } - if (_sent > _WSbuffer->size()) { - _status = WS_MSG_ERROR; - // ets_printf("E: %u > %u\n", _sent, _WSbuffer->length()); - return 0; - } - - size_t toSend = _WSbuffer->size() - _sent; - size_t window = webSocketSendFrameWindow(client); - - if (window < toSend) { - toSend = window; - } - - _sent += toSend; - _ack += toSend + ((toSend < 126) ? 2 : 4) + (_mask * 4); - - // ets_printf("W: %u %u\n", _sent - toSend, toSend); - - bool final = (_sent == _WSbuffer->size()); - uint8_t* dPtr = (uint8_t*)(_WSbuffer->data() + (_sent - toSend)); - uint8_t opCode = (toSend && _sent == toSend) ? _opcode : (uint8_t)WS_CONTINUATION; - - size_t sent = webSocketSendFrame(client, final, opCode, _mask, dPtr, toSend); - _status = WS_MSG_SENDING; - if (toSend && sent != toSend) { - // ets_printf("E: %u != %u\n", toSend, sent); - _sent -= (toSend - sent); - _ack -= (toSend - sent); - } - // ets_printf("S: %u %u\n", _sent, sent); - return sent; -} - -/* - * Async WebSocket Client - */ -const char* AWSC_PING_PAYLOAD = "ESPAsyncWebServer-PING"; -const size_t AWSC_PING_PAYLOAD_LEN = 22; - -AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest* request, AsyncWebSocket* server) - : _tempObject(NULL) { - _client = request->client(); - _server = server; - _clientId = _server->_getNextId(); - _status = WS_CONNECTED; - _pstate = 0; - _lastMessageTime = millis(); - _keepAlivePeriod = 0; - _client->setRxTimeout(0); - _client->onError([](void* r, AsyncClient* c, int8_t error) { (void)c; ((AsyncWebSocketClient*)(r))->_onError(error); }, this); - _client->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; ((AsyncWebSocketClient*)(r))->_onAck(len, time); }, this); - _client->onDisconnect([](void* r, AsyncClient* c) { ((AsyncWebSocketClient*)(r))->_onDisconnect(); delete c; }, this); - _client->onTimeout([](void* r, AsyncClient* c, uint32_t time) { (void)c; ((AsyncWebSocketClient*)(r))->_onTimeout(time); }, this); - _client->onData([](void* r, AsyncClient* c, void* buf, size_t len) { (void)c; ((AsyncWebSocketClient*)(r))->_onData(buf, len); }, this); - _client->onPoll([](void* r, AsyncClient* c) { (void)c; ((AsyncWebSocketClient*)(r))->_onPoll(); }, this); - _server->_handleEvent(this, WS_EVT_CONNECT, request, NULL, 0); - delete request; - memset(&_pinfo, 0, sizeof(_pinfo)); -} - -AsyncWebSocketClient::~AsyncWebSocketClient() { - { -#ifdef ESP32 - std::lock_guard lock(_lock); -#endif - _messageQueue.clear(); - _controlQueue.clear(); - } - _server->_handleEvent(this, WS_EVT_DISCONNECT, NULL, NULL, 0); -} - -void AsyncWebSocketClient::_clearQueue() { - while (!_messageQueue.empty() && _messageQueue.front().finished()) - _messageQueue.pop_front(); -} - -void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) { - _lastMessageTime = millis(); - -#ifdef ESP32 - std::lock_guard lock(_lock); -#endif - - if (!_controlQueue.empty()) { - auto& head = _controlQueue.front(); - if (head.finished()) { - len -= head.len(); - if (_status == WS_DISCONNECTING && head.opcode() == WS_DISCONNECT) { - _controlQueue.pop_front(); - _status = WS_DISCONNECTED; - if (_client) - _client->close(true); - return; - } - _controlQueue.pop_front(); - } - } - - if (len && !_messageQueue.empty()) { - _messageQueue.front().ack(len, time); - } - - _clearQueue(); - - _runQueue(); -} - -void AsyncWebSocketClient::_onPoll() { - if (!_client) - return; - -#ifdef ESP32 - std::unique_lock lock(_lock); -#endif - if (_client->canSend() && (!_controlQueue.empty() || !_messageQueue.empty())) { - _runQueue(); - } else if (_keepAlivePeriod > 0 && (millis() - _lastMessageTime) >= _keepAlivePeriod && (_controlQueue.empty() && _messageQueue.empty())) { -#ifdef ESP32 - lock.unlock(); -#endif - ping((uint8_t*)AWSC_PING_PAYLOAD, AWSC_PING_PAYLOAD_LEN); - } -} - -void AsyncWebSocketClient::_runQueue() { - // all calls to this method MUST be protected by a mutex lock! - if (!_client) - return; - - _clearQueue(); - - if (!_controlQueue.empty() && (_messageQueue.empty() || _messageQueue.front().betweenFrames()) && webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front().len() - 1)) { - _controlQueue.front().send(_client); - } else if (!_messageQueue.empty() && _messageQueue.front().betweenFrames() && webSocketSendFrameWindow(_client)) { - _messageQueue.front().send(_client); - } -} - -bool AsyncWebSocketClient::queueIsFull() const { -#ifdef ESP32 - std::lock_guard lock(_lock); -#endif - size_t size = _messageQueue.size(); - ; - return (size >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED); -} - -size_t AsyncWebSocketClient::queueLen() const { -#ifdef ESP32 - std::lock_guard lock(_lock); -#endif - - return _messageQueue.size() + _controlQueue.size(); -} - -bool AsyncWebSocketClient::canSend() const { -#ifdef ESP32 - std::lock_guard lock(_lock); -#endif - return _messageQueue.size() < WS_MAX_QUEUED_MESSAGES; -} - -void AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t* data, size_t len, bool mask) { - if (!_client) - return; - - { -#ifdef ESP32 - std::lock_guard lock(_lock); -#endif - _controlQueue.emplace_back(opcode, data, len, mask); - } - - if (_client && _client->canSend()) - _runQueue(); -} - -void AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode, bool mask) { - if (!_client || buffer->size() == 0 || _status != WS_CONNECTED) - return; - -#ifdef ESP32 - std::lock_guard lock(_lock); -#endif - if (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) { - if (closeWhenFull) { -#ifdef ESP8266 - ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: closing connection\n"); -#elif defined(ESP32) - log_e("Too many messages queued: closing connection"); -#endif - _status = WS_DISCONNECTED; - if (_client) - _client->close(true); - } else { -#ifdef ESP8266 - ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: discarding new message\n"); -#elif defined(ESP32) - log_e("Too many messages queued: discarding new message"); -#endif - } - return; - } else { - _messageQueue.emplace_back(buffer, opcode, mask); - } - - if (_client && _client->canSend()) - _runQueue(); -} - -void AsyncWebSocketClient::close(uint16_t code, const char* message) { - if (_status != WS_CONNECTED) - return; - - if (code) { - uint8_t packetLen = 2; - if (message != NULL) { - size_t mlen = strlen(message); - if (mlen > 123) - mlen = 123; - packetLen += mlen; - } - char* buf = (char*)malloc(packetLen); - if (buf != NULL) { - buf[0] = (uint8_t)(code >> 8); - buf[1] = (uint8_t)(code & 0xFF); - if (message != NULL) { - memcpy(buf + 2, message, packetLen - 2); - } - _queueControl(WS_DISCONNECT, (uint8_t*)buf, packetLen); - free(buf); - return; - } - } - _queueControl(WS_DISCONNECT); -} - -void AsyncWebSocketClient::ping(const uint8_t* data, size_t len) { - if (_status == WS_CONNECTED) - _queueControl(WS_PING, data, len); -} - -void AsyncWebSocketClient::_onError(int8_t) { - // Serial.println("onErr"); -} - -void AsyncWebSocketClient::_onTimeout(uint32_t time) { - // Serial.println("onTime"); - (void)time; - _client->close(true); -} - -void AsyncWebSocketClient::_onDisconnect() { - // Serial.println("onDis"); - _client = NULL; -} - -void AsyncWebSocketClient::_onData(void* pbuf, size_t plen) { - // Serial.println("onData"); - _lastMessageTime = millis(); - uint8_t* data = (uint8_t*)pbuf; - while (plen > 0) { - if (!_pstate) { - const uint8_t* fdata = data; - _pinfo.index = 0; - _pinfo.final = (fdata[0] & 0x80) != 0; - _pinfo.opcode = fdata[0] & 0x0F; - _pinfo.masked = (fdata[1] & 0x80) != 0; - _pinfo.len = fdata[1] & 0x7F; - data += 2; - plen -= 2; - if (_pinfo.len == 126) { - _pinfo.len = fdata[3] | (uint16_t)(fdata[2]) << 8; - data += 2; - plen -= 2; - } 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; - data += 8; - plen -= 8; - } - - if (_pinfo.masked) { - memcpy(_pinfo.mask, data, 4); - data += 4; - plen -= 4; - } - } - - const size_t datalen = std::min((size_t)(_pinfo.len - _pinfo.index), plen); - const auto datalast = data[datalen]; - - if (_pinfo.masked) { - for (size_t i = 0; i < datalen; i++) - data[i] ^= _pinfo.mask[(_pinfo.index + i) % 4]; - } - - if ((datalen + _pinfo.index) < _pinfo.len) { - _pstate = 1; - - if (_pinfo.index == 0) { - if (_pinfo.opcode) { - _pinfo.message_opcode = _pinfo.opcode; - _pinfo.num = 0; - } - } - if (datalen > 0) - _server->_handleEvent(this, WS_EVT_DATA, (void*)&_pinfo, (uint8_t*)data, datalen); - - _pinfo.index += datalen; - } else if ((datalen + _pinfo.index) == _pinfo.len) { - _pstate = 0; - if (_pinfo.opcode == WS_DISCONNECT) { - if (datalen) { - uint16_t reasonCode = (uint16_t)(data[0] << 8) + data[1]; - char* reasonString = (char*)(data + 2); - if (reasonCode > 1001) { - _server->_handleEvent(this, WS_EVT_ERROR, (void*)&reasonCode, (uint8_t*)reasonString, strlen(reasonString)); - } - } - if (_status == WS_DISCONNECTING) { - _status = WS_DISCONNECTED; - _client->close(true); - } else { - _status = WS_DISCONNECTING; - _client->ackLater(); - _queueControl(WS_DISCONNECT, data, datalen); - } - } else if (_pinfo.opcode == WS_PING) { - _queueControl(WS_PONG, data, datalen); - } else if (_pinfo.opcode == WS_PONG) { - if (datalen != AWSC_PING_PAYLOAD_LEN || memcmp(AWSC_PING_PAYLOAD, data, AWSC_PING_PAYLOAD_LEN) != 0) - _server->_handleEvent(this, WS_EVT_PONG, NULL, data, datalen); - } else if (_pinfo.opcode < 8) { // continuation or text/binary frame - _server->_handleEvent(this, WS_EVT_DATA, (void*)&_pinfo, data, datalen); - if (_pinfo.final) - _pinfo.num = 0; - else - _pinfo.num += 1; - } - } else { - // os_printf("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len); - // what should we do? - break; - } - - // restore byte as _handleEvent may have added a null terminator i.e., data[len] = 0; - if (datalen > 0) - data[datalen] = datalast; - - data += datalen; - plen -= datalen; - } -} - -size_t AsyncWebSocketClient::printf(const char* format, ...) { - va_list arg; - va_start(arg, format); - char* temp = new char[MAX_PRINTF_LEN]; - if (!temp) { - va_end(arg); - return 0; - } - char* buffer = temp; - size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg); - va_end(arg); - - if (len > (MAX_PRINTF_LEN - 1)) { - buffer = new char[len + 1]; - if (!buffer) { - delete[] temp; - return 0; - } - va_start(arg, format); - vsnprintf(buffer, len + 1, format, arg); - va_end(arg); - } - text(buffer, len); - if (buffer != temp) { - delete[] buffer; - } - delete[] temp; - return len; -} - -#ifdef ESP8266 -size_t AsyncWebSocketClient::printf_P(PGM_P formatP, ...) { - va_list arg; - va_start(arg, formatP); - char* temp = new char[MAX_PRINTF_LEN]; - if (!temp) { - va_end(arg); - return 0; - } - char* buffer = temp; - size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg); - va_end(arg); - - if (len > (MAX_PRINTF_LEN - 1)) { - buffer = new char[len + 1]; - if (!buffer) { - delete[] temp; - return 0; - } - va_start(arg, formatP); - vsnprintf_P(buffer, len + 1, formatP, arg); - va_end(arg); - } - text(buffer, len); - if (buffer != temp) { - delete[] buffer; - } - delete[] temp; - return len; -} -#endif - -namespace { - AsyncWebSocketSharedBuffer makeSharedBuffer(const uint8_t* message, size_t len) { - auto buffer = std::make_shared>(len); - std::memcpy(buffer->data(), message, len); - return buffer; - } -} - -void AsyncWebSocketClient::text(AsyncWebSocketMessageBuffer* buffer) { - if (buffer) { - text(std::move(buffer->_buffer)); - delete buffer; - } -} - -void AsyncWebSocketClient::text(AsyncWebSocketSharedBuffer buffer) { - _queueMessage(buffer); -} - -void AsyncWebSocketClient::text(const uint8_t* message, size_t len) { - text(makeSharedBuffer(message, len)); -} - -void AsyncWebSocketClient::text(const char* message, size_t len) { - text((const uint8_t*)message, len); -} - -void AsyncWebSocketClient::text(const char* message) { - text(message, strlen(message)); -} - -void AsyncWebSocketClient::text(const String& message) { - text(message.c_str(), message.length()); -} - -#ifdef ESP8266 -void AsyncWebSocketClient::text(const __FlashStringHelper* data) { - PGM_P p = reinterpret_cast(data); - - size_t n = 0; - while (1) { - if (pgm_read_byte(p + n) == 0) - break; - n += 1; - } - - char* message = (char*)malloc(n + 1); - if (message) { - memcpy_P(message, p, n); - message[n] = 0; - text(message, n); - free(message); - } -} -#endif // ESP8266 - -void AsyncWebSocketClient::binary(AsyncWebSocketMessageBuffer* buffer) { - if (buffer) { - binary(std::move(buffer->_buffer)); - delete buffer; - } -} - -void AsyncWebSocketClient::binary(AsyncWebSocketSharedBuffer buffer) { - _queueMessage(buffer, WS_BINARY); -} - -void AsyncWebSocketClient::binary(const uint8_t* message, size_t len) { - binary(makeSharedBuffer(message, len)); -} - -void AsyncWebSocketClient::binary(const char* message, size_t len) { - binary((const uint8_t*)message, len); -} - -void AsyncWebSocketClient::binary(const char* message) { - binary(message, strlen(message)); -} - -void AsyncWebSocketClient::binary(const String& message) { - binary(message.c_str(), message.length()); -} - -#ifdef ESP8266 -void AsyncWebSocketClient::binary(const __FlashStringHelper* data, size_t len) { - PGM_P p = reinterpret_cast(data); - char* message = (char*)malloc(len); - if (message) { - memcpy_P(message, p, len); - binary(message, len); - free(message); - } -} -#endif - -IPAddress AsyncWebSocketClient::remoteIP() const { - if (!_client) - return IPAddress((uint32_t)0U); - - return _client->remoteIP(); -} - -uint16_t AsyncWebSocketClient::remotePort() const { - if (!_client) - return 0; - - return _client->remotePort(); -} - -/* - * Async Web Socket - Each separate socket location - */ - -void AsyncWebSocket::_handleEvent(AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) { - if (_eventHandler != NULL) { - _eventHandler(this, client, type, arg, data, len); - } -} - -AsyncWebSocketClient* AsyncWebSocket::_newClient(AsyncWebServerRequest* request) { - _clients.emplace_back(request, this); - return &_clients.back(); -} - -bool AsyncWebSocket::availableForWriteAll() { - return std::none_of(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient& c) { return c.queueIsFull(); }); -} - -bool AsyncWebSocket::availableForWrite(uint32_t id) { - const auto iter = std::find_if(std::begin(_clients), std::end(_clients), [id](const AsyncWebSocketClient& c) { return c.id() == id; }); - if (iter == std::end(_clients)) - return true; - return !iter->queueIsFull(); -} - -size_t AsyncWebSocket::count() const { - return std::count_if(std::begin(_clients), std::end(_clients), [](const AsyncWebSocketClient& c) { return c.status() == WS_CONNECTED; }); -} - -AsyncWebSocketClient* AsyncWebSocket::client(uint32_t id) { - const auto iter = std::find_if(_clients.begin(), _clients.end(), [id](const AsyncWebSocketClient& c) { return c.id() == id && c.status() == WS_CONNECTED; }); - if (iter == std::end(_clients)) - return nullptr; - - return &(*iter); -} - -void AsyncWebSocket::close(uint32_t id, uint16_t code, const char* message) { - if (AsyncWebSocketClient* c = client(id)) - c->close(code, message); -} - -void AsyncWebSocket::closeAll(uint16_t code, const char* message) { - for (auto& c : _clients) - if (c.status() == WS_CONNECTED) - c.close(code, message); -} - -void AsyncWebSocket::cleanupClients(uint16_t maxClients) { - if (count() > maxClients) - _clients.front().close(); - - for (auto iter = std::begin(_clients); iter != std::end(_clients);) { - if (iter->shouldBeDeleted()) - iter = _clients.erase(iter); - else - iter++; - } -} - -void AsyncWebSocket::ping(uint32_t id, const uint8_t* data, size_t len) { - if (AsyncWebSocketClient* c = client(id)) - c->ping(data, len); -} - -void AsyncWebSocket::pingAll(const uint8_t* data, size_t len) { - for (auto& c : _clients) - if (c.status() == WS_CONNECTED) - c.ping(data, len); -} - -void AsyncWebSocket::text(uint32_t id, const uint8_t* message, size_t len) { - if (AsyncWebSocketClient* c = client(id)) - c->text(makeSharedBuffer(message, len)); -} -void AsyncWebSocket::text(uint32_t id, const char* message, size_t len) { - text(id, (const uint8_t*)message, len); -} -void AsyncWebSocket::text(uint32_t id, const char* message) { - text(id, message, strlen(message)); -} -void AsyncWebSocket::text(uint32_t id, const String& message) { - text(id, message.c_str(), message.length()); -} - -#ifdef ESP8266 -void AsyncWebSocket::text(uint32_t id, const __FlashStringHelper* data) { - PGM_P p = reinterpret_cast(data); - - size_t n = 0; - while (true) { - if (pgm_read_byte(p + n) == 0) - break; - n += 1; - } - - char* message = (char*)malloc(n + 1); - if (message) { - memcpy_P(message, p, n); - message[n] = 0; - text(id, message, n); - free(message); - } -} -#endif // ESP8266 - -void AsyncWebSocket::text(uint32_t id, AsyncWebSocketMessageBuffer* buffer) { - if (buffer) { - text(id, std::move(buffer->_buffer)); - delete buffer; - } -} -void AsyncWebSocket::text(uint32_t id, AsyncWebSocketSharedBuffer buffer) { - if (AsyncWebSocketClient* c = client(id)) - c->text(buffer); -} - -void AsyncWebSocket::textAll(const uint8_t* message, size_t len) { - textAll(makeSharedBuffer(message, len)); -} -void AsyncWebSocket::textAll(const char* message, size_t len) { - textAll((const uint8_t*)message, len); -} -void AsyncWebSocket::textAll(const char* message) { - textAll(message, strlen(message)); -} -void AsyncWebSocket::textAll(const String& message) { - textAll(message.c_str(), message.length()); -} -#ifdef ESP8266 -void AsyncWebSocket::textAll(const __FlashStringHelper* data) { - PGM_P p = reinterpret_cast(data); - - size_t n = 0; - while (1) { - if (pgm_read_byte(p + n) == 0) - break; - n += 1; - } - - char* message = (char*)malloc(n + 1); - if (message) { - memcpy_P(message, p, n); - message[n] = 0; - textAll(message, n); - free(message); - } -} -#endif // ESP8266 -void AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer* buffer) { - if (buffer) { - textAll(std::move(buffer->_buffer)); - delete buffer; - } -} - -void AsyncWebSocket::textAll(AsyncWebSocketSharedBuffer buffer) { - for (auto& c : _clients) - if (c.status() == WS_CONNECTED) - c.text(buffer); -} - -void AsyncWebSocket::binary(uint32_t id, const uint8_t* message, size_t len) { - if (AsyncWebSocketClient* c = client(id)) - c->binary(makeSharedBuffer(message, len)); -} -void AsyncWebSocket::binary(uint32_t id, const char* message, size_t len) { - binary(id, (const uint8_t*)message, len); -} -void AsyncWebSocket::binary(uint32_t id, const char* message) { - binary(id, message, strlen(message)); -} -void AsyncWebSocket::binary(uint32_t id, const String& message) { - binary(id, message.c_str(), message.length()); -} - -#ifdef ESP8266 -void AsyncWebSocket::binary(uint32_t id, const __FlashStringHelper* data, size_t len) { - PGM_P p = reinterpret_cast(data); - char* message = (char*)malloc(len); - if (message) { - memcpy_P(message, p, len); - binary(id, message, len); - free(message); - } -} -#endif // ESP8266 - -void AsyncWebSocket::binary(uint32_t id, AsyncWebSocketMessageBuffer* buffer) { - if (buffer) { - binary(id, std::move(buffer->_buffer)); - delete buffer; - } -} -void AsyncWebSocket::binary(uint32_t id, AsyncWebSocketSharedBuffer buffer) { - if (AsyncWebSocketClient* c = client(id)) - c->binary(buffer); -} - -void AsyncWebSocket::binaryAll(const uint8_t* message, size_t len) { - binaryAll(makeSharedBuffer(message, len)); -} -void AsyncWebSocket::binaryAll(const char* message, size_t len) { - binaryAll((const uint8_t*)message, len); -} -void AsyncWebSocket::binaryAll(const char* message) { - binaryAll(message, strlen(message)); -} -void AsyncWebSocket::binaryAll(const String& message) { - binaryAll(message.c_str(), message.length()); -} - -#ifdef ESP8266 -void AsyncWebSocket::binaryAll(const __FlashStringHelper* data, size_t len) { - PGM_P p = reinterpret_cast(data); - char* message = (char*)malloc(len); - if (message) { - memcpy_P(message, p, len); - binaryAll(message, len); - free(message); - } -} -#endif // ESP8266 - -void AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer* buffer) { - if (buffer) { - binaryAll(std::move(buffer->_buffer)); - delete buffer; - } -} -void AsyncWebSocket::binaryAll(AsyncWebSocketSharedBuffer buffer) { - for (auto& c : _clients) - if (c.status() == WS_CONNECTED) - c.binary(buffer); -} - -size_t AsyncWebSocket::printf(uint32_t id, const char* format, ...) { - AsyncWebSocketClient* c = client(id); - if (c) { - va_list arg; - va_start(arg, format); - size_t len = c->printf(format, arg); - va_end(arg); - return len; - } - return 0; -} - -size_t AsyncWebSocket::printfAll(const char* format, ...) { - va_list arg; - char* temp = new char[MAX_PRINTF_LEN]; - if (!temp) - return 0; - - va_start(arg, format); - size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg); - va_end(arg); - delete[] temp; - - AsyncWebSocketSharedBuffer buffer = std::make_shared>(len); - - va_start(arg, format); - vsnprintf((char*)buffer->data(), len + 1, format, arg); - va_end(arg); - - textAll(buffer); - return len; -} - -#ifdef ESP8266 -size_t AsyncWebSocket::printf_P(uint32_t id, PGM_P formatP, ...) { - AsyncWebSocketClient* c = client(id); - if (c != NULL) { - va_list arg; - va_start(arg, formatP); - size_t len = c->printf_P(formatP, arg); - va_end(arg); - return len; - } - return 0; -} - -size_t AsyncWebSocket::printfAll_P(PGM_P formatP, ...) { - va_list arg; - char* temp = new char[MAX_PRINTF_LEN]; - if (!temp) - return 0; - - va_start(arg, formatP); - size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg); - va_end(arg); - delete[] temp; - - AsyncWebSocketSharedBuffer buffer = std::make_shared>(len + 1); - - va_start(arg, formatP); - vsnprintf_P((char*)buffer->data(), len + 1, formatP, arg); - va_end(arg); - - textAll(buffer); - return len; -} -#endif - -const char __WS_STR_CONNECTION[] PROGMEM = {"Connection"}; -const char __WS_STR_UPGRADE[] PROGMEM = {"Upgrade"}; -const char __WS_STR_ORIGIN[] PROGMEM = {"Origin"}; -const char __WS_STR_COOKIE[] PROGMEM = {"Cookie"}; -const char __WS_STR_VERSION[] PROGMEM = {"Sec-WebSocket-Version"}; -const char __WS_STR_KEY[] PROGMEM = {"Sec-WebSocket-Key"}; -const char __WS_STR_PROTOCOL[] PROGMEM = {"Sec-WebSocket-Protocol"}; -const char __WS_STR_ACCEPT[] PROGMEM = {"Sec-WebSocket-Accept"}; -const char __WS_STR_UUID[] PROGMEM = {"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"}; - -#define WS_STR_UUID_LEN 36 - -#define WS_STR_CONNECTION FPSTR(__WS_STR_CONNECTION) -#define WS_STR_UPGRADE FPSTR(__WS_STR_UPGRADE) -#define WS_STR_ORIGIN FPSTR(__WS_STR_ORIGIN) -#define WS_STR_COOKIE FPSTR(__WS_STR_COOKIE) -#define WS_STR_VERSION FPSTR(__WS_STR_VERSION) -#define WS_STR_KEY FPSTR(__WS_STR_KEY) -#define WS_STR_PROTOCOL FPSTR(__WS_STR_PROTOCOL) -#define WS_STR_ACCEPT FPSTR(__WS_STR_ACCEPT) -#define WS_STR_UUID FPSTR(__WS_STR_UUID) - -bool AsyncWebSocket::canHandle(AsyncWebServerRequest* request) { - if (!_enabled) - return false; - - if (request->method() != HTTP_GET || !request->url().equals(_url) || !request->isExpectedRequestedConnType(RCT_WS)) - return false; - - request->addInterestingHeader(WS_STR_CONNECTION); - request->addInterestingHeader(WS_STR_UPGRADE); - request->addInterestingHeader(WS_STR_ORIGIN); - request->addInterestingHeader(WS_STR_COOKIE); - request->addInterestingHeader(WS_STR_VERSION); - request->addInterestingHeader(WS_STR_KEY); - request->addInterestingHeader(WS_STR_PROTOCOL); - return true; -} - -void AsyncWebSocket::handleRequest(AsyncWebServerRequest* request) { - if (!request->hasHeader(WS_STR_VERSION) || !request->hasHeader(WS_STR_KEY)) { - request->send(400); - return; - } - if ((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) { - return request->requestAuthentication(); - } - if (_handshakeHandler != nullptr) { - if (!_handshakeHandler(request)) { - request->send(401); - return; - } - } - const AsyncWebHeader* version = request->getHeader(WS_STR_VERSION); - if (version->value().toInt() != 13) { - AsyncWebServerResponse* response = request->beginResponse(400); - response->addHeader(WS_STR_VERSION, T_13); - request->send(response); - return; - } - const AsyncWebHeader* key = request->getHeader(WS_STR_KEY); - AsyncWebServerResponse* response = new AsyncWebSocketResponse(key->value(), this); - if (request->hasHeader(WS_STR_PROTOCOL)) { - const AsyncWebHeader* protocol = request->getHeader(WS_STR_PROTOCOL); - // ToDo: check protocol - response->addHeader(WS_STR_PROTOCOL, protocol->value()); - } - request->send(response); -} - -AsyncWebSocketMessageBuffer* AsyncWebSocket::makeBuffer(size_t size) { - AsyncWebSocketMessageBuffer* buffer = new AsyncWebSocketMessageBuffer(size); - if (buffer->length() != size) { - delete buffer; - return nullptr; - } else { - return buffer; - } -} - -AsyncWebSocketMessageBuffer* AsyncWebSocket::makeBuffer(const uint8_t* data, size_t size) { - AsyncWebSocketMessageBuffer* buffer = new AsyncWebSocketMessageBuffer(data, size); - if (buffer->length() != size) { - delete buffer; - return nullptr; - } else { - return buffer; - } -} - -/* - * Response to Web Socket request - sends the authorization and detaches the TCP Client from the web server - * Authentication code from https://github.com/Links2004/arduinoWebSockets/blob/master/src/WebSockets.cpp#L480 - */ - -AsyncWebSocketResponse::AsyncWebSocketResponse(const String& key, AsyncWebSocket* server) { - _server = server; - _code = 101; - _sendContentLength = false; - - uint8_t hash[20]; - char buffer[33]; - -#if defined(ESP8266) || defined(TARGET_RP2040) - sha1(key + WS_STR_UUID, hash); -#else - String k; - k.reserve(key.length() + WS_STR_UUID_LEN); - k.concat(key); - k.concat(WS_STR_UUID); - SHA1Builder sha1; - sha1.begin(); - sha1.add((const uint8_t*)k.c_str(), k.length()); - sha1.calculate(); - sha1.getBytes(hash); -#endif - base64_encodestate _state; - base64_init_encodestate(&_state); - int len = base64_encode_block((const char*)hash, 20, buffer, &_state); - len = base64_encode_blockend((buffer + len), &_state); - addHeader(WS_STR_CONNECTION, WS_STR_UPGRADE); - addHeader(WS_STR_UPGRADE, T_WS); - addHeader(WS_STR_ACCEPT, buffer); -} - -void AsyncWebSocketResponse::_respond(AsyncWebServerRequest* request) { - if (_state == RESPONSE_FAILED) { - request->client()->close(true); - return; - } - String out(_assembleHead(request->version())); - request->client()->write(out.c_str(), _headLength); - _state = RESPONSE_WAIT_ACK; -} - -size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) { - (void)time; - - if (len) - _server->_newClient(request); - - return 0; -} diff --git a/lib/ESPAsyncWebServer/src/AsyncWebSocket.h b/lib/ESPAsyncWebServer/src/AsyncWebSocket.h deleted file mode 100644 index 34256a7..0000000 --- a/lib/ESPAsyncWebServer/src/AsyncWebSocket.h +++ /dev/null @@ -1,379 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#ifndef ASYNCWEBSOCKET_H_ -#define ASYNCWEBSOCKET_H_ - -#include -#ifdef ESP32 - #include - #include - #ifndef WS_MAX_QUEUED_MESSAGES - #define WS_MAX_QUEUED_MESSAGES 32 - #endif -#elif defined(ESP8266) - #include - #ifndef WS_MAX_QUEUED_MESSAGES - #define WS_MAX_QUEUED_MESSAGES 8 - #endif -#elif defined(TARGET_RP2040) - #include - #ifndef WS_MAX_QUEUED_MESSAGES - #define WS_MAX_QUEUED_MESSAGES 32 - #endif -#endif - -#include - -#include -#include -#include - -#ifdef ESP8266 - #include - #ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library - #include <../src/Hash.h> - #endif -#endif - -#ifndef DEFAULT_MAX_WS_CLIENTS - #ifdef ESP32 - #define DEFAULT_MAX_WS_CLIENTS 8 - #else - #define DEFAULT_MAX_WS_CLIENTS 4 - #endif -#endif - -using AsyncWebSocketSharedBuffer = std::shared_ptr>; - -class AsyncWebSocket; -class AsyncWebSocketResponse; -class AsyncWebSocketClient; -class AsyncWebSocketControl; - -typedef struct { - /** Message type as defined by enum AwsFrameType. - * Note: Applications will only see WS_TEXT and WS_BINARY. - * All other types are handled by the library. */ - uint8_t message_opcode; - /** Frame number of a fragmented message. */ - uint32_t num; - /** Is this the last frame in a fragmented message ?*/ - uint8_t final; - /** Is this frame masked? */ - uint8_t masked; - /** Message type as defined by enum AwsFrameType. - * This value is the same as message_opcode for non-fragmented - * messages, but may also be WS_CONTINUATION in a fragmented message. */ - uint8_t opcode; - /** Length of the current frame. - * This equals the total length of the message if num == 0 && final == true */ - uint64_t len; - /** Mask key */ - uint8_t mask[4]; - /** Offset of the data inside the current frame. */ - uint64_t index; -} AwsFrameInfo; - -typedef enum { WS_DISCONNECTED, - WS_CONNECTED, - WS_DISCONNECTING } AwsClientStatus; -typedef enum { WS_CONTINUATION, - WS_TEXT, - WS_BINARY, - WS_DISCONNECT = 0x08, - WS_PING, - WS_PONG } AwsFrameType; -typedef enum { WS_MSG_SENDING, - WS_MSG_SENT, - WS_MSG_ERROR } AwsMessageStatus; -typedef enum { WS_EVT_CONNECT, - WS_EVT_DISCONNECT, - WS_EVT_PONG, - WS_EVT_ERROR, - WS_EVT_DATA } AwsEventType; - -class AsyncWebSocketMessageBuffer { - friend AsyncWebSocket; - friend AsyncWebSocketClient; - - private: - AsyncWebSocketSharedBuffer _buffer; - - public: - AsyncWebSocketMessageBuffer() {} - explicit AsyncWebSocketMessageBuffer(size_t size); - AsyncWebSocketMessageBuffer(const uint8_t* data, size_t size); - //~AsyncWebSocketMessageBuffer(); - bool reserve(size_t size); - uint8_t* get() { return _buffer->data(); } - size_t length() const { return _buffer->size(); } -}; - -class AsyncWebSocketMessage { - private: - AsyncWebSocketSharedBuffer _WSbuffer; - uint8_t _opcode{WS_TEXT}; - bool _mask{false}; - AwsMessageStatus _status{WS_MSG_ERROR}; - size_t _sent{}; - size_t _ack{}; - size_t _acked{}; - - public: - AsyncWebSocketMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false); - - bool finished() const { return _status != WS_MSG_SENDING; } - bool betweenFrames() const { return _acked == _ack; } - - void ack(size_t len, uint32_t time); - size_t send(AsyncClient* client); -}; - -class AsyncWebSocketClient { - private: - AsyncClient* _client; - AsyncWebSocket* _server; - uint32_t _clientId; - AwsClientStatus _status; -#ifdef ESP32 - mutable std::mutex _lock; -#endif - std::deque _controlQueue; - std::deque _messageQueue; - bool closeWhenFull = true; - - uint8_t _pstate; - AwsFrameInfo _pinfo; - - uint32_t _lastMessageTime; - uint32_t _keepAlivePeriod; - - void _queueControl(uint8_t opcode, const uint8_t* data = NULL, size_t len = 0, bool mask = false); - void _queueMessage(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false); - void _runQueue(); - void _clearQueue(); - - public: - void* _tempObject; - - AsyncWebSocketClient(AsyncWebServerRequest* request, AsyncWebSocket* server); - ~AsyncWebSocketClient(); - - // client id increments for the given server - uint32_t id() const { return _clientId; } - AwsClientStatus status() const { return _status; } - AsyncClient* client() { return _client; } - const AsyncClient* client() const { return _client; } - AsyncWebSocket* server() { return _server; } - const AsyncWebSocket* server() const { return _server; } - AwsFrameInfo const& pinfo() const { return _pinfo; } - - // - If "true" (default), the connection will be closed if the message queue is full. - // This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection. - // The big issue with this behavior is that is can cause the UI to automatically re-create a new WS connection, which can be filled again, - // and so on, causing a resource exhaustion. - // - // - If "false", the incoming message will be discarded if the queue is full. - // This is the default behavior in the original ESPAsyncWebServer library from me-no-dev. - // This behavior allows the best performance at the expense of unreliable message delivery in case the queue is full (some messages may be lost). - // - // - In any case, when the queue is full, a message is logged. - // - IT is recommended to use the methods queueIsFull(), availableForWriteAll(), availableForWrite(clientId) to check if the queue is full before sending a message. - // - // Usage: - // - can be set in the onEvent listener when connecting (event type is: WS_EVT_CONNECT) - // - // Use cases:, - // - if using websocket to send logging messages, maybe some loss is acceptable. - // - But if using websocket to send UI update messages, maybe the connection should be closed and the UI redrawn. - void setCloseClientOnQueueFull(bool close) { closeWhenFull = close; } - bool willCloseClientOnQueueFull() const { return closeWhenFull; } - - IPAddress remoteIP() const; - uint16_t remotePort() const; - - bool shouldBeDeleted() const { return !_client; } - - // control frames - void close(uint16_t code = 0, const char* message = NULL); - void ping(const uint8_t* data = NULL, size_t len = 0); - - // set auto-ping period in seconds. disabled if zero (default) - void keepAlivePeriod(uint16_t seconds) { - _keepAlivePeriod = seconds * 1000; - } - uint16_t keepAlivePeriod() { - return (uint16_t)(_keepAlivePeriod / 1000); - } - - // data packets - void message(AsyncWebSocketSharedBuffer buffer, uint8_t opcode = WS_TEXT, bool mask = false) { _queueMessage(buffer, opcode, mask); } - bool queueIsFull() const; - size_t queueLen() const; - - size_t printf(const char* format, ...) __attribute__((format(printf, 2, 3))); - - void text(AsyncWebSocketSharedBuffer buffer); - void text(const uint8_t* message, size_t len); - void text(const char* message, size_t len); - void text(const char* message); - void text(const String& message); - void text(AsyncWebSocketMessageBuffer* buffer); - - void binary(AsyncWebSocketSharedBuffer buffer); - void binary(const uint8_t* message, size_t len); - void binary(const char* message, size_t len); - void binary(const char* message); - void binary(const String& message); - void binary(AsyncWebSocketMessageBuffer* buffer); - - bool canSend() const; - - // system callbacks (do not call) - void _onAck(size_t len, uint32_t time); - void _onError(int8_t); - void _onPoll(); - void _onTimeout(uint32_t time); - void _onDisconnect(); - void _onData(void* pbuf, size_t plen); - -#ifdef ESP8266 - size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3))); - void text(const __FlashStringHelper* message); - void binary(const __FlashStringHelper* message, size_t len); -#endif -}; - -using AwsHandshakeHandler = std::function; -using AwsEventHandler = std::function; - -// WebServer Handler implementation that plays the role of a socket server -class AsyncWebSocket : public AsyncWebHandler { - private: - String _url; - std::list _clients; - uint32_t _cNextId; - AwsEventHandler _eventHandler{nullptr}; - AwsHandshakeHandler _handshakeHandler; - bool _enabled; -#ifdef ESP32 - mutable std::mutex _lock; -#endif - - public: - explicit AsyncWebSocket(const char* url) : _url(url), _cNextId(1), _enabled(true) {} - AsyncWebSocket(const String& url) : _url(url), _cNextId(1), _enabled(true) {} - ~AsyncWebSocket(){}; - const char* url() const { return _url.c_str(); } - void enable(bool e) { _enabled = e; } - bool enabled() const { return _enabled; } - bool availableForWriteAll(); - bool availableForWrite(uint32_t id); - - size_t count() const; - AsyncWebSocketClient* client(uint32_t id); - bool hasClient(uint32_t id) { return client(id) != nullptr; } - - void close(uint32_t id, uint16_t code = 0, const char* message = NULL); - void closeAll(uint16_t code = 0, const char* message = NULL); - void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS); - - void ping(uint32_t id, const uint8_t* data = NULL, size_t len = 0); - void pingAll(const uint8_t* data = NULL, size_t len = 0); // done - - void text(uint32_t id, const uint8_t* message, size_t len); - void text(uint32_t id, const char* message, size_t len); - void text(uint32_t id, const char* message); - void text(uint32_t id, const String& message); - void text(uint32_t id, AsyncWebSocketMessageBuffer* buffer); - void text(uint32_t id, AsyncWebSocketSharedBuffer buffer); - - void textAll(const uint8_t* message, size_t len); - void textAll(const char* message, size_t len); - void textAll(const char* message); - void textAll(const String& message); - void textAll(AsyncWebSocketMessageBuffer* buffer); - void textAll(AsyncWebSocketSharedBuffer buffer); - - void binary(uint32_t id, const uint8_t* message, size_t len); - void binary(uint32_t id, const char* message, size_t len); - void binary(uint32_t id, const char* message); - void binary(uint32_t id, const String& message); - void binary(uint32_t id, AsyncWebSocketMessageBuffer* buffer); - void binary(uint32_t id, AsyncWebSocketSharedBuffer buffer); - - void binaryAll(const uint8_t* message, size_t len); - void binaryAll(const char* message, size_t len); - void binaryAll(const char* message); - void binaryAll(const String& message); - void binaryAll(AsyncWebSocketMessageBuffer* buffer); - void binaryAll(AsyncWebSocketSharedBuffer buffer); - - size_t printf(uint32_t id, const char* format, ...) __attribute__((format(printf, 3, 4))); - size_t printfAll(const char* format, ...) __attribute__((format(printf, 2, 3))); - -#ifdef ESP8266 - void text(uint32_t id, const __FlashStringHelper* message); - void textAll(const __FlashStringHelper* message); - void binary(uint32_t id, const __FlashStringHelper* message, size_t len); - void binaryAll(const __FlashStringHelper* message, size_t len); - size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__((format(printf, 3, 4))); - size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3))); -#endif - - // event listener - void onEvent(AwsEventHandler handler) { - _eventHandler = handler; - } - - // Handshake Handler - void handleHandshake(AwsHandshakeHandler handler) { - _handshakeHandler = handler; - } - - // system callbacks (do not call) - uint32_t _getNextId() { return _cNextId++; } - AsyncWebSocketClient* _newClient(AsyncWebServerRequest* request); - 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(const uint8_t* data, size_t size); - - const std::list& getClients() const { return _clients; } -}; - -// WebServer response to authenticate the socket and detach the tcp client from the web server request -class AsyncWebSocketResponse : public AsyncWebServerResponse { - private: - String _content; - AsyncWebSocket* _server; - - public: - AsyncWebSocketResponse(const String& key, AsyncWebSocket* server); - void _respond(AsyncWebServerRequest* request); - size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time); - bool _sourceValid() const { return true; } -}; - -#endif /* ASYNCWEBSOCKET_H_ */ diff --git a/lib/ESPAsyncWebServer/src/ChunkPrint.h b/lib/ESPAsyncWebServer/src/ChunkPrint.h deleted file mode 100644 index 2f40741..0000000 --- a/lib/ESPAsyncWebServer/src/ChunkPrint.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef CHUNKPRINT_H -#define CHUNKPRINT_H - -#include - -class ChunkPrint : public Print { - private: - uint8_t* _destination; - size_t _to_skip; - size_t _to_write; - size_t _pos; - - public: - ChunkPrint(uint8_t* destination, size_t from, size_t len) - : _destination(destination), _to_skip(from), _to_write(len), _pos{0} {} - virtual ~ChunkPrint() {} - size_t write(uint8_t c) { - if (_to_skip > 0) { - _to_skip--; - return 1; - } else if (_to_write > 0) { - _to_write--; - _destination[_pos++] = c; - return 1; - } - return 0; - } - size_t write(const uint8_t* buffer, size_t size) { - return this->Print::write(buffer, size); - } -}; -#endif diff --git a/lib/ESPAsyncWebServer/src/ESPAsyncWebServer.h b/lib/ESPAsyncWebServer/src/ESPAsyncWebServer.h deleted file mode 100644 index de08bc0..0000000 --- a/lib/ESPAsyncWebServer/src/ESPAsyncWebServer.h +++ /dev/null @@ -1,714 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#ifndef _ESPAsyncWebServer_H_ -#define _ESPAsyncWebServer_H_ - -#include "Arduino.h" - -#include "FS.h" -#include -#include -#include - -#ifdef ESP32 - #include - #include -#elif defined(ESP8266) - #include - #include -#elif defined(TARGET_RP2040) - #include - #include - #include - #include -#else - #error Platform not supported -#endif - -#include "literals.h" - -#define ASYNCWEBSERVER_VERSION "3.1.5" -#define ASYNCWEBSERVER_VERSION_MAJOR 3 -#define ASYNCWEBSERVER_VERSION_MINOR 1 -#define ASYNCWEBSERVER_VERSION_REVISION 5 -#define ASYNCWEBSERVER_FORK_mathieucarbou - -#ifdef ASYNCWEBSERVER_REGEX - #define ASYNCWEBSERVER_REGEX_ATTRIBUTE -#else - #define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined"))) -#endif - -class AsyncWebServer; -class AsyncWebServerRequest; -class AsyncWebServerResponse; -class AsyncWebHeader; -class AsyncWebParameter; -class AsyncWebRewrite; -class AsyncWebHandler; -class AsyncStaticWebHandler; -class AsyncCallbackWebHandler; -class AsyncResponseStream; - -#if defined(TARGET_RP2040) -typedef enum http_method WebRequestMethod; -#else - #ifndef WEBSERVER_H -typedef enum { - HTTP_GET = 0b00000001, - HTTP_POST = 0b00000010, - HTTP_DELETE = 0b00000100, - HTTP_PUT = 0b00001000, - HTTP_PATCH = 0b00010000, - HTTP_HEAD = 0b00100000, - HTTP_OPTIONS = 0b01000000, - HTTP_ANY = 0b01111111, -} WebRequestMethod; - #endif -#endif - -#ifndef HAVE_FS_FILE_OPEN_MODE -namespace fs { - class FileOpenMode { - public: - static const char* read; - static const char* write; - static const char* append; - }; -}; -#else - #include "FileOpenMode.h" -#endif - -// if this value is returned when asked for data, packet will not be sent and you will be asked for data again -#define RESPONSE_TRY_AGAIN 0xFFFFFFFF - -typedef uint8_t WebRequestMethodComposite; -typedef std::function ArDisconnectHandler; - -/* - * PARAMETER :: Chainable object to hold GET/POST and FILE parameters - * */ - -class AsyncWebParameter { - private: - String _name; - String _value; - size_t _size; - bool _isForm; - bool _isFile; - - public: - AsyncWebParameter(const String& name, const String& value, bool form = false, bool file = false, size_t size = 0) : _name(name), _value(value), _size(size), _isForm(form), _isFile(file) {} - const String& name() const { return _name; } - const String& value() const { return _value; } - size_t size() const { return _size; } - bool isPost() const { return _isForm; } - bool isFile() const { return _isFile; } -}; - -/* - * HEADER :: Chainable object to hold the headers - * */ - -class AsyncWebHeader { - private: - String _name; - String _value; - - public: - AsyncWebHeader() = default; - AsyncWebHeader(const AsyncWebHeader&) = default; - - AsyncWebHeader(const char* name, const char* value) : _name(name), _value(value) {} - AsyncWebHeader(const String& name, const String& value) : _name(name), _value(value) {} - AsyncWebHeader(const String& data) { - if (!data) - return; - int index = data.indexOf(':'); - if (index < 0) - return; - _name = data.substring(0, index); - _value = data.substring(index + 2); - } - - AsyncWebHeader& operator=(const AsyncWebHeader&) = default; - - const String& name() const { return _name; } - const String& value() const { return _value; } - String toString() const { - String str = _name; - str.concat((char)0x3a); - str.concat((char)0x20); - str.concat(_value); - str.concat(asyncsrv::T_rn); - return str; - } -}; - -/* - * REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect - * */ - -typedef enum { RCT_NOT_USED = -1, - RCT_DEFAULT = 0, - RCT_HTTP, - RCT_WS, - RCT_EVENT, - RCT_MAX } RequestedConnectionType; - -typedef std::function AwsResponseFiller; -typedef std::function AwsTemplateProcessor; - -class AsyncWebServerRequest { - using File = fs::File; - using FS = fs::FS; - friend class AsyncWebServer; - friend class AsyncCallbackWebHandler; - - private: - AsyncClient* _client; - AsyncWebServer* _server; - AsyncWebHandler* _handler; - AsyncWebServerResponse* _response; - std::vector _interestingHeaders; - ArDisconnectHandler _onDisconnectfn; - - String _temp; - uint8_t _parseState; - - uint8_t _version; - WebRequestMethodComposite _method; - String _url; - String _host; - String _contentType; - String _boundary; - String _authorization; - RequestedConnectionType _reqconntype; - void _removeNotInterestingHeaders(); - bool _isDigest; - bool _isMultipart; - bool _isPlainPost; - bool _expectingContinue; - size_t _contentLength; - size_t _parsedLength; - - std::list _headers; - std::list _params; - std::vector _pathParams; - - uint8_t _multiParseState; - uint8_t _boundaryPosition; - size_t _itemStartIndex; - size_t _itemSize; - String _itemName; - String _itemFilename; - String _itemType; - String _itemValue; - uint8_t* _itemBuffer; - size_t _itemBufferIndex; - bool _itemIsFile; - - void _onPoll(); - void _onAck(size_t len, uint32_t time); - void _onError(int8_t error); - void _onTimeout(uint32_t time); - void _onDisconnect(); - void _onData(void* buf, size_t len); - - void _addPathParam(const char* param); - - bool _parseReqHead(); - bool _parseReqHeader(); - void _parseLine(); - void _parsePlainPostChar(uint8_t data); - void _parseMultipartPostByte(uint8_t data, bool last); - void _addGetParams(const String& params); - - void _handleUploadStart(); - void _handleUploadByte(uint8_t data, bool last); - void _handleUploadEnd(); - - public: - File _tempFile; - void* _tempObject; - - AsyncWebServerRequest(AsyncWebServer*, AsyncClient*); - ~AsyncWebServerRequest(); - - AsyncClient* client() { return _client; } - uint8_t version() const { return _version; } - WebRequestMethodComposite method() const { return _method; } - const String& url() const { return _url; } - const String& host() const { return _host; } - const String& contentType() const { return _contentType; } - size_t contentLength() const { return _contentLength; } - bool multipart() const { return _isMultipart; } - -#ifndef ESP8266 - const char* methodToString() const; - const char* requestedConnTypeToString() const; -#else - const __FlashStringHelper* methodToString() const; - const __FlashStringHelper* requestedConnTypeToString() const; -#endif - - RequestedConnectionType requestedConnType() const { return _reqconntype; } - bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED); - void onDisconnect(ArDisconnectHandler fn); - - // hash is the string representation of: - // base64(user:pass) for basic or - // user:realm:md5(user:realm:pass) for digest - bool authenticate(const char* hash); - 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) { _handler = handler; } - - /** - * @brief add header to collect from a response - * - * @param name - */ - void addInterestingHeader(const char* name); - void addInterestingHeader(const String& name) { return addInterestingHeader(name.c_str()); }; - - /** - * @brief issue 302 redirect response - * - * @param url - */ - void redirect(const char* url); - void redirect(const String& url) { return redirect(url.c_str()); }; - - void send(AsyncWebServerResponse* response); - - void send(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, callback)); } - void send(int code, const String& contentType, const String& content = emptyString, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, callback)); } - - void send(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, len, callback)); } - void send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, len, callback)); } - - void send(FS& fs, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) { - if (fs.exists(path) || (!download && fs.exists(path + asyncsrv::T__gz))) { - send(beginResponse(fs, path, contentType, download, callback)); - } else - send(404); - } - void send(FS& fs, const String& path, const String& contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { send(fs, path, contentType.c_str(), download, callback); } - - void send(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) { - if (content) { - send(beginResponse(content, path, contentType, download, callback)); - } else - send(404); - } - void send(File content, const String& path, const String& contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { send(content, path, contentType.c_str(), download, callback); } - - void send(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(stream, contentType, len, callback)); } - void send(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(stream, contentType, len, callback)); } - - void send(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginResponse(contentType, len, callback, templateCallback)); } - void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginResponse(contentType, len, callback, templateCallback)); } - - void sendChunked(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginChunkedResponse(contentType, callback, templateCallback)); } - void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginChunkedResponse(contentType, callback, templateCallback)); } - - [[deprecated("Replaced by send(...)")]] - void send_P(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { - send(code, contentType, content, len, callback); - } - [[deprecated("Replaced by send(...)")]] - void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr) { - send(code, contentType, content, callback); - } - -#ifdef ESP8266 - void send(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, callback)); } -#endif - - AsyncWebServerResponse* beginResponse(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr); - AsyncWebServerResponse* beginResponse(int code, const String& contentType, const String& content = emptyString, AwsTemplateProcessor callback = nullptr) { return beginResponse(code, contentType.c_str(), content.c_str(), callback); } - - AsyncWebServerResponse* beginResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr); - AsyncWebServerResponse* beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { return beginResponse(code, contentType.c_str(), content, len, callback); } - - AsyncWebServerResponse* beginResponse(FS& fs, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr); - AsyncWebServerResponse* beginResponse(FS& fs, const String& path, const String& contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { return beginResponse(fs, path, contentType.c_str(), download, callback); } - - AsyncWebServerResponse* beginResponse(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr); - AsyncWebServerResponse* beginResponse(File content, const String& path, const String& contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { return beginResponse(content, path, contentType.c_str(), download, callback); } - - AsyncWebServerResponse* beginResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback = nullptr); - AsyncWebServerResponse* beginResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) { return beginResponse(stream, contentType.c_str(), len, callback); } - - AsyncWebServerResponse* beginResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - AsyncWebServerResponse* beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { return beginResponse(contentType.c_str(), len, callback, templateCallback); } - - AsyncWebServerResponse* beginChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - AsyncWebServerResponse* beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - - AsyncResponseStream* beginResponseStream(const char* contentType, size_t bufferSize = 1460); - AsyncResponseStream* beginResponseStream(const String& contentType, size_t bufferSize = 1460) { return beginResponseStream(contentType.c_str(), bufferSize); } - - [[deprecated("Replaced by beginResponse(...)")]] - AsyncWebServerResponse* beginResponse_P(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { - return beginResponse(code, contentType, content, len, callback); - } - [[deprecated("Replaced by beginResponse(...)")]] - AsyncWebServerResponse* beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr) { - return beginResponse(code, contentType, content, callback); - } - -#ifdef ESP8266 - AsyncWebServerResponse* beginResponse(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr); -#endif - - size_t headers() const; // get header count - - // check if header exists - bool hasHeader(const char* name) const; - bool hasHeader(const String& name) const { return hasHeader(name.c_str()); }; -#ifdef ESP8266 - bool hasHeader(const __FlashStringHelper* data) const; // check if header exists -#endif - - const AsyncWebHeader* getHeader(const char* name) const; - const AsyncWebHeader* getHeader(const String& name) const { return getHeader(name.c_str()); }; -#ifdef ESP8266 - const AsyncWebHeader* getHeader(const __FlashStringHelper* data) const; -#endif - const AsyncWebHeader* getHeader(size_t num) const; - - size_t params() const; // get arguments count - bool hasParam(const char* name, bool post = false, bool file = false) const; - bool hasParam(const String& name, bool post = false, bool file = false) const { return hasParam(name.c_str(), post, file); }; -#ifdef ESP8266 - bool hasParam(const __FlashStringHelper* data, bool post = false, bool file = false) const { return hasParam(String(data).c_str(), post, file); }; -#endif - - /** - * @brief Get the Request parameter by name - * - * @param name - * @param post - * @param file - * @return const AsyncWebParameter* - */ - const AsyncWebParameter* getParam(const char* name, bool post = false, bool file = false) const; - - const AsyncWebParameter* getParam(const String& name, bool post = false, bool file = false) const { return getParam(name.c_str(), post, file); }; -#ifdef ESP8266 - const AsyncWebParameter* getParam(const __FlashStringHelper* data, bool post, bool file) const; -#endif - - /** - * @brief Get request parameter by number - * i.e., n-th parameter - * @param num - * @return const AsyncWebParameter* - */ - const AsyncWebParameter* getParam(size_t num) const; - - size_t args() const { return params(); } // get arguments count - - // get request argument value by name - const String& arg(const char* name) const; - // get request argument value by name - const String& arg(const String& name) const { return arg(name.c_str()); }; -#ifdef ESP8266 - const String& arg(const __FlashStringHelper* data) const; // get request argument value by F(name) -#endif - const String& arg(size_t i) const; // get request argument value by number - const String& argName(size_t i) const; // get request argument name by number - bool hasArg(const char* name) const; // check if argument exists - bool hasArg(const String& name) const { return hasArg(name.c_str()); }; -#ifdef ESP8266 - bool hasArg(const __FlashStringHelper* data) const; // check if F(argument) exists -#endif - - const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const; - - // get request header value by name - const String& header(const char* name) const; - const String& header(const String& name) const { return header(name.c_str()); }; - -#ifdef ESP8266 - const String& header(const __FlashStringHelper* data) const; // get request header value by F(name) -#endif - - const String& header(size_t i) const; // get request header value by number - const String& headerName(size_t i) const; // get request header name by number - - String urlDecode(const String& text) const; -}; - -/* - * FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server) - * */ - -using ArRequestFilterFunction = std::function; - -bool ON_STA_FILTER(AsyncWebServerRequest* request); - -bool ON_AP_FILTER(AsyncWebServerRequest* request); - -/* - * REWRITE :: One instance can be handle any Request (done by the Server) - * */ - -class AsyncWebRewrite { - protected: - String _from; - String _toUrl; - String _params; - ArRequestFilterFunction _filter{nullptr}; - - public: - AsyncWebRewrite(const char* from, const char* to) : _from(from), _toUrl(to) { - int index = _toUrl.indexOf('?'); - if (index > 0) { - _params = _toUrl.substring(index + 1); - _toUrl = _toUrl.substring(0, index); - } - } - virtual ~AsyncWebRewrite() {} - AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { - _filter = fn; - return *this; - } - bool filter(AsyncWebServerRequest* request) const { return _filter == NULL || _filter(request); } - const String& from(void) const { return _from; } - const String& toUrl(void) const { return _toUrl; } - const String& params(void) const { return _params; } - virtual bool match(AsyncWebServerRequest* request) { return from() == request->url() && filter(request); } -}; - -/* - * HANDLER :: One instance can be attached to any Request (done by the Server) - * */ - -class AsyncWebHandler { - protected: - ArRequestFilterFunction _filter{nullptr}; - String _username; - String _password; - - public: - AsyncWebHandler() {} - AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { - _filter = fn; - return *this; - } - AsyncWebHandler& setAuthentication(const char* username, const char* password) { - _username = username; - _password = password; - return *this; - }; - AsyncWebHandler& setAuthentication(const String& username, const String& password) { - _username = username; - _password = password; - return *this; - }; - bool filter(AsyncWebServerRequest* request) { return _filter == NULL || _filter(request); } - virtual ~AsyncWebHandler() {} - 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 bool isRequestHandlerTrivial() { return true; } -}; - -/* - * RESPONSE :: One instance is created for each Request (attached by the Handler) - * */ - -typedef enum { - RESPONSE_SETUP, - RESPONSE_HEADERS, - RESPONSE_CONTENT, - RESPONSE_WAIT_ACK, - RESPONSE_END, - RESPONSE_FAILED -} WebResponseState; - -class AsyncWebServerResponse { - protected: - int _code; - std::list _headers; - String _contentType; - size_t _contentLength; - bool _sendContentLength; - bool _chunked; - size_t _headLength; - size_t _sentLength; - size_t _ackedLength; - size_t _writtenLength; - WebResponseState _state; - - public: -#ifndef ESP8266 - static const char* responseCodeToString(int code); -#else - static const __FlashStringHelper* responseCodeToString(int code); -#endif - - public: - AsyncWebServerResponse(); - virtual ~AsyncWebServerResponse(); - virtual void setCode(int code); - virtual void setContentLength(size_t len); - void setContentType(const String& type) { setContentType(type.c_str()); } - virtual void setContentType(const char* type); - virtual void addHeader(const char* name, const char* value); - void addHeader(const String& name, const String& value) { addHeader(name.c_str(), value.c_str()); } - virtual String _assembleHead(uint8_t version); - virtual bool _started() const; - virtual bool _finished() const; - virtual bool _failed() const; - virtual bool _sourceValid() const; - virtual void _respond(AsyncWebServerRequest* request); - virtual size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time); -}; - -/* - * SERVER :: One instance - * */ - -typedef std::function ArRequestHandlerFunction; -typedef std::function ArUploadHandlerFunction; -typedef std::function ArBodyHandlerFunction; - -class AsyncWebServer { - protected: - AsyncServer _server; - std::list> _rewrites; - std::list> _handlers; - AsyncCallbackWebHandler* _catchAllHandler; - - public: - AsyncWebServer(uint16_t port); - ~AsyncWebServer(); - - void begin(); - void end(); - -#if ASYNC_TCP_SSL_ENABLED - void onSslFileRequest(AcSSlFileHandler cb, void* arg); - void beginSecure(const char* cert, const char* private_key_file, const char* password); -#endif - - AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite); - - /** - * @brief (compat) Add url rewrite rule by pointer - * a deep copy of the pounter object will be created, - * it is up to user to manage further lifetime of the object in argument - * - * @param rewrite pointer to rewrite object to copy setting from - * @return AsyncWebRewrite& reference to a newly created rewrite rule - */ - AsyncWebRewrite& addRewrite(std::shared_ptr rewrite); - - /** - * @brief add url rewrite rule - * - * @param from - * @param to - * @return AsyncWebRewrite& - */ - AsyncWebRewrite& rewrite(const char* from, const char* to); - - /** - * @brief (compat) remove rewrite rule via referenced object - * this will NOT deallocate pointed object itself, internal rule with same from/to urls will be removed if any - * it's a compat method, better use `removeRewrite(const char* from, const char* to)` - * @param rewrite - * @return true - * @return false - */ - bool removeRewrite(AsyncWebRewrite* rewrite); - - /** - * @brief remove rewrite rule - * - * @param from - * @param to - * @return true - * @return false - */ - bool removeRewrite(const char* from, const char* to); - - AsyncWebHandler& addHandler(AsyncWebHandler* handler); - bool removeHandler(AsyncWebHandler* handler); - - AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest); - 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); - - AsyncStaticWebHandler& serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL); - - void onNotFound(ArRequestHandlerFunction fn); // called when handler is not assigned - void onFileUpload(ArUploadHandlerFunction fn); // handle file uploads - void onRequestBody(ArBodyHandlerFunction fn); // handle posts with plain body content (JSON often transmitted this way as a request) - - void reset(); // remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody - - void _handleDisconnect(AsyncWebServerRequest* request); - void _attachHandler(AsyncWebServerRequest* request); - void _rewriteRequest(AsyncWebServerRequest* request); -}; - -class DefaultHeaders { - using headers_t = std::list; - headers_t _headers; - - public: - DefaultHeaders() = default; - - using ConstIterator = headers_t::const_iterator; - - void addHeader(const String& name, const String& value) { - _headers.emplace_back(name, value); - } - - ConstIterator begin() const { return _headers.begin(); } - ConstIterator end() const { return _headers.end(); } - - DefaultHeaders(DefaultHeaders const&) = delete; - DefaultHeaders& operator=(DefaultHeaders const&) = delete; - - static DefaultHeaders& Instance() { - static DefaultHeaders instance; - return instance; - } -}; - -#include "AsyncEventSource.h" -#include "AsyncWebSocket.h" -#include "WebHandlerImpl.h" -#include "WebResponseImpl.h" - -#endif /* _AsyncWebServer_H_ */ diff --git a/lib/ESPAsyncWebServer/src/WebAuthentication.cpp b/lib/ESPAsyncWebServer/src/WebAuthentication.cpp deleted file mode 100644 index b5962fc..0000000 --- a/lib/ESPAsyncWebServer/src/WebAuthentication.cpp +++ /dev/null @@ -1,249 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "WebAuthentication.h" -#include -#if defined(ESP32) || defined(TARGET_RP2040) - #include -#else - #include "md5.h" -#endif -#include "literals.h" - -using namespace asyncsrv; - -// Basic Auth hash = base64("username:password") - -bool checkBasicAuthentication(const char* hash, const char* username, const char* password) { - if (username == NULL || password == NULL || hash == NULL) - return false; - - size_t toencodeLen = strlen(username) + strlen(password) + 1; - size_t encodedLen = base64_encode_expected_len(toencodeLen); - if (strlen(hash) != encodedLen) -// Fix from https://github.com/me-no-dev/ESPAsyncWebServer/issues/667 -#ifdef ARDUINO_ARCH_ESP32 - if (strlen(hash) != encodedLen) -#else - if (strlen(hash) != encodedLen - 1) -#endif - return false; - - char* toencode = new char[toencodeLen + 1]; - if (toencode == NULL) { - return false; - } - char* encoded = new char[base64_encode_expected_len(toencodeLen) + 1]; - if (encoded == NULL) { - delete[] toencode; - return false; - } - sprintf_P(toencode, PSTR("%s:%s"), username, password); - if (base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0) { - delete[] toencode; - delete[] encoded; - return true; - } - delete[] toencode; - delete[] encoded; - return false; -} - -static bool getMD5(uint8_t* data, uint16_t len, char* output) { // 33 bytes or more -#if defined(ESP32) || defined(TARGET_RP2040) - MD5Builder md5; - md5.begin(); - md5.add(data, len); - md5.calculate(); - md5.getChars(output); -#else - md5_context_t _ctx; - - uint8_t* _buf = (uint8_t*)malloc(16); - if (_buf == NULL) - return false; - memset(_buf, 0x00, 16); - - MD5Init(&_ctx); - MD5Update(&_ctx, data, len); - MD5Final(_buf, &_ctx); - - for (uint8_t i = 0; i < 16; i++) { - sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]); - } - - free(_buf); -#endif - return true; -} - -static String genRandomMD5() { -#ifdef ESP8266 - uint32_t r = RANDOM_REG32; -#else - uint32_t r = rand(); -#endif - char* out = (char*)malloc(33); - if (out == NULL || !getMD5((uint8_t*)(&r), 4, out)) - return emptyString; - String res = String(out); - free(out); - return res; -} - -static String stringMD5(const String& in) { - char* out = (char*)malloc(33); - if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) - return emptyString; - String res = String(out); - free(out); - return res; -} - -String generateDigestHash(const char* username, const char* password, const char* realm) { - if (username == NULL || password == NULL || realm == NULL) { - return emptyString; - } - char* out = (char*)malloc(33); - String res = String(username); - res += ':'; - res.concat(realm); - res += ':'; - String in = res; - in.concat(password); - if (out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) - return emptyString; - res.concat(out); - free(out); - return res; -} - -String requestDigestAuthentication(const char* realm) { - String header(T_realm__); - if (realm == NULL) - header.concat(T_asyncesp); - else - header.concat(realm); - header.concat(T_auth_nonce); - header.concat(genRandomMD5()); - header.concat(T__opaque); - header.concat(genRandomMD5()); - header += (char)0x22; // '"' - return header; -} - -#ifndef ESP8266 -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) -#else -bool checkDigestAuthentication(const char* header, const __FlashStringHelper* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri) -#endif -{ - if (username == NULL || password == NULL || header == NULL || method == NULL) { - // os_printf("AUTH FAIL: missing requred fields\n"); - return false; - } - - String myHeader(header); - int nextBreak = myHeader.indexOf(','); - if (nextBreak < 0) { - // os_printf("AUTH FAIL: no variables\n"); - return false; - } - - String myUsername; - String myRealm; - String myNonce; - String myUri; - String myResponse; - String myQop; - String myNc; - String myCnonce; - - myHeader += (char)0x2c; // ',' - myHeader += (char)0x20; // ' ' - do { - String avLine(myHeader.substring(0, nextBreak)); - avLine.trim(); - myHeader = myHeader.substring(nextBreak + 1); - nextBreak = myHeader.indexOf(','); - - int eqSign = avLine.indexOf('='); - if (eqSign < 0) { - // os_printf("AUTH FAIL: no = sign\n"); - return false; - } - String varName(avLine.substring(0, eqSign)); - avLine = avLine.substring(eqSign + 1); - if (avLine.startsWith(String('"'))) { - avLine = avLine.substring(1, avLine.length() - 1); - } - - if (varName.equals(T_username)) { - if (!avLine.equals(username)) { - // os_printf("AUTH FAIL: username\n"); - return false; - } - myUsername = avLine; - } else if (varName.equals(T_realm)) { - if (realm != NULL && !avLine.equals(realm)) { - // os_printf("AUTH FAIL: realm\n"); - return false; - } - myRealm = avLine; - } else if (varName.equals(T_nonce)) { - if (nonce != NULL && !avLine.equals(nonce)) { - // os_printf("AUTH FAIL: nonce\n"); - return false; - } - myNonce = avLine; - } else if (varName.equals(T_opaque)) { - if (opaque != NULL && !avLine.equals(opaque)) { - // os_printf("AUTH FAIL: opaque\n"); - return false; - } - } else if (varName.equals(T_uri)) { - if (uri != NULL && !avLine.equals(uri)) { - // os_printf("AUTH FAIL: uri\n"); - return false; - } - myUri = avLine; - } else if (varName.equals(T_response)) { - myResponse = avLine; - } else if (varName.equals(T_qop)) { - myQop = avLine; - } else if (varName.equals(T_nc)) { - myNc = avLine; - } else if (varName.equals(T_cnonce)) { - myCnonce = avLine; - } - } while (nextBreak > 0); - - String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + password); - String ha2 = String(method) + ':' + myUri; - String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + stringMD5(ha2); - - if (myResponse.equals(stringMD5(response))) { - // os_printf("AUTH SUCCESS\n"); - return true; - } - - // os_printf("AUTH FAIL: password\n"); - return false; -} diff --git a/lib/ESPAsyncWebServer/src/WebAuthentication.h b/lib/ESPAsyncWebServer/src/WebAuthentication.h deleted file mode 100644 index d519777..0000000 --- a/lib/ESPAsyncWebServer/src/WebAuthentication.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef WEB_AUTHENTICATION_H_ -#define WEB_AUTHENTICATION_H_ - -#include "Arduino.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); - -#ifdef ESP8266 -bool checkDigestAuthentication(const char* header, const __FlashStringHelper* method, const char* username, const char* password, const char* realm, bool passwordIsHash, const char* nonce, const char* opaque, const char* uri); -#endif - -// for storing hashed versions on the device that can be authenticated against -String generateDigestHash(const char* username, const char* password, const char* realm); - -#endif diff --git a/lib/ESPAsyncWebServer/src/WebHandlerImpl.h b/lib/ESPAsyncWebServer/src/WebHandlerImpl.h deleted file mode 100644 index 22757d7..0000000 --- a/lib/ESPAsyncWebServer/src/WebHandlerImpl.h +++ /dev/null @@ -1,155 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#ifndef ASYNCWEBSERVERHANDLERIMPL_H_ -#define ASYNCWEBSERVERHANDLERIMPL_H_ - -#include -#ifdef ASYNCWEBSERVER_REGEX - #include -#endif - -#include "stddef.h" -#include - -class AsyncStaticWebHandler : public AsyncWebHandler { - using File = fs::File; - using FS = fs::FS; - - private: - bool _getFile(AsyncWebServerRequest* request); - bool _fileExists(AsyncWebServerRequest* request, const String& path); - uint8_t _countBits(const uint8_t value) const; - - protected: - FS _fs; - String _uri; - String _path; - String _default_file; - String _cache_control; - String _last_modified; - AwsTemplateProcessor _callback; - bool _isDir; - bool _gzipFirst; - uint8_t _gzipStats; - - public: - AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control); - virtual bool canHandle(AsyncWebServerRequest* request) override final; - virtual void handleRequest(AsyncWebServerRequest* request) override final; - AsyncStaticWebHandler& setIsDir(bool isDir); - AsyncStaticWebHandler& setDefaultFile(const char* filename); - AsyncStaticWebHandler& setCacheControl(const char* cache_control); - AsyncStaticWebHandler& setLastModified(const char* last_modified); - AsyncStaticWebHandler& setLastModified(struct tm* last_modified); -#ifdef ESP8266 - AsyncStaticWebHandler& setLastModified(time_t last_modified); - AsyncStaticWebHandler& setLastModified(); // sets to current time. Make sure sntp is runing and time is updated -#endif - AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) { - _callback = newCallback; - return *this; - } -}; - -class AsyncCallbackWebHandler : public AsyncWebHandler { - private: - protected: - String _uri; - WebRequestMethodComposite _method; - ArRequestHandlerFunction _onRequest; - ArUploadHandlerFunction _onUpload; - ArBodyHandlerFunction _onBody; - bool _isRegex; - - public: - AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} - void setUri(const String& uri) { - _uri = uri; - _isRegex = uri.startsWith("^") && uri.endsWith("$"); - } - void setMethod(WebRequestMethodComposite method) { _method = method; } - void onRequest(ArRequestHandlerFunction fn) { _onRequest = fn; } - void onUpload(ArUploadHandlerFunction fn) { _onUpload = fn; } - void onBody(ArBodyHandlerFunction fn) { _onBody = fn; } - - virtual bool canHandle(AsyncWebServerRequest* request) override final { - - if (!_onRequest) - return false; - - if (!(_method & request->method())) - return false; - -#ifdef ASYNCWEBSERVER_REGEX - if (_isRegex) { - std::regex pattern(_uri.c_str()); - std::smatch matches; - std::string s(request->url().c_str()); - if (std::regex_search(s, matches, pattern)) { - for (size_t i = 1; i < matches.size(); ++i) { // start from 1 - request->_addPathParam(matches[i].str().c_str()); - } - } else { - return false; - } - } else -#endif - if (_uri.length() && _uri.startsWith("/*.")) { - String uriTemplate = String(_uri); - uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf(".")); - if (!request->url().endsWith(uriTemplate)) - return false; - } else if (_uri.length() && _uri.endsWith("*")) { - String uriTemplate = String(_uri); - uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1); - if (!request->url().startsWith(uriTemplate)) - return false; - } else if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) - return false; - - request->addInterestingHeader("ANY"); - return true; - } - - virtual void handleRequest(AsyncWebServerRequest* request) override final { - if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) - return request->requestAuthentication(); - if (_onRequest) - _onRequest(request); - else - request->send(500); - } - virtual void handleUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) override final { - if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) - return request->requestAuthentication(); - if (_onUpload) - _onUpload(request, filename, index, data, len, final); - } - virtual void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final { - if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) - return request->requestAuthentication(); - if (_onBody) - _onBody(request, data, len, index, total); - } - virtual bool isRequestHandlerTrivial() override final { return _onRequest ? false : true; } -}; - -#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */ diff --git a/lib/ESPAsyncWebServer/src/WebHandlers.cpp b/lib/ESPAsyncWebServer/src/WebHandlers.cpp deleted file mode 100644 index d904a39..0000000 --- a/lib/ESPAsyncWebServer/src/WebHandlers.cpp +++ /dev/null @@ -1,250 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "ESPAsyncWebServer.h" -#include "WebHandlerImpl.h" - -using namespace asyncsrv; - - -AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control) - : _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr) { - // Ensure leading '/' - if (_uri.length() == 0 || _uri[0] != '/') - _uri = String('/') + _uri; - if (_path.length() == 0 || _path[0] != '/') - _path = String('/') + _path; - - // If path ends with '/' we assume a hint that this is a directory to improve performance. - // However - if it does not end with '/' we, can't assume a file, path can still be a directory. - _isDir = _path[_path.length() - 1] == '/'; - - // Remove the trailing '/' so we can handle default file - // Notice that root will be "" not "/" - if (_uri[_uri.length() - 1] == '/') - _uri = _uri.substring(0, _uri.length() - 1); - if (_path[_path.length() - 1] == '/') - _path = _path.substring(0, _path.length() - 1); - - // Reset stats - _gzipFirst = false; - _gzipStats = 0xF8; -} - -AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir) { - _isDir = isDir; - return *this; -} - -AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename) { - _default_file = String(filename); - return *this; -} - -AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control) { - _cache_control = String(cache_control); - return *this; -} - -AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified) { - _last_modified = last_modified; - return *this; -} - -AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified) { - auto formatP = PSTR("%a, %d %b %Y %H:%M:%S %Z"); - char format[strlen_P(formatP) + 1]; - strcpy_P(format, formatP); - - char result[30]; - strftime(result, sizeof(result), format, last_modified); - return setLastModified((const char*)result); -} - -#ifdef ESP8266 -AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified) { - return setLastModified((struct tm*)gmtime(&last_modified)); -} - -AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified() { - time_t last_modified; - if (time(&last_modified) == 0) // time is not yet set - return *this; - return setLastModified(last_modified); -} -#endif -bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest* request) { - if (request->method() != HTTP_GET || !request->url().startsWith(_uri) || !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP)) { - return false; - } - if (_getFile(request)) { - // We interested in "If-Modified-Since" header to check if file was modified - if (_last_modified.length()) - request->addInterestingHeader(F("If-Modified-Since")); - - if (_cache_control.length()) - request->addInterestingHeader(F("If-None-Match")); - - return true; - } - - return false; -} - -bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest* request) { - // Remove the found uri - String path = request->url().substring(_uri.length()); - - // We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/' - bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length() - 1] == '/'); - - path = _path + path; - - // Do we have a file or .gz file - if (!canSkipFileCheck && _fileExists(request, path)) - return true; - - // Can't handle if not default file - if (_default_file.length() == 0) - return false; - - // Try to add default file, ensure there is a trailing '/' ot the path. - if (path.length() == 0 || path[path.length() - 1] != '/') - path += String('/'); - path += _default_file; - - return _fileExists(request, path); -} - -#ifdef ESP32 - #define FILE_IS_REAL(f) (f == true && !f.isDirectory()) -#else - #define FILE_IS_REAL(f) (f == true) -#endif - -bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest* request, const String& path) { - bool fileFound = false; - bool gzipFound = false; - - String gzip = path + F(".gz"); - - if (_gzipFirst) { - if (_fs.exists(gzip)) { - request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read); - gzipFound = FILE_IS_REAL(request->_tempFile); - } - if (!gzipFound) { - if (_fs.exists(path)) { - request->_tempFile = _fs.open(path, fs::FileOpenMode::read); - fileFound = FILE_IS_REAL(request->_tempFile); - } - } - } else { - if (_fs.exists(path)) { - request->_tempFile = _fs.open(path, fs::FileOpenMode::read); - fileFound = FILE_IS_REAL(request->_tempFile); - } - if (!fileFound) { - if (_fs.exists(gzip)) { - request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read); - gzipFound = FILE_IS_REAL(request->_tempFile); - } - } - } - - bool found = fileFound || gzipFound; - - if (found) { - // Extract the file name from the path and keep it in _tempObject - size_t pathLen = path.length(); - char* _tempPath = (char*)malloc(pathLen + 1); - snprintf_P(_tempPath, pathLen + 1, PSTR("%s"), path.c_str()); - request->_tempObject = (void*)_tempPath; - - // Calculate gzip statistic - _gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); - if (_gzipStats == 0x00) - _gzipFirst = false; // All files are not gzip - else if (_gzipStats == 0xFF) - _gzipFirst = true; // All files are gzip - else - _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first - } - - return found; -} - -uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const { - uint8_t w = value; - uint8_t n; - for (n = 0; w != 0; n++) - w &= w - 1; - return n; -} - -void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) { - // Get the filename from request->_tempObject and free it - String filename = String((char*)request->_tempObject); - free(request->_tempObject); - request->_tempObject = NULL; - if ((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) - return request->requestAuthentication(); - - if (request->_tempFile == true) { - time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS) - // set etag to lastmod timestamp if available, otherwise to size - String etag; - if (lw) { - setLastModified(gmtime(&lw)); -#if defined(TARGET_RP2040) - // time_t == long long int - const size_t len = 1 + 8 * sizeof(time_t); - char buf[len]; - char* ret = lltoa(lw, buf, len, 10); - etag = ret ? String(ret) : String(request->_tempFile.size()); -#else - etag = String(lw); -#endif - } else { - etag = String(request->_tempFile.size()); - } - if (_last_modified.length() && _last_modified == request->header(T_IMS)) { - request->_tempFile.close(); - request->send(304); // Not modified - } else if (_cache_control.length() && request->hasHeader(T_INM) && request->header(T_INM).equals(etag)) { - request->_tempFile.close(); - AsyncWebServerResponse* response = new AsyncBasicResponse(304); // Not modified - response->addHeader(T_Cache_Control, _cache_control.c_str()); - response->addHeader(T_ETag, etag.c_str()); - request->send(response); - } else { - AsyncWebServerResponse* response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback); - if (_last_modified.length()) - response->addHeader(T_Last_Modified, _last_modified.c_str()); - if (_cache_control.length()) { - response->addHeader(T_Cache_Control, _cache_control.c_str()); - response->addHeader(T_ETag, etag.c_str()); - } - request->send(response); - } - } else { - request->send(404); - } -} diff --git a/lib/ESPAsyncWebServer/src/WebRequest.cpp b/lib/ESPAsyncWebServer/src/WebRequest.cpp deleted file mode 100644 index 95fda7e..0000000 --- a/lib/ESPAsyncWebServer/src/WebRequest.cpp +++ /dev/null @@ -1,985 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "ESPAsyncWebServer.h" -#include "WebAuthentication.h" -#include "WebResponseImpl.h" -#include "literals.h" -#include - -#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '=')) - -using namespace asyncsrv; - -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(), _authorization(), _reqconntype(RCT_HTTP), _isDigest(false), _isMultipart(false), _isPlainPost(false), _expectingContinue(false), _contentLength(0), _parsedLength(0), _multiParseState(0), _boundaryPosition(0), _itemStartIndex(0), _itemSize(0), _itemName(), _itemFilename(), _itemType(), _itemValue(), _itemBuffer(0), _itemBufferIndex(0), _itemIsFile(false), _tempObject(NULL) { - c->onError([](void* r, AsyncClient* c, int8_t error) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this); - c->onAck([](void* r, AsyncClient* c, size_t len, uint32_t time) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this); - c->onDisconnect([](void* r, AsyncClient* c) { AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onDisconnect(); delete c; }, this); - c->onTimeout([](void* r, AsyncClient* c, uint32_t time) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this); - c->onData([](void* r, AsyncClient* c, void* buf, size_t len) { (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this); - c->onPoll([](void* r, AsyncClient* c) { (void)c; AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this); -} - -AsyncWebServerRequest::~AsyncWebServerRequest() { - _headers.clear(); - - _pathParams.clear(); - - _interestingHeaders.clear(); - - if (_response != NULL) { - delete _response; - } - - if (_tempObject != NULL) { - free(_tempObject); - } - - if (_tempFile) { - _tempFile.close(); - } - - if (_itemBuffer) { - free(_itemBuffer); - } -} - -void AsyncWebServerRequest::_onData(void* buf, size_t len) { - size_t i = 0; - while (true) { - - if (_parseState < PARSE_REQ_BODY) { - // Find new line in buf - char* str = (char*)buf; - for (i = 0; i < len; i++) { - if (str[i] == '\n') { - break; - } - } - if (i == len) { // No new line, just add the buffer in _temp - char ch = str[len - 1]; - str[len - 1] = 0; - _temp.reserve(_temp.length() + len); - _temp.concat(str); - _temp.concat(ch); - } else { // Found new line - extract it and parse - str[i] = 0; // Terminate the string at the end of the line. - _temp.concat(str); - _temp.trim(); - _parseLine(); - if (++i < len) { - // Still have more buffer to process - buf = str + i; - len -= i; - continue; - } - } - } else if (_parseState == PARSE_REQ_BODY) { - // A handler should be already attached at this point in _parseLine function. - // If handler does nothing (_onRequest is NULL), we don't need to really parse the body. - const bool needParse = _handler && !_handler->isRequestHandlerTrivial(); - if (_isMultipart) { - if (needParse) { - size_t i; - for (i = 0; i < len; i++) { - _parseMultipartPostByte(((uint8_t*)buf)[i], i == len - 1); - _parsedLength++; - } - } else - _parsedLength += len; - } else { - if (_parsedLength == 0) { - if (_contentType.startsWith(T_app_xform_urlencoded)) { - _isPlainPost = true; - } else if (_contentType == T_text_plain && __is_param_char(((char*)buf)[0])) { - size_t i = 0; - while (i < len && __is_param_char(((char*)buf)[i++])) - ; - if (i < len && ((char*)buf)[i - 1] == '=') { - _isPlainPost = true; - } - } - } - if (!_isPlainPost) { - // check if authenticated before calling the body - if (_handler) - _handler->handleBody(this, (uint8_t*)buf, len, _parsedLength, _contentLength); - _parsedLength += len; - } else if (needParse) { - size_t i; - for (i = 0; i < len; i++) { - _parsedLength++; - _parsePlainPostChar(((uint8_t*)buf)[i]); - } - } else { - _parsedLength += len; - } - } - if (_parsedLength == _contentLength) { - _parseState = PARSE_REQ_END; - // check if authenticated before calling handleRequest and request auth instead - if (_handler) - _handler->handleRequest(this); - else - send(501); - } - } - break; - } -} - -void AsyncWebServerRequest::_removeNotInterestingHeaders() { - if (std::any_of(std::begin(_interestingHeaders), std::end(_interestingHeaders), [](const String& str) { return str.equalsIgnoreCase(T_ANY); })) - return; // nothing to do - - for (auto iter = std::begin(_headers); iter != std::end(_headers);) { - const auto name = iter->name(); - - if (std::none_of(std::begin(_interestingHeaders), std::end(_interestingHeaders), [&name](const String& str) { return str.equalsIgnoreCase(name); })) - iter = _headers.erase(iter); - else - iter++; - } -} - -void AsyncWebServerRequest::_onPoll() { - // os_printf("p\n"); - if (_response != NULL && _client != NULL && _client->canSend()) { - if (!_response->_finished()) { - _response->_ack(this, 0, 0); - } else { - AsyncWebServerResponse* r = _response; - _response = NULL; - delete r; - - _client->close(); - } - } -} - -void AsyncWebServerRequest::_onAck(size_t len, uint32_t time) { - // os_printf("a:%u:%u\n", len, time); - if (_response != NULL) { - if (!_response->_finished()) { - _response->_ack(this, len, time); - } else if (_response->_finished()) { - AsyncWebServerResponse* r = _response; - _response = NULL; - delete r; - - _client->close(); - } - } -} - -void AsyncWebServerRequest::_onError(int8_t error) { - (void)error; -} - -void AsyncWebServerRequest::_onTimeout(uint32_t time) { - (void)time; - // os_printf("TIMEOUT: %u, state: %s\n", time, _client->stateToString()); - _client->close(); -} - -void AsyncWebServerRequest::onDisconnect(ArDisconnectHandler fn) { - _onDisconnectfn = fn; -} - -void AsyncWebServerRequest::_onDisconnect() { - // os_printf("d\n"); - if (_onDisconnectfn) { - _onDisconnectfn(); - } - _server->_handleDisconnect(this); -} - -void AsyncWebServerRequest::_addPathParam(const char* p) { - _pathParams.emplace_back(p); -} - -void AsyncWebServerRequest::_addGetParams(const String& params) { - size_t start = 0; - while (start < params.length()) { - int end = params.indexOf('&', start); - if (end < 0) - end = params.length(); - int equal = params.indexOf('=', start); - if (equal < 0 || equal > end) - equal = end; - String name(params.substring(start, equal)); - String value(equal + 1 < end ? params.substring(equal + 1, end) : String()); - _params.emplace_back(urlDecode(name), urlDecode(value)); - start = end + 1; - } -} - -bool AsyncWebServerRequest::_parseReqHead() { - // Split the head into method, url and version - int index = _temp.indexOf(' '); - String m = _temp.substring(0, index); - index = _temp.indexOf(' ', index + 1); - String u = _temp.substring(m.length() + 1, index); - _temp = _temp.substring(index + 1); - - if (m == T_GET) { - _method = HTTP_GET; - } else if (m == T_POST) { - _method = HTTP_POST; - } else if (m == T_DELETE) { - _method = HTTP_DELETE; - } else if (m == T_PUT) { - _method = HTTP_PUT; - } else if (m == T_PATCH) { - _method = HTTP_PATCH; - } else if (m == T_HEAD) { - _method = HTTP_HEAD; - } else if (m == T_OPTIONS) { - _method = HTTP_OPTIONS; - } - - String g; - index = u.indexOf('?'); - if (index > 0) { - g = u.substring(index + 1); - u = u.substring(0, index); - } - _url = urlDecode(u); - _addGetParams(g); - - if (!_temp.startsWith(T_HTTP_1_0)) - _version = 1; - - _temp = emptyString; - return true; -} - -bool AsyncWebServerRequest::_parseReqHeader() { - int index = _temp.indexOf(':'); - if (index) { - String name(_temp.substring(0, index)); - String value(_temp.substring(index + 2)); - if (name.equalsIgnoreCase(T_Host)) { - _host = value; - } else if (name.equalsIgnoreCase(T_Content_Type)) { - _contentType = value.substring(0, value.indexOf(';')); - if (value.startsWith(T_MULTIPART_)) { - _boundary = value.substring(value.indexOf('=') + 1); - _boundary.replace(String('"'), String()); - _isMultipart = true; - } - } else if (name.equalsIgnoreCase(T_Content_Length)) { - _contentLength = atoi(value.c_str()); - } else if (name.equalsIgnoreCase(T_EXPECT) && value == T_100_CONTINUE) { - _expectingContinue = true; - } else if (name.equalsIgnoreCase(T_AUTH)) { - if (value.length() > 5 && value.substring(0, 5).equalsIgnoreCase(T_BASIC)) { - _authorization = value.substring(6); - } else if (value.length() > 6 && value.substring(0, 6).equalsIgnoreCase(T_DIGEST)) { - _isDigest = true; - _authorization = value.substring(7); - } - } else { - if (name.equalsIgnoreCase(T_UPGRADE) && value.equalsIgnoreCase(T_WS)) { - // WebSocket request can be uniquely identified by header: [Upgrade: websocket] - _reqconntype = RCT_WS; - } else if (name.equalsIgnoreCase(T_ACCEPT)) { - String lowcase(value); - lowcase.toLowerCase(); -#ifndef ESP8266 - const char* substr = std::strstr(lowcase.c_str(), T_text_event_stream); -#else - const char* substr = std::strstr(lowcase.c_str(), String(T_text_event_stream).c_str()); -#endif - if (substr != NULL) { - // WebEvent request can be uniquely identified by header: [Accept: text/event-stream] - _reqconntype = RCT_EVENT; - } - } - } - _headers.emplace_back(name, value); - } -#ifndef TARGET_RP2040 - _temp.clear(); -#else - // Ancient PRI core does not have String::clear() method 8-() - _temp = emptyString; -#endif - return true; -} - -void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) { - if (data && (char)data != '&') - _temp += (char)data; - if (!data || (char)data == '&' || _parsedLength == _contentLength) { - String name(T_BODY); - String value(_temp); - if (!(_temp.charAt(0) == '{') && !(_temp.charAt(0) == '[') && _temp.indexOf('=') > 0) { - name = _temp.substring(0, _temp.indexOf('=')); - value = _temp.substring(_temp.indexOf('=') + 1); - } - _params.emplace_back(urlDecode(name), urlDecode(value), true); - -#ifndef TARGET_RP2040 - _temp.clear(); -#else - // Ancient PRI core does not have String::clear() method 8-() - _temp = emptyString; -#endif - } -} - -void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last) { - _itemBuffer[_itemBufferIndex++] = data; - - if (last || _itemBufferIndex == 1460) { - // check if authenticated before calling the upload - if (_handler) - _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false); - _itemBufferIndex = 0; - } -} - -enum { - EXPECT_BOUNDARY, - PARSE_HEADERS, - WAIT_FOR_RETURN1, - EXPECT_FEED1, - EXPECT_DASH1, - EXPECT_DASH2, - BOUNDARY_OR_DATA, - DASH3_OR_RETURN2, - EXPECT_FEED2, - PARSING_FINISHED, - PARSE_ERROR -}; - -void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) { -#define itemWriteByte(b) \ - do { \ - _itemSize++; \ - if (_itemIsFile) \ - _handleUploadByte(b, last); \ - else \ - _itemValue += (char)(b); \ - } while (0) - - if (!_parsedLength) { - _multiParseState = EXPECT_BOUNDARY; - _temp = emptyString; - _itemName = emptyString; - _itemFilename = emptyString; - _itemType = emptyString; - } - - if (_multiParseState == WAIT_FOR_RETURN1) { - if (data != '\r') { - itemWriteByte(data); - } else { - _multiParseState = EXPECT_FEED1; - } - } else if (_multiParseState == EXPECT_BOUNDARY) { - if (_parsedLength < 2 && data != '-') { - _multiParseState = PARSE_ERROR; - return; - } else if (_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data) { - _multiParseState = PARSE_ERROR; - return; - } else if (_parsedLength - 2 == _boundary.length() && data != '\r') { - _multiParseState = PARSE_ERROR; - return; - } else if (_parsedLength - 3 == _boundary.length()) { - if (data != '\n') { - _multiParseState = PARSE_ERROR; - return; - } - _multiParseState = PARSE_HEADERS; - _itemIsFile = false; - } - } else if (_multiParseState == PARSE_HEADERS) { - if ((char)data != '\r' && (char)data != '\n') - _temp += (char)data; - if ((char)data == '\n') { - if (_temp.length()) { - if (_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase(T_Content_Type)) { - _itemType = _temp.substring(14); - _itemIsFile = true; - } else if (_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase(T_Content_Disposition)) { - _temp = _temp.substring(_temp.indexOf(';') + 2); - while (_temp.indexOf(';') > 0) { - String name = _temp.substring(0, _temp.indexOf('=')); - String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1); - if (name == T_name) { - _itemName = nameVal; - } else if (name == T_filename) { - _itemFilename = nameVal; - _itemIsFile = true; - } - _temp = _temp.substring(_temp.indexOf(';') + 2); - } - String name = _temp.substring(0, _temp.indexOf('=')); - String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1); - if (name == T_name) { - _itemName = nameVal; - } else if (name == T_filename) { - _itemFilename = nameVal; - _itemIsFile = true; - } - } - _temp = emptyString; - } else { - _multiParseState = WAIT_FOR_RETURN1; - // value starts from here - _itemSize = 0; - _itemStartIndex = _parsedLength; - _itemValue = emptyString; - if (_itemIsFile) { - if (_itemBuffer) - free(_itemBuffer); - _itemBuffer = (uint8_t*)malloc(1460); - if (_itemBuffer == NULL) { - _multiParseState = PARSE_ERROR; - return; - } - _itemBufferIndex = 0; - } - } - } - } else if (_multiParseState == EXPECT_FEED1) { - if (data != '\n') { - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); - _parseMultipartPostByte(data, last); - } else { - _multiParseState = EXPECT_DASH1; - } - } else if (_multiParseState == EXPECT_DASH1) { - if (data != '-') { - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); - itemWriteByte('\n'); - _parseMultipartPostByte(data, last); - } else { - _multiParseState = EXPECT_DASH2; - } - } else if (_multiParseState == EXPECT_DASH2) { - if (data != '-') { - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); - itemWriteByte('\n'); - itemWriteByte('-'); - _parseMultipartPostByte(data, last); - } else { - _multiParseState = BOUNDARY_OR_DATA; - _boundaryPosition = 0; - } - } else if (_multiParseState == BOUNDARY_OR_DATA) { - if (_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data) { - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); - itemWriteByte('\n'); - itemWriteByte('-'); - itemWriteByte('-'); - uint8_t i; - for (i = 0; i < _boundaryPosition; i++) - itemWriteByte(_boundary.c_str()[i]); - _parseMultipartPostByte(data, last); - } else if (_boundaryPosition == _boundary.length() - 1) { - _multiParseState = DASH3_OR_RETURN2; - if (!_itemIsFile) { - _params.emplace_back(_itemName, _itemValue, true); - } else { - if (_itemSize) { - // check if authenticated before calling the upload - if (_handler) - _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true); - _itemBufferIndex = 0; - _params.emplace_back(_itemName, _itemFilename, true, true, _itemSize); - } - free(_itemBuffer); - _itemBuffer = NULL; - } - - } else { - _boundaryPosition++; - } - } else if (_multiParseState == DASH3_OR_RETURN2) { - if (data == '-' && (_contentLength - _parsedLength - 4) != 0) { - // os_printf("ERROR: The parser got to the end of the POST but is expecting %u bytes more!\nDrop an issue so we can have more info on the matter!\n", _contentLength - _parsedLength - 4); - _contentLength = _parsedLength + 4; // lets close the request gracefully - } - if (data == '\r') { - _multiParseState = EXPECT_FEED2; - } else if (data == '-' && _contentLength == (_parsedLength + 4)) { - _multiParseState = PARSING_FINISHED; - } else { - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); - itemWriteByte('\n'); - itemWriteByte('-'); - itemWriteByte('-'); - uint8_t i; - for (i = 0; i < _boundary.length(); i++) - itemWriteByte(_boundary.c_str()[i]); - _parseMultipartPostByte(data, last); - } - } else if (_multiParseState == EXPECT_FEED2) { - if (data == '\n') { - _multiParseState = PARSE_HEADERS; - _itemIsFile = false; - } else { - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); - itemWriteByte('\n'); - itemWriteByte('-'); - itemWriteByte('-'); - uint8_t i; - for (i = 0; i < _boundary.length(); i++) - itemWriteByte(_boundary.c_str()[i]); - itemWriteByte('\r'); - _parseMultipartPostByte(data, last); - } - } -} - -void AsyncWebServerRequest::_parseLine() { - if (_parseState == PARSE_REQ_START) { - if (!_temp.length()) { - _parseState = PARSE_REQ_FAIL; - _client->close(); - } else { - _parseReqHead(); - _parseState = PARSE_REQ_HEADERS; - } - return; - } - - if (_parseState == PARSE_REQ_HEADERS) { - if (!_temp.length()) { - // end of headers - _server->_rewriteRequest(this); - _server->_attachHandler(this); - _removeNotInterestingHeaders(); - if (_expectingContinue) { - String response(T_HTTP_100_CONT); - _client->write(response.c_str(), response.length()); - } - // check handler for authentication - if (_contentLength) { - _parseState = PARSE_REQ_BODY; - } else { - _parseState = PARSE_REQ_END; - if (_handler) - _handler->handleRequest(this); - else - send(501); - } - } else - _parseReqHeader(); - } -} - -size_t AsyncWebServerRequest::headers() const { - return _headers.size(); -} - -bool AsyncWebServerRequest::hasHeader(const char* name) const { - for (const auto& h : _headers) { - if (h.name().equalsIgnoreCase(name)) { - return true; - } - } - return false; -} - -#ifdef ESP8266 -bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper* data) const { - return hasHeader(String(data)); -} -#endif - -const AsyncWebHeader* AsyncWebServerRequest::getHeader(const char* name) const { - auto iter = std::find_if(std::begin(_headers), std::end(_headers), [&name](const AsyncWebHeader& header) { return header.name().equalsIgnoreCase(name); }); - - return (iter == std::end(_headers)) ? nullptr : &(*iter); -} - -#ifdef ESP8266 -const AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper* data) const { - PGM_P p = reinterpret_cast(data); - size_t n = strlen_P(p); - char* name = (char*)malloc(n + 1); - if (name) { - strcpy_P(name, p); - const AsyncWebHeader* result = getHeader(String(name)); - free(name); - return result; - } else { - return nullptr; - } -} -#endif - -const AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const { - if (num >= _headers.size()) - return nullptr; - return &(*std::next(_headers.cbegin(), num)); -} - -size_t AsyncWebServerRequest::params() const { - return _params.size(); -} - -bool AsyncWebServerRequest::hasParam(const char* name, bool post, bool file) const { - for (const auto& p : _params) { - if (p.name().equals(name) && p.isPost() == post && p.isFile() == file) { - return true; - } - } - return false; -} - -const AsyncWebParameter* AsyncWebServerRequest::getParam(const char* name, bool post, bool file) const { - for (const auto& p : _params) { - if (p.name() == name && p.isPost() == post && p.isFile() == file) { - return &p; - } - } - return nullptr; -} - -#ifdef ESP8266 -const AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelper* data, bool post, bool file) const { - return getParam(String(data), post, file); -} -#endif - -const AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const { - if (num >= _params.size()) - return nullptr; - return &(*std::next(_params.cbegin(), num)); -} - -void AsyncWebServerRequest::addInterestingHeader(const char* name) { - if (std::none_of(std::begin(_interestingHeaders), std::end(_interestingHeaders), [&name](const String& str) { return str.equalsIgnoreCase(name); })) - _interestingHeaders.emplace_back(name); -} - -AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(int code, const char* contentType, const char* content, AwsTemplateProcessor callback) { - if (callback) - return new AsyncProgmemResponse(code, contentType, (const uint8_t*)content, strlen(content), callback); - return new AsyncBasicResponse(code, contentType, content); -} - -AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) { - return new AsyncProgmemResponse(code, contentType, content, len, callback); -} - -AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(FS& fs, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) { - if (fs.exists(path) || (!download && fs.exists(path + T__gz))) - return new AsyncFileResponse(fs, path, contentType, download, callback); - return NULL; -} - -AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(File content, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) { - if (content == true) - return new AsyncFileResponse(content, path, contentType, download, callback); - return NULL; -} - -AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback) { - return new AsyncStreamResponse(stream, contentType, len, callback); -} - -AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) { - return new AsyncCallbackResponse(contentType, len, callback, templateCallback); -} - -AsyncWebServerResponse* AsyncWebServerRequest::beginChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) { - if (_version) - return new AsyncChunkedResponse(contentType, callback, templateCallback); - return new AsyncCallbackResponse(contentType, 0, callback, templateCallback); -} - -AsyncResponseStream* AsyncWebServerRequest::beginResponseStream(const char* contentType, size_t bufferSize) { - return new AsyncResponseStream(contentType, bufferSize); -} - -#ifdef ESP8266 -AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback) { - return new AsyncProgmemResponse(code, contentType, (const uint8_t*)content, strlen_P(content), callback); -} -#endif - -void AsyncWebServerRequest::send(AsyncWebServerResponse* response) { - _response = response; - if (_response == NULL) { - _client->close(true); - _onDisconnect(); - return; - } - if (!_response->_sourceValid()) { - delete response; - _response = NULL; - send(500); - } else { - _client->setRxTimeout(0); - _response->_respond(this); - } -} - -void AsyncWebServerRequest::redirect(const char* url) { - AsyncWebServerResponse* response = beginResponse(302); - response->addHeader(T_LOCATION, url); - send(response); -} - -bool AsyncWebServerRequest::authenticate(const char* username, const char* password, const char* realm, bool passwordIsHash) { - if (_authorization.length()) { - if (_isDigest) - return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL); - else if (!passwordIsHash) - return checkBasicAuthentication(_authorization.c_str(), username, password); - else - return _authorization.equals(password); - } - return false; -} - -bool AsyncWebServerRequest::authenticate(const char* hash) { - if (!_authorization.length() || hash == NULL) - return false; - - if (_isDigest) { - String hStr = String(hash); - int separator = hStr.indexOf(':'); - if (separator <= 0) - return false; - String username = hStr.substring(0, separator); - hStr = hStr.substring(separator + 1); - separator = hStr.indexOf(':'); - if (separator <= 0) - return false; - String realm = hStr.substring(0, separator); - hStr = hStr.substring(separator + 1); - return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL); - } - - return (_authorization.equals(hash)); -} - -void AsyncWebServerRequest::requestAuthentication(const char* realm, bool isDigest) { - AsyncWebServerResponse* r = beginResponse(401); - if (!isDigest && realm == NULL) { - r->addHeader(T_WWW_AUTH, T_BASIC_REALM_LOGIN_REQ); - } else if (!isDigest) { - String header(T_BASIC_REALM); - header.concat(realm); - header += '"'; - r->addHeader(T_WWW_AUTH, header.c_str()); - } else { - String header(T_DIGEST_); - header.concat(requestDigestAuthentication(realm)); - r->addHeader(T_WWW_AUTH, header.c_str()); - } - send(r); -} - -bool AsyncWebServerRequest::hasArg(const char* name) const { - for (const auto& arg : _params) { - if (arg.name() == name) { - return true; - } - } - return false; -} - -#ifdef ESP8266 -bool AsyncWebServerRequest::hasArg(const __FlashStringHelper* data) const { - return hasArg(String(data).c_str()); -} -#endif - -const String& AsyncWebServerRequest::arg(const char* name) const { - for (const auto& arg : _params) { - if (arg.name() == name) { - return arg.value(); - } - } - return emptyString; -} - -#ifdef ESP8266 -const String& AsyncWebServerRequest::arg(const __FlashStringHelper* data) const { - return arg(String(data).c_str()); -} -#endif - -const String& AsyncWebServerRequest::arg(size_t i) const { - return getParam(i)->value(); -} - -const String& AsyncWebServerRequest::argName(size_t i) const { - return getParam(i)->name(); -} - -const String& AsyncWebServerRequest::pathArg(size_t i) const { - return i < _pathParams.size() ? _pathParams[i] : emptyString; -} - -const String& AsyncWebServerRequest::header(const char* name) const { - const AsyncWebHeader* h = getHeader(name); - return h ? h->value() : emptyString; -} - -#ifdef ESP8266 -const String& AsyncWebServerRequest::header(const __FlashStringHelper* data) const { - return header(String(data).c_str()); -}; -#endif - -const String& AsyncWebServerRequest::header(size_t i) const { - const AsyncWebHeader* h = getHeader(i); - return h ? h->value() : emptyString; -} - -const String& AsyncWebServerRequest::headerName(size_t i) const { - const AsyncWebHeader* h = getHeader(i); - return h ? h->name() : emptyString; -} - -String AsyncWebServerRequest::urlDecode(const String& text) const { - char temp[] = "0x00"; - unsigned int len = text.length(); - unsigned int i = 0; - String decoded; - decoded.reserve(len); // Allocate the string internal buffer - never longer from source text - while (i < len) { - char decodedChar; - char encodedChar = text.charAt(i++); - if ((encodedChar == '%') && (i + 1 < len)) { - temp[2] = text.charAt(i++); - temp[3] = text.charAt(i++); - decodedChar = strtol(temp, NULL, 16); - } else if (encodedChar == '+') { - decodedChar = ' '; - } else { - decodedChar = encodedChar; // normal ascii char - } - decoded.concat(decodedChar); - } - return decoded; -} - -#ifndef ESP8266 -const char* AsyncWebServerRequest::methodToString() const { - if (_method == HTTP_ANY) - return T_ANY; - if (_method & HTTP_GET) - return T_GET; - if (_method & HTTP_POST) - return T_POST; - if (_method & HTTP_DELETE) - return T_DELETE; - if (_method & HTTP_PUT) - return T_PUT; - if (_method & HTTP_PATCH) - return T_PATCH; - if (_method & HTTP_HEAD) - return T_HEAD; - if (_method & HTTP_OPTIONS) - return T_OPTIONS; - return T_UNKNOWN; -} -#else // ESP8266 -const __FlashStringHelper* AsyncWebServerRequest::methodToString() const { - if (_method == HTTP_ANY) - return FPSTR(T_ANY); - if (_method & HTTP_GET) - return FPSTR(T_GET); - if (_method & HTTP_POST) - return FPSTR(T_POST); - if (_method & HTTP_DELETE) - return FPSTR(T_DELETE); - if (_method & HTTP_PUT) - return FPSTR(T_PUT); - if (_method & HTTP_PATCH) - return FPSTR(T_PATCH); - if (_method & HTTP_HEAD) - return FPSTR(T_HEAD); - if (_method & HTTP_OPTIONS) - return FPSTR(T_OPTIONS); - return FPSTR(T_UNKNOWN); -} -#endif // ESP8266 - -#ifndef ESP8266 -const char* AsyncWebServerRequest::requestedConnTypeToString() const { - switch (_reqconntype) { - case RCT_NOT_USED: - return T_RCT_NOT_USED; - case RCT_DEFAULT: - return T_RCT_DEFAULT; - case RCT_HTTP: - return T_RCT_HTTP; - case RCT_WS: - return T_RCT_WS; - case RCT_EVENT: - return T_RCT_EVENT; - default: - return T_ERROR; - } -} -#else // ESP8266 -const __FlashStringHelper* AsyncWebServerRequest::requestedConnTypeToString() const { - switch (_reqconntype) { - case RCT_NOT_USED: - return FPSTR(T_RCT_NOT_USED); - case RCT_DEFAULT: - return FPSTR(T_RCT_DEFAULT); - case RCT_HTTP: - return FPSTR(T_RCT_HTTP); - case RCT_WS: - return FPSTR(T_RCT_WS); - case RCT_EVENT: - return FPSTR(T_RCT_EVENT); - default: - return FPSTR(T_ERROR); - } -} -#endif // ESP8266 - -bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) { - bool res = false; - if ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) - res = true; - if ((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype)) - res = true; - if ((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype)) - res = true; - return res; -} diff --git a/lib/ESPAsyncWebServer/src/WebResponseImpl.h b/lib/ESPAsyncWebServer/src/WebResponseImpl.h deleted file mode 100644 index a6f71bb..0000000 --- a/lib/ESPAsyncWebServer/src/WebResponseImpl.h +++ /dev/null @@ -1,157 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_ -#define ASYNCWEBSERVERRESPONSEIMPL_H_ - -#ifdef Arduino_h - // arduino is not compatible with std::vector - #undef min - #undef max -#endif -#include -#include -#include "literals.h" - -// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max. - -class AsyncBasicResponse : public AsyncWebServerResponse { - private: - String _content; - - public: - explicit AsyncBasicResponse(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty); - AsyncBasicResponse(int code, const String& contentType, const String& content = emptyString) : AsyncBasicResponse(code, contentType.c_str(), content.c_str()) {} - void _respond(AsyncWebServerRequest* request); - size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time); - bool _sourceValid() const { return true; } -}; - -class AsyncAbstractResponse : public AsyncWebServerResponse { - private: - String _head; - // Data is inserted into cache at begin(). - // This is inefficient with vector, but if we use some other container, - // we won't be able to access it as contiguous array of bytes when reading from it, - // so by gaining performance in one place, we'll lose it in another. - std::vector _cache; - size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len); - size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen); - - protected: - AwsTemplateProcessor _callback; - - public: - AsyncAbstractResponse(AwsTemplateProcessor callback = nullptr); - void _respond(AsyncWebServerRequest* request); - size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time); - bool _sourceValid() const { return false; } - virtual size_t _fillBuffer(uint8_t* buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; } -}; - -#ifndef TEMPLATE_PLACEHOLDER - #define TEMPLATE_PLACEHOLDER '%' -#endif - -#define TEMPLATE_PARAM_NAME_LENGTH 32 -class AsyncFileResponse : public AsyncAbstractResponse { - using File = fs::File; - using FS = fs::FS; - - private: - File _content; - String _path; - void _setContentTypeFromPath(const String& path); - - public: - AsyncFileResponse(FS& fs, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr); - AsyncFileResponse(FS& fs, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callback = nullptr) : AsyncFileResponse(fs, path, contentType.c_str(), download, callback) {} - AsyncFileResponse(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr); - AsyncFileResponse(File content, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callack = nullptr) : AsyncFileResponse(content, path, contentType.c_str(), download, callack) {} - ~AsyncFileResponse(); - bool _sourceValid() const { return !!(_content); } - virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override; -}; - -class AsyncStreamResponse : public AsyncAbstractResponse { - private: - Stream* _content; - - public: - AsyncStreamResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback = nullptr); - AsyncStreamResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) : AsyncStreamResponse(stream, contentType.c_str(), len, callback) {} - bool _sourceValid() const { return !!(_content); } - virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override; -}; - -class AsyncCallbackResponse : public AsyncAbstractResponse { - private: - AwsResponseFiller _content; - size_t _filledLength; - - public: - AsyncCallbackResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) : AsyncCallbackResponse(contentType.c_str(), len, callback, templateCallback) {} - bool _sourceValid() const { return !!(_content); } - virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override; -}; - -class AsyncChunkedResponse : public AsyncAbstractResponse { - private: - AwsResponseFiller _content; - size_t _filledLength; - - public: - AsyncChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) : AsyncChunkedResponse(contentType.c_str(), callback, templateCallback) {} - bool _sourceValid() const { return !!(_content); } - virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override; -}; - -class AsyncProgmemResponse : public AsyncAbstractResponse { - private: - const uint8_t* _content; - size_t _readLength; - - public: - AsyncProgmemResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr); - AsyncProgmemResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) : AsyncProgmemResponse(code, contentType.c_str(), content, len, callback) {} - bool _sourceValid() const { return true; } - virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override; -}; - -class cbuf; - -class AsyncResponseStream : public AsyncAbstractResponse, public Print { - private: - std::unique_ptr _content; - - public: - AsyncResponseStream(const char* contentType, size_t bufferSize); - AsyncResponseStream(const String& contentType, size_t bufferSize) : AsyncResponseStream(contentType.c_str(), bufferSize) {} - ~AsyncResponseStream(); - 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_H_ */ diff --git a/lib/ESPAsyncWebServer/src/WebResponses.cpp b/lib/ESPAsyncWebServer/src/WebResponses.cpp deleted file mode 100644 index f1994b1..0000000 --- a/lib/ESPAsyncWebServer/src/WebResponses.cpp +++ /dev/null @@ -1,849 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "ESPAsyncWebServer.h" -#include "WebResponseImpl.h" -#include "cbuf.h" - -using namespace asyncsrv; - -// Since ESP8266 does not link memchr by default, here's its implementation. -void* memchr(void* ptr, int ch, size_t count) { - unsigned char* p = static_cast(ptr); - while (count--) - if (*p++ == static_cast(ch)) - return --p; - return nullptr; -} - -/* - * Abstract Response - * - */ - -#ifndef ESP8266 -const char* AsyncWebServerResponse::responseCodeToString(int code) { - switch (code) { - case 100: - return T_HTTP_CODE_100; - case 101: - return T_HTTP_CODE_101; - case 200: - return T_HTTP_CODE_200; - case 201: - return T_HTTP_CODE_201; - case 202: - return T_HTTP_CODE_202; - case 203: - return T_HTTP_CODE_203; - case 204: - return T_HTTP_CODE_204; - case 205: - return T_HTTP_CODE_205; - case 206: - return T_HTTP_CODE_206; - case 300: - return T_HTTP_CODE_300; - case 301: - return T_HTTP_CODE_301; - case 302: - return T_HTTP_CODE_302; - case 303: - return T_HTTP_CODE_303; - case 304: - return T_HTTP_CODE_304; - case 305: - return T_HTTP_CODE_305; - case 307: - return T_HTTP_CODE_307; - case 400: - return T_HTTP_CODE_400; - case 401: - return T_HTTP_CODE_401; - case 402: - return T_HTTP_CODE_402; - case 403: - return T_HTTP_CODE_403; - case 404: - return T_HTTP_CODE_404; - case 405: - return T_HTTP_CODE_405; - case 406: - return T_HTTP_CODE_406; - case 407: - return T_HTTP_CODE_407; - case 408: - return T_HTTP_CODE_408; - case 409: - return T_HTTP_CODE_409; - case 410: - return T_HTTP_CODE_410; - case 411: - return T_HTTP_CODE_411; - case 412: - return T_HTTP_CODE_412; - case 413: - return T_HTTP_CODE_413; - case 414: - return T_HTTP_CODE_414; - case 415: - return T_HTTP_CODE_415; - case 416: - return T_HTTP_CODE_416; - case 417: - return T_HTTP_CODE_417; - case 500: - return T_HTTP_CODE_500; - case 501: - return T_HTTP_CODE_501; - case 502: - return T_HTTP_CODE_502; - case 503: - return T_HTTP_CODE_503; - case 504: - return T_HTTP_CODE_504; - case 505: - return T_HTTP_CODE_505; - default: - return T_HTTP_CODE_ANY; - } -} -#else // ESP8266 -const __FlashStringHelper* AsyncWebServerResponse::responseCodeToString(int code) -{ - switch (code) { - case 100: - return FPSTR(T_HTTP_CODE_100); - case 101: - return FPSTR(T_HTTP_CODE_101); - case 200: - return FPSTR(T_HTTP_CODE_200); - case 201: - return FPSTR(T_HTTP_CODE_201); - case 202: - return FPSTR(T_HTTP_CODE_202); - case 203: - return FPSTR(T_HTTP_CODE_203); - case 204: - return FPSTR(T_HTTP_CODE_204); - case 205: - return FPSTR(T_HTTP_CODE_205); - case 206: - return FPSTR(T_HTTP_CODE_206); - case 300: - return FPSTR(T_HTTP_CODE_300); - case 301: - return FPSTR(T_HTTP_CODE_301); - case 302: - return FPSTR(T_HTTP_CODE_302); - case 303: - return FPSTR(T_HTTP_CODE_303); - case 304: - return FPSTR(T_HTTP_CODE_304); - case 305: - return FPSTR(T_HTTP_CODE_305); - case 307: - return FPSTR(T_HTTP_CODE_307); - case 400: - return FPSTR(T_HTTP_CODE_400); - case 401: - return FPSTR(T_HTTP_CODE_401); - case 402: - return FPSTR(T_HTTP_CODE_402); - case 403: - return FPSTR(T_HTTP_CODE_403); - case 404: - return FPSTR(T_HTTP_CODE_404); - case 405: - return FPSTR(T_HTTP_CODE_405); - case 406: - return FPSTR(T_HTTP_CODE_406); - case 407: - return FPSTR(T_HTTP_CODE_407); - case 408: - return FPSTR(T_HTTP_CODE_408); - case 409: - return FPSTR(T_HTTP_CODE_409); - case 410: - return FPSTR(T_HTTP_CODE_410); - case 411: - return FPSTR(T_HTTP_CODE_411); - case 412: - return FPSTR(T_HTTP_CODE_412); - case 413: - return FPSTR(T_HTTP_CODE_413); - case 414: - return FPSTR(T_HTTP_CODE_414); - case 415: - return FPSTR(T_HTTP_CODE_415); - case 416: - return FPSTR(T_HTTP_CODE_416); - case 417: - return FPSTR(T_HTTP_CODE_417); - case 500: - return FPSTR(T_HTTP_CODE_500); - case 501: - return FPSTR(T_HTTP_CODE_501); - case 502: - return FPSTR(T_HTTP_CODE_502); - case 503: - return FPSTR(T_HTTP_CODE_503); - case 504: - return FPSTR(T_HTTP_CODE_504); - case 505: - return FPSTR(T_HTTP_CODE_505); - default: - return FPSTR(T_HTTP_CODE_ANY); - } -} -#endif // ESP8266 - -AsyncWebServerResponse::AsyncWebServerResponse() - : _code(0), _contentType(), _contentLength(0), _sendContentLength(true), _chunked(false), _headLength(0), _sentLength(0), _ackedLength(0), _writtenLength(0), _state(RESPONSE_SETUP) { - for (const auto& header : DefaultHeaders::Instance()) { - _headers.emplace_back(header); - } -} - -AsyncWebServerResponse::~AsyncWebServerResponse() = default; - -void AsyncWebServerResponse::setCode(int code) { - if (_state == RESPONSE_SETUP) - _code = code; -} - -void AsyncWebServerResponse::setContentLength(size_t len) { - if (_state == RESPONSE_SETUP) - _contentLength = len; -} - -void AsyncWebServerResponse::setContentType(const char* type) { - if (_state == RESPONSE_SETUP) - _contentType = type; -} - -void AsyncWebServerResponse::addHeader(const char* name, const char* value) { - _headers.emplace_back(name, value); -} - -String AsyncWebServerResponse::_assembleHead(uint8_t version) { - if (version) { - addHeader(T_Accept_Ranges, T_none); - if (_chunked) - addHeader(Transfer_Encoding, T_chunked); - } - String out; - int bufSize = 300; - char buf[bufSize]; - -#ifndef ESP8266 - snprintf(buf, bufSize, "HTTP/1.%d %d %s\r\n", version, _code, responseCodeToString(_code)); -#else - snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, String(responseCodeToString(_code)).c_str()); -#endif - out.concat(buf); - - if (_sendContentLength) { - snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength); - out.concat(buf); - } - if (_contentType.length()) { - snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str()); - out.concat(buf); - } - - for (const auto& header : _headers) { - snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header.name().c_str(), header.value().c_str()); - out.concat(buf); - } - _headers.clear(); - - out.concat(T_rn); - _headLength = out.length(); - return out; -} - -bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; } -bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; } -bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; } -bool AsyncWebServerResponse::_sourceValid() const { return false; } -void AsyncWebServerResponse::_respond(AsyncWebServerRequest* request) { - _state = RESPONSE_END; - request->client()->close(); -} -size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) { - (void)request; - (void)len; - (void)time; - return 0; -} - -/* - * String/Code Response - * */ -AsyncBasicResponse::AsyncBasicResponse(int code, const char* contentType, const char* content) { - _code = code; - _content = content; - _contentType = contentType; - if (_content.length()) { - _contentLength = _content.length(); - if (!_contentType.length()) - _contentType = T_text_plain; - } - addHeader(T_Connection, T_close); -} - -void AsyncBasicResponse::_respond(AsyncWebServerRequest* request) { - _state = RESPONSE_HEADERS; - String out = _assembleHead(request->version()); - size_t outLen = out.length(); - size_t space = request->client()->space(); - if (!_contentLength && space >= outLen) { - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_WAIT_ACK; - } else if (_contentLength && space >= outLen + _contentLength) { - out += _content; - outLen += _contentLength; - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_WAIT_ACK; - } else if (space && space < outLen) { - String partial = out.substring(0, space); - _content = out.substring(space) + _content; - _contentLength += outLen - space; - _writtenLength += request->client()->write(partial.c_str(), partial.length()); - _state = RESPONSE_CONTENT; - } else if (space > outLen && space < (outLen + _contentLength)) { - size_t shift = space - outLen; - outLen += shift; - _sentLength += shift; - out += _content.substring(0, shift); - _content = _content.substring(shift); - _writtenLength += request->client()->write(out.c_str(), outLen); - _state = RESPONSE_CONTENT; - } else { - _content = out + _content; - _contentLength += outLen; - _state = RESPONSE_CONTENT; - } -} - -size_t AsyncBasicResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) { - (void)time; - _ackedLength += len; - if (_state == RESPONSE_CONTENT) { - size_t available = _contentLength - _sentLength; - size_t space = request->client()->space(); - // we can fit in this packet - if (space > available) { - _writtenLength += request->client()->write(_content.c_str(), available); - _content = emptyString; - _state = RESPONSE_WAIT_ACK; - return available; - } - // send some data, the rest on ack - String out = _content.substring(0, space); - _content = _content.substring(space); - _sentLength += space; - _writtenLength += request->client()->write(out.c_str(), space); - return space; - } else if (_state == RESPONSE_WAIT_ACK) { - if (_ackedLength >= _writtenLength) { - _state = RESPONSE_END; - } - } - return 0; -} - -/* - * Abstract Response - * */ - -AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback) : _callback(callback) { - // In case of template processing, we're unable to determine real response size - if (callback) { - _contentLength = 0; - _sendContentLength = false; - _chunked = true; - } -} - -void AsyncAbstractResponse::_respond(AsyncWebServerRequest* request) { - addHeader(T_Connection, T_close); - _head = _assembleHead(request->version()); - _state = RESPONSE_HEADERS; - _ack(request, 0, 0); -} - -size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, uint32_t time) { - (void)time; - if (!_sourceValid()) { - _state = RESPONSE_FAILED; - request->client()->close(); - return 0; - } - _ackedLength += len; - size_t space = request->client()->space(); - - size_t headLen = _head.length(); - if (_state == RESPONSE_HEADERS) { - if (space >= headLen) { - _state = RESPONSE_CONTENT; - space -= headLen; - } else { - String out = _head.substring(0, space); - _head = _head.substring(space); - _writtenLength += request->client()->write(out.c_str(), out.length()); - return out.length(); - } - } - - if (_state == RESPONSE_CONTENT) { - size_t outLen; - if (_chunked) { - if (space <= 8) { - return 0; - } - outLen = space; - } else if (!_sendContentLength) { - outLen = space; - } else { - outLen = ((_contentLength - _sentLength) > space) ? space : (_contentLength - _sentLength); - } - - uint8_t* buf = (uint8_t*)malloc(outLen + headLen); - if (!buf) { - // os_printf("_ack malloc %d failed\n", outLen+headLen); - return 0; - } - - if (headLen) { - memcpy(buf, _head.c_str(), _head.length()); - } - - size_t readLen = 0; - - if (_chunked) { - // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added. - // See RFC2616 sections 2, 3.6.1. - readLen = _fillBufferAndProcessTemplates(buf + headLen + 6, outLen - 8); - if (readLen == RESPONSE_TRY_AGAIN) { - free(buf); - return 0; - } - outLen = sprintf_P((char*)buf + headLen, PSTR("%x"), readLen) + headLen; - while (outLen < headLen + 4) - buf[outLen++] = ' '; - buf[outLen++] = '\r'; - buf[outLen++] = '\n'; - outLen += readLen; - buf[outLen++] = '\r'; - buf[outLen++] = '\n'; - } else { - readLen = _fillBufferAndProcessTemplates(buf + headLen, outLen); - if (readLen == RESPONSE_TRY_AGAIN) { - free(buf); - return 0; - } - outLen = readLen + headLen; - } - - if (headLen) { - _head = emptyString; - } - - if (outLen) { - _writtenLength += request->client()->write((const char*)buf, outLen); - } - - if (_chunked) { - _sentLength += readLen; - } else { - _sentLength += outLen - headLen; - } - - free(buf); - - if ((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)) { - _state = RESPONSE_WAIT_ACK; - } - return outLen; - - } else if (_state == RESPONSE_WAIT_ACK) { - if (!_sendContentLength || _ackedLength >= _writtenLength) { - _state = RESPONSE_END; - if (!_chunked && !_sendContentLength) - request->client()->close(true); - } - } - return 0; -} - -size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) { - // If we have something in cache, copy it to buffer - const size_t readFromCache = std::min(len, _cache.size()); - if (readFromCache) { - memcpy(data, _cache.data(), readFromCache); - _cache.erase(_cache.begin(), _cache.begin() + readFromCache); - } - // If we need to read more... - const size_t needFromFile = len - readFromCache; - const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile); - return readFromCache + readFromContent; -} - -size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) { - if (!_callback) - return _fillBuffer(data, len); - - const size_t originalLen = len; - len = _readDataFromCacheOrContent(data, len); - // Now we've read 'len' bytes, either from cache or from file - // Search for template placeholders - uint8_t* pTemplateStart = data; - while ((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1] - uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr; - // temporary buffer to hold parameter name - uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; - String paramName; - // If closing placeholder is found: - if (pTemplateEnd) { - // prepare argument to callback - const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1)); - if (paramNameLength) { - memcpy(buf, pTemplateStart + 1, paramNameLength); - buf[paramNameLength] = 0; - paramName = String(reinterpret_cast(buf)); - } else { // double percent sign encountered, this is single percent sign escaped. - // remove the 2nd percent sign - memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); - len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; - ++pTemplateStart; - } - } else if (&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data - memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart); - const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1)); - if (readFromCacheOrContent) { - pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); - if (pTemplateEnd) { - // prepare argument to callback - *pTemplateEnd = 0; - paramName = String(reinterpret_cast(buf)); - // Copy remaining read-ahead data into cache - _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); - pTemplateEnd = &data[len - 1]; - } else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position - { - // but first, store read file data in cache - _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); - ++pTemplateStart; - } - } else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position - ++pTemplateStart; - } else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position - ++pTemplateStart; - if (paramName.length()) { - // call callback and replace with result. - // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. - // Data after pTemplateEnd may need to be moved. - // The first byte of data after placeholder is located at pTemplateEnd + 1. - // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value). - const String paramValue(_callback(paramName)); - const char* pvstr = paramValue.c_str(); - const unsigned int pvlen = paramValue.length(); - const size_t numBytesCopied = std::min(pvlen, static_cast(&data[originalLen - 1] - pTemplateStart + 1)); - // make room for param value - // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store - if ((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) { - _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]); - // 2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end - memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied); - len = originalLen; // fix issue with truncated data, not sure if it has any side effects - } else if (pTemplateEnd + 1 != pTemplateStart + numBytesCopied) - // 2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit. - // Move the entire data after the placeholder - memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); - // 3. replace placeholder with actual value - memcpy(pTemplateStart, pvstr, numBytesCopied); - // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer) - if (numBytesCopied < pvlen) { - _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen); - } else if (pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text... - // there is some free room, fill it from cache - const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; - const size_t totalFreeRoom = originalLen - len + roomFreed; - len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed; - } else { // result is copied fully; it is longer than placeholder text - const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1; - len = std::min(len + roomTaken, originalLen); - } - } - } // while(pTemplateStart) - return len; -} - -/* - * File Response - * */ - -AsyncFileResponse::~AsyncFileResponse() { - if (_content) - _content.close(); -} - -void AsyncFileResponse::_setContentTypeFromPath(const String& path) { -#if HAVE_EXTERN_GET_Content_Type_FUNCTION - #ifndef ESP8266 - extern const char* getContentType(const String& path); - #else - extern const __FlashStringHelper* getContentType(const String& path); - #endif - _contentType = getContentType(path); -#else - if (path.endsWith(T__html)) - _contentType = T_text_html; - else if (path.endsWith(T__htm)) - _contentType = T_text_html; - else if (path.endsWith(T__css)) - _contentType = T_text_css; - else if (path.endsWith(T__json)) - _contentType = T_application_json; - else if (path.endsWith(T__js)) - _contentType = T_application_javascript; - else if (path.endsWith(T__png)) - _contentType = T_image_png; - else if (path.endsWith(T__gif)) - _contentType = T_image_gif; - else if (path.endsWith(T__jpg)) - _contentType = T_image_jpeg; - else if (path.endsWith(T__ico)) - _contentType = T_image_x_icon; - else if (path.endsWith(T__svg)) - _contentType = T_image_svg_xml; - else if (path.endsWith(T__eot)) - _contentType = T_font_eot; - else if (path.endsWith(T__woff)) - _contentType = T_font_woff; - else if (path.endsWith(T__woff2)) - _contentType = T_font_woff2; - else if (path.endsWith(T__ttf)) - _contentType = T_font_ttf; - else if (path.endsWith(T__xml)) - _contentType = T_text_xml; - else if (path.endsWith(T__pdf)) - _contentType = T_application_pdf; - else if (path.endsWith(T__zip)) - _contentType = T_application_zip; - else if (path.endsWith(T__gz)) - _contentType = T_application_x_gzip; - else - _contentType = T_text_plain; -#endif -} - -AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) { - _code = 200; - _path = path; - - if (!download && !fs.exists(_path) && fs.exists(_path + T__gz)) { - _path = _path + T__gz; - addHeader(T_Content_Encoding, T_gzip); - _callback = nullptr; // Unable to process zipped templates - _sendContentLength = true; - _chunked = false; - } - - _content = fs.open(_path, fs::FileOpenMode::read); - _contentLength = _content.size(); - - if (strlen(contentType) == 0) - _setContentTypeFromPath(path); - else - _contentType = contentType; - - int filenameStart = path.lastIndexOf('/') + 1; - char buf[26 + path.length() - filenameStart]; - char* filename = (char*)path.c_str() + filenameStart; - - if (download) { - // set filename and force download - snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename); - } else { - // set filename and force rendering - snprintf_P(buf, sizeof(buf), PSTR("inline")); - } - addHeader(T_Content_Disposition, buf); -} - -AsyncFileResponse::AsyncFileResponse(File content, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) { - _code = 200; - _path = path; - - if (!download && String(content.name()).endsWith(T__gz) && !path.endsWith(T__gz)) { - addHeader(T_Content_Encoding, T_gzip); - _callback = nullptr; // Unable to process gzipped templates - _sendContentLength = true; - _chunked = false; - } - - _content = content; - _contentLength = _content.size(); - - if (strlen(contentType) == 0) - _setContentTypeFromPath(path); - else - _contentType = contentType; - - int filenameStart = path.lastIndexOf('/') + 1; - char buf[26 + path.length() - filenameStart]; - char* filename = (char*)path.c_str() + filenameStart; - - if (download) { - snprintf_P(buf, sizeof(buf), PSTR("attachment; filename=\"%s\""), filename); - } else { - snprintf_P(buf, sizeof(buf), PSTR("inline")); - } - addHeader(T_Content_Disposition, buf); -} - -size_t AsyncFileResponse::_fillBuffer(uint8_t* data, size_t len) { - return _content.read(data, len); -} - -/* - * Stream Response - * */ - -AsyncStreamResponse::AsyncStreamResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) { - _code = 200; - _content = &stream; - _contentLength = len; - _contentType = contentType; -} - -size_t AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) { - size_t available = _content->available(); - size_t outLen = (available > len) ? len : available; - size_t i; - for (i = 0; i < outLen; i++) - data[i] = _content->read(); - return outLen; -} - -/* - * Callback Response - * */ - -AsyncCallbackResponse::AsyncCallbackResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) : AsyncAbstractResponse(templateCallback) { - _code = 200; - _content = callback; - _contentLength = len; - if (!len) - _sendContentLength = false; - _contentType = contentType; - _filledLength = 0; -} - -size_t AsyncCallbackResponse::_fillBuffer(uint8_t* data, size_t len) { - size_t ret = _content(data, len, _filledLength); - if (ret != RESPONSE_TRY_AGAIN) { - _filledLength += ret; - } - return ret; -} - -/* - * Chunked Response - * */ - -AsyncChunkedResponse::AsyncChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback) : AsyncAbstractResponse(processorCallback) { - _code = 200; - _content = callback; - _contentLength = 0; - _contentType = contentType; - _sendContentLength = false; - _chunked = true; - _filledLength = 0; -} - -size_t AsyncChunkedResponse::_fillBuffer(uint8_t* data, size_t len) { - size_t ret = _content(data, len, _filledLength); - if (ret != RESPONSE_TRY_AGAIN) { - _filledLength += ret; - } - return ret; -} - -/* - * Progmem Response - * */ - -AsyncProgmemResponse::AsyncProgmemResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) { - _code = code; - _content = content; - _contentType = contentType; - _contentLength = len; - _readLength = 0; -} - -size_t AsyncProgmemResponse::_fillBuffer(uint8_t* data, size_t len) { - size_t left = _contentLength - _readLength; - if (left > len) { - memcpy_P(data, _content + _readLength, len); - _readLength += len; - return len; - } - memcpy_P(data, _content + _readLength, left); - _readLength += left; - return left; -} - -/* - * Response Stream (You can print/write/printf to it, up to the contentLen bytes) - * */ - -AsyncResponseStream::AsyncResponseStream(const char* contentType, size_t bufferSize) { - _code = 200; - _contentLength = 0; - _contentType = contentType; - _content = std::unique_ptr(new cbuf(bufferSize)); // std::make_unique(bufferSize); -} - -AsyncResponseStream::~AsyncResponseStream() = default; - -size_t AsyncResponseStream::_fillBuffer(uint8_t* buf, size_t maxLen) { - return _content->read((char*)buf, maxLen); -} - -size_t AsyncResponseStream::write(const uint8_t* data, size_t len) { - if (_started()) - return 0; - - if (len > _content->room()) { - size_t needed = len - _content->room(); - _content->resizeAdd(needed); - } - size_t written = _content->write((const char*)data, len); - _contentLength += written; - return written; -} - -size_t AsyncResponseStream::write(uint8_t data) { - return write(&data, 1); -} diff --git a/lib/ESPAsyncWebServer/src/WebServer.cpp b/lib/ESPAsyncWebServer/src/WebServer.cpp deleted file mode 100644 index d7c9a02..0000000 --- a/lib/ESPAsyncWebServer/src/WebServer.cpp +++ /dev/null @@ -1,227 +0,0 @@ -/* - Asynchronous WebServer library for Espressif MCUs - - Copyright (c) 2016 Hristo Gochkov. All rights reserved. - This file is part of the esp8266 core for Arduino environment. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ -#include "ESPAsyncWebServer.h" -#include "WebHandlerImpl.h" - -using namespace asyncsrv; - -bool ON_STA_FILTER(AsyncWebServerRequest* request) { - #ifndef CONFIG_IDF_TARGET_ESP32H2 - return WiFi.localIP() == request->client()->localIP(); - #else - return false; - #endif -} - -bool ON_AP_FILTER(AsyncWebServerRequest* request) { - #ifndef CONFIG_IDF_TARGET_ESP32H2 - return WiFi.localIP() != request->client()->localIP(); - #else - return false; - #endif -} - -#ifndef HAVE_FS_FILE_OPEN_MODE -const char* fs::FileOpenMode::read = "r"; -const char* fs::FileOpenMode::write = "w"; -const char* fs::FileOpenMode::append = "a"; -#endif - -AsyncWebServer::AsyncWebServer(uint16_t port) - : _server(port) { - _catchAllHandler = new AsyncCallbackWebHandler(); - if (_catchAllHandler == NULL) - return; - _server.onClient([](void* s, AsyncClient* c) { - if (c == NULL) - return; - c->setRxTimeout(3); - AsyncWebServerRequest* r = new AsyncWebServerRequest((AsyncWebServer*)s, c); - if (r == NULL) { - c->close(true); - c->free(); - delete c; - } - }, - this); -} - -AsyncWebServer::~AsyncWebServer() { - reset(); - end(); - if (_catchAllHandler) - delete _catchAllHandler; -} - -AsyncWebRewrite& AsyncWebServer::addRewrite(std::shared_ptr rewrite) { - _rewrites.emplace_back(rewrite); - return *_rewrites.back().get(); -} - -AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite) { - _rewrites.emplace_back(rewrite); - return *_rewrites.back().get(); -} - -bool AsyncWebServer::removeRewrite(AsyncWebRewrite* rewrite) { - return removeRewrite(rewrite->from().c_str(), rewrite->toUrl().c_str()); -} - -bool AsyncWebServer::removeRewrite(const char* from, const char* to) { - for (auto r = _rewrites.begin(); r != _rewrites.end(); ++r) { - if (r->get()->from() == from && r->get()->toUrl() == to) { - _rewrites.erase(r); - return true; - } - } - return false; -} - -AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to) { - _rewrites.emplace_back(std::make_shared(from, to)); - return *_rewrites.back().get(); -} - -AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler) { - _handlers.emplace_back(handler); - return *(_handlers.back().get()); -} - -bool AsyncWebServer::removeHandler(AsyncWebHandler* handler) { - for (auto i = _handlers.begin(); i != _handlers.end(); ++i) { - if (i->get() == handler) { - _handlers.erase(i); - return true; - } - } - return false; -} - -void AsyncWebServer::begin() { - _server.setNoDelay(true); - _server.begin(); -} - -void AsyncWebServer::end() { - _server.end(); -} - -#if ASYNC_TCP_SSL_ENABLED -void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg) { - _server.onSslFileRequest(cb, arg); -} - -void AsyncWebServer::beginSecure(const char* cert, const char* key, const char* password) { - _server.beginSecure(cert, key, password); -} -#endif - -void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest* request) { - delete request; -} - -void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest* request) { - for (const auto& r : _rewrites) { - if (r->match(request)) { - request->_url = r->toUrl(); - request->_addGetParams(r->params()); - } - } -} - -void AsyncWebServer::_attachHandler(AsyncWebServerRequest* request) { - for (auto& h : _handlers) { - if (h->filter(request) && h->canHandle(request)) { - request->setHandler(h.get()); - return; - } - } - - request->addInterestingHeader(T_ANY); - request->setHandler(_catchAllHandler); -} - -AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody) { - AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); - handler->setUri(uri); - handler->setMethod(method); - handler->onRequest(onRequest); - handler->onUpload(onUpload); - handler->onBody(onBody); - addHandler(handler); - return *handler; -} - -AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload) { - AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); - handler->setUri(uri); - handler->setMethod(method); - handler->onRequest(onRequest); - handler->onUpload(onUpload); - addHandler(handler); - return *handler; -} - -AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest) { - AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); - handler->setUri(uri); - handler->setMethod(method); - handler->onRequest(onRequest); - addHandler(handler); - return *handler; -} - -AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest) { - AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); - handler->setUri(uri); - handler->onRequest(onRequest); - addHandler(handler); - return *handler; -} - -AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control) { - AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control); - addHandler(handler); - return *handler; -} - -void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn) { - _catchAllHandler->onRequest(fn); -} - -void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn) { - _catchAllHandler->onUpload(fn); -} - -void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn) { - _catchAllHandler->onBody(fn); -} - -void AsyncWebServer::reset() { - _rewrites.clear(); - _handlers.clear(); - - if (_catchAllHandler != NULL) { - _catchAllHandler->onRequest(NULL); - _catchAllHandler->onUpload(NULL); - _catchAllHandler->onBody(NULL); - } -} diff --git a/lib/ESPAsyncWebServer/src/literals.h b/lib/ESPAsyncWebServer/src/literals.h deleted file mode 100644 index 8a7293b..0000000 --- a/lib/ESPAsyncWebServer/src/literals.h +++ /dev/null @@ -1,345 +0,0 @@ -#pragma once - -namespace asyncsrv { - -static constexpr const char* empty = ""; - -#ifndef ESP8622 -static constexpr const char* T_100_CONTINUE = "100-continue"; -static constexpr const char* T_ACCEPT = "Accept"; -static constexpr const char* T_Accept_Ranges = "Accept-Ranges"; -static constexpr const char* T_app_xform_urlencoded = "application/x-www-form-urlencoded"; -static constexpr const char* T_AUTH = "Authorization"; -static constexpr const char* T_BASIC = "Basic"; -static constexpr const char* T_BASIC_REALM = "Basic realm=\""; -static constexpr const char* T_BASIC_REALM_LOGIN_REQ = "Basic realm=\"Login Required\""; -static constexpr const char* T_BODY = "body"; -static constexpr const char* T_Cache_Control = "Cache-Control"; -static constexpr const char* T_chunked = "chunked"; -static constexpr const char* T_close = "close"; -static constexpr const char* T_Connection = "Connection"; -static constexpr const char* T_Content_Disposition = "Content-Disposition"; -static constexpr const char* T_Content_Encoding = "Content-Encoding"; -static constexpr const char* T_Content_Length = "Content-Length"; -static constexpr const char* T_Content_Type = "Content-Type"; -static constexpr const char* T_Cookie = "Cookie"; -static constexpr const char* T_DIGEST = "Digest"; -static constexpr const char* T_DIGEST_ = "Digest "; -static constexpr const char* T_ETag = "ETag"; -static constexpr const char* T_EXPECT = "Expect"; -static constexpr const char* T_HTTP_1_0 = "HTTP/1.0"; -static constexpr const char* T_HTTP_100_CONT = "HTTP/1.1 100 Continue\r\n\r\n"; -static constexpr const char* T_IMS = "If-Modified-Since"; -static constexpr const char* T_INM = "If-None-Match"; -static constexpr const char* T_keep_alive = "keep-alive"; -static constexpr const char* T_Last_Event_ID = "Last-Event-ID"; -static constexpr const char* T_Last_Modified = "Last-Modified"; -static constexpr const char* T_LOCATION = "Location"; -static constexpr const char* T_MULTIPART_ = "multipart/"; -static constexpr const char* T_no_cache = "no-cache"; -static constexpr const char* T_none = "none"; -static constexpr const char* T_UPGRADE = "Upgrade"; -static constexpr const char* T_WS = "websocket"; -static constexpr const char* T_WWW_AUTH = "WWW-Authenticate"; -static constexpr const char* Transfer_Encoding = "Transfer-Encoding"; - -// HTTP Methods -static constexpr const char* T_ANY = "ANY"; -static constexpr const char* T_GET = "GET"; -static constexpr const char* T_POST = "POST"; -static constexpr const char* T_PUT = "PUT"; -static constexpr const char* T_DELETE = "DELETE"; -static constexpr const char* T_PATCH = "PATCH"; -static constexpr const char* T_HEAD = "HEAD"; -static constexpr const char* T_OPTIONS = "OPTIONS"; -static constexpr const char* T_UNKNOWN = "UNKNOWN"; - -// Req content types -static constexpr const char* T_RCT_NOT_USED = "RCT_NOT_USED"; -static constexpr const char* T_RCT_DEFAULT = "RCT_DEFAULT"; -static constexpr const char* T_RCT_HTTP = "RCT_HTTP"; -static constexpr const char* T_RCT_WS = "RCT_WS"; -static constexpr const char* T_RCT_EVENT = "RCT_EVENT"; -static constexpr const char* T_ERROR = "ERROR"; - -// extentions & MIME-Types -static constexpr const char* T__css = ".css"; -static constexpr const char* T__eot = ".eot"; -static constexpr const char* T__gif = ".gif"; -static constexpr const char* T__gz = ".gz"; -static constexpr const char* T__htm = ".htm"; -static constexpr const char* T__html = ".html"; -static constexpr const char* T__ico = ".ico"; -static constexpr const char* T__jpg = ".jpg"; -static constexpr const char* T__js = ".js"; -static constexpr const char* T__json = ".json"; -static constexpr const char* T__pdf = ".pdf"; -static constexpr const char* T__png = ".png"; -static constexpr const char* T__svg = ".svg"; -static constexpr const char* T__ttf = ".ttf"; -static constexpr const char* T__woff = ".woff"; -static constexpr const char* T__woff2 = ".woff2"; -static constexpr const char* T__xml = ".xml"; -static constexpr const char* T__zip = ".zip"; -static constexpr const char* T_application_javascript = "application/javascript"; -static constexpr const char* T_application_json = "application/json"; -static constexpr const char* T_application_msgpack = "application/msgpack"; -static constexpr const char* T_application_pdf = "application/pdf"; -static constexpr const char* T_application_x_gzip = "application/x-gzip"; -static constexpr const char* T_application_zip = "application/zip"; -static constexpr const char* T_font_eot = "font/eot"; -static constexpr const char* T_font_ttf = "font/ttf"; -static constexpr const char* T_font_woff = "font/woff"; -static constexpr const char* T_font_woff2 = "font/woff2"; -static constexpr const char* T_image_gif = "image/gif"; -static constexpr const char* T_image_jpeg = "image/jpeg"; -static constexpr const char* T_image_png = "image/png"; -static constexpr const char* T_image_svg_xml = "image/svg+xml"; -static constexpr const char* T_image_x_icon = "image/x-icon"; -static constexpr const char* T_text_css = "text/css"; -static constexpr const char* T_text_event_stream = "text/event-stream"; -static constexpr const char* T_text_html = "text/html"; -static constexpr const char* T_text_plain = "text/plain"; -static constexpr const char* T_text_xml = "text/xml"; - -// Responce codes -static constexpr const char* T_HTTP_CODE_100 = "Continue"; -static constexpr const char* T_HTTP_CODE_101 = "Switching Protocols"; -static constexpr const char* T_HTTP_CODE_200 = "OK"; -static constexpr const char* T_HTTP_CODE_201 = "Created"; -static constexpr const char* T_HTTP_CODE_202 = "Accepted"; -static constexpr const char* T_HTTP_CODE_203 = "Non-Authoritative Information"; -static constexpr const char* T_HTTP_CODE_204 = "No Content"; -static constexpr const char* T_HTTP_CODE_205 = "Reset Content"; -static constexpr const char* T_HTTP_CODE_206 = "Partial Content"; -static constexpr const char* T_HTTP_CODE_300 = "Multiple Choices"; -static constexpr const char* T_HTTP_CODE_301 = "Moved Permanently"; -static constexpr const char* T_HTTP_CODE_302 = "Found"; -static constexpr const char* T_HTTP_CODE_303 = "See Other"; -static constexpr const char* T_HTTP_CODE_304 = "Not Modified"; -static constexpr const char* T_HTTP_CODE_305 = "Use Proxy"; -static constexpr const char* T_HTTP_CODE_307 = "Temporary Redirect"; -static constexpr const char* T_HTTP_CODE_400 = "Bad Request"; -static constexpr const char* T_HTTP_CODE_401 = "Unauthorized"; -static constexpr const char* T_HTTP_CODE_402 = "Payment Required"; -static constexpr const char* T_HTTP_CODE_403 = "Forbidden"; -static constexpr const char* T_HTTP_CODE_404 = "Not Found"; -static constexpr const char* T_HTTP_CODE_405 = "Method Not Allowed"; -static constexpr const char* T_HTTP_CODE_406 = "Not Acceptable"; -static constexpr const char* T_HTTP_CODE_407 = "Proxy Authentication Required"; -static constexpr const char* T_HTTP_CODE_408 = "Request Time-out"; -static constexpr const char* T_HTTP_CODE_409 = "Conflict"; -static constexpr const char* T_HTTP_CODE_410 = "Gone"; -static constexpr const char* T_HTTP_CODE_411 = "Length Required"; -static constexpr const char* T_HTTP_CODE_412 = "Precondition Failed"; -static constexpr const char* T_HTTP_CODE_413 = "Request Entity Too Large"; -static constexpr const char* T_HTTP_CODE_414 = "Request-URI Too Large"; -static constexpr const char* T_HTTP_CODE_415 = "Unsupported Media Type"; -static constexpr const char* T_HTTP_CODE_416 = "Requested range not satisfiable"; -static constexpr const char* T_HTTP_CODE_417 = "Expectation Failed"; -static constexpr const char* T_HTTP_CODE_500 = "Internal Server Error"; -static constexpr const char* T_HTTP_CODE_501 = "Not Implemented"; -static constexpr const char* T_HTTP_CODE_502 = "Bad Gateway"; -static constexpr const char* T_HTTP_CODE_503 = "Service Unavailable"; -static constexpr const char* T_HTTP_CODE_504 = "Gateway Time-out"; -static constexpr const char* T_HTTP_CODE_505 = "HTTP Version not supported"; -static constexpr const char* T_HTTP_CODE_ANY = "Unknown code"; - -// other -static constexpr const char* T__opaque = "\", opaque=\""; -static constexpr const char* T_13 = "13"; -static constexpr const char* T_asyncesp = "asyncesp"; -static constexpr const char* T_auth_nonce = "\", qop=\"auth\", nonce=\""; -static constexpr const char* T_cnonce = "cnonce"; -static constexpr const char* T_data_ = "data: "; -static constexpr const char* T_event_ = "event: "; -static constexpr const char* T_filename = "filename"; -static constexpr const char* T_gzip = "gzip"; -static constexpr const char* T_Host = "Host"; -static constexpr const char* T_id__ = "id: "; -static constexpr const char* T_name = "name"; -static constexpr const char* T_nc = "nc"; -static constexpr const char* T_nonce = "nonce"; -static constexpr const char* T_opaque = "opaque"; -static constexpr const char* T_qop = "qop"; -static constexpr const char* T_realm = "realm"; -static constexpr const char* T_realm__ = "realm=\""; -static constexpr const char* T_response = "response"; -static constexpr const char* T_retry_ = "retry: "; -static constexpr const char* T_rn = "\r\n"; -static constexpr const char* T_rnrn = "\r\n\r\n"; -static constexpr const char* T_uri = "uri"; -static constexpr const char* T_username = "username"; - - -#else // ESP8622 - -static const char T_100_CONTINUE[] PROGMEM = "100-continue"; -static const char T_ACCEPT[] PROGMEM = "Accept"; -static const char T_Accept_Ranges[] PROGMEM = "Accept-Ranges"; -static const char T_app_xform_urlencoded[] PROGMEM = "application/x-www-form-urlencoded"; -static const char T_AUTH[] PROGMEM = "Authorization"; -static const char T_BASIC[] PROGMEM = "Basic"; -static const char T_BASIC_REALM[] PROGMEM = "Basic realm=\""; -static const char T_BASIC_REALM_LOGIN_REQ[] PROGMEM = "Basic realm=\"Login Required\""; -static const char T_BODY[] PROGMEM = "body"; -static const char T_Cache_Control[] PROGMEM = "Cache-Control"; -static const char T_chunked[] PROGMEM = "chunked"; -static const char T_close[] PROGMEM = "close"; -static const char T_Connection[] PROGMEM = "Connection"; -static const char T_Content_Disposition[] PROGMEM = "Content-Disposition"; -static const char T_Content_Encoding[] PROGMEM = "Content-Encoding"; -static const char T_Content_Length[] PROGMEM = "Content-Length"; -static const char T_Content_Type[] PROGMEM = "Content-Type"; -static const char T_Cookie[] PROGMEM = "Cookie"; -static const char T_DIGEST[] PROGMEM = "Digest"; -static const char T_DIGEST_[] PROGMEM = "Digest "; -static const char T_ETag[] PROGMEM = "ETag"; -static const char T_EXPECT[] PROGMEM = "Expect"; -static const char T_HTTP_1_0[] PROGMEM = "HTTP/1.0"; -static const char T_HTTP_100_CONT[] PROGMEM = "HTTP/1.1 100 Continue\r\n\r\n"; -static const char T_IMS[] PROGMEM = "If-Modified-Since"; -static const char T_INM[] PROGMEM = "If-None-Match"; -static const char T_keep_alive[] PROGMEM = "keep-alive"; -static const char T_Last_Event_ID[] PROGMEM = "Last-Event-ID"; -static const char T_Last_Modified[] PROGMEM = "Last-Modified"; -static const char T_LOCATION[] PROGMEM = "Location"; -static const char T_MULTIPART_[] PROGMEM = "multipart/"; -static const char T_no_cache[] PROGMEM = "no-cache"; -static const char T_none[] PROGMEM = "none"; -static const char T_UPGRADE[] PROGMEM = "Upgrade"; -static const char T_WS[] PROGMEM = "websocket"; -static const char T_WWW_AUTH[] PROGMEM = "WWW-Authenticate"; -static const char Transfer_Encoding[] PROGMEM = "Transfer-Encoding"; - -// HTTP Methods -static const char T_ANY[] PROGMEM = "ANY"; -static const char T_GET[] PROGMEM = "GET"; -static const char T_POST[] PROGMEM = "POST"; -static const char T_PUT[] PROGMEM = "PUT"; -static const char T_DELETE[] PROGMEM = "DELETE"; -static const char T_PATCH[] PROGMEM = "PATCH"; -static const char T_HEAD[] PROGMEM = "HEAD"; -static const char T_OPTIONS[] PROGMEM = "OPTIONS"; -static const char T_UNKNOWN[] PROGMEM = "UNKNOWN"; - -// Req content types -static const char T_RCT_NOT_USED[] PROGMEM = "RCT_NOT_USED"; -static const char T_RCT_DEFAULT[] PROGMEM = "RCT_DEFAULT"; -static const char T_RCT_HTTP[] PROGMEM = "RCT_HTTP"; -static const char T_RCT_WS[] PROGMEM = "RCT_WS"; -static const char T_RCT_EVENT[] PROGMEM = "RCT_EVENT"; -static const char T_ERROR[] PROGMEM = "ERROR"; - -// extentions & MIME-Types -static const char T__css[] PROGMEM = ".css"; -static const char T__eot[] PROGMEM = ".eot"; -static const char T__gif[] PROGMEM = ".gif"; -static const char T__gz[] PROGMEM = ".gz"; -static const char T__htm[] PROGMEM = ".htm"; -static const char T__html[] PROGMEM = ".html"; -static const char T__ico[] PROGMEM = ".ico"; -static const char T__jpg[] PROGMEM = ".jpg"; -static const char T__js[] PROGMEM = ".js"; -static const char T__json[] PROGMEM = ".json"; -static const char T__pdf[] PROGMEM = ".pdf"; -static const char T__png[] PROGMEM = ".png"; -static const char T__svg[] PROGMEM = ".svg"; -static const char T__ttf[] PROGMEM = ".ttf"; -static const char T__woff[] PROGMEM = ".woff"; -static const char T__woff2[] PROGMEM = ".woff2"; -static const char T__xml[] PROGMEM = ".xml"; -static const char T__zip[] PROGMEM = ".zip"; -static const char T_application_javascript[] PROGMEM = "application/javascript"; -static const char T_application_json[] PROGMEM = "application/json"; -static const char T_application_msgpack[] PROGMEM = "application/msgpack"; -static const char T_application_pdf[] PROGMEM = "application/pdf"; -static const char T_application_x_gzip[] PROGMEM = "application/x-gzip"; -static const char T_application_zip[] PROGMEM = "application/zip"; -static const char T_font_eot[] PROGMEM = "font/eot"; -static const char T_font_ttf[] PROGMEM = "font/ttf"; -static const char T_font_woff[] PROGMEM = "font/woff"; -static const char T_font_woff2[] PROGMEM = "font/woff2"; -static const char T_image_gif[] PROGMEM = "image/gif"; -static const char T_image_jpeg[] PROGMEM = "image/jpeg"; -static const char T_image_png[] PROGMEM = "image/png"; -static const char T_image_svg_xml[] PROGMEM = "image/svg+xml"; -static const char T_image_x_icon[] PROGMEM = "image/x-icon"; -static const char T_text_css[] PROGMEM = "text/css"; -static const char T_text_event_stream[] PROGMEM = "text/event-stream"; -static const char T_text_html[] PROGMEM = "text/html"; -static const char T_text_plain[] PROGMEM = "text/plain"; -static const char T_text_xml[] PROGMEM = "text/xml"; - -// Responce codes -static const char T_HTTP_CODE_100[] PROGMEM = "Continue"; -static const char T_HTTP_CODE_101[] PROGMEM = "Switching Protocols"; -static const char T_HTTP_CODE_200[] PROGMEM = "OK"; -static const char T_HTTP_CODE_201[] PROGMEM = "Created"; -static const char T_HTTP_CODE_202[] PROGMEM = "Accepted"; -static const char T_HTTP_CODE_203[] PROGMEM = "Non-Authoritative Information"; -static const char T_HTTP_CODE_204[] PROGMEM = "No Content"; -static const char T_HTTP_CODE_205[] PROGMEM = "Reset Content"; -static const char T_HTTP_CODE_206[] PROGMEM = "Partial Content"; -static const char T_HTTP_CODE_300[] PROGMEM = "Multiple Choices"; -static const char T_HTTP_CODE_301[] PROGMEM = "Moved Permanently"; -static const char T_HTTP_CODE_302[] PROGMEM = "Found"; -static const char T_HTTP_CODE_303[] PROGMEM = "See Other"; -static const char T_HTTP_CODE_304[] PROGMEM = "Not Modified"; -static const char T_HTTP_CODE_305[] PROGMEM = "Use Proxy"; -static const char T_HTTP_CODE_307[] PROGMEM = "Temporary Redirect"; -static const char T_HTTP_CODE_400[] PROGMEM = "Bad Request"; -static const char T_HTTP_CODE_401[] PROGMEM = "Unauthorized"; -static const char T_HTTP_CODE_402[] PROGMEM = "Payment Required"; -static const char T_HTTP_CODE_403[] PROGMEM = "Forbidden"; -static const char T_HTTP_CODE_404[] PROGMEM = "Not Found"; -static const char T_HTTP_CODE_405[] PROGMEM = "Method Not Allowed"; -static const char T_HTTP_CODE_406[] PROGMEM = "Not Acceptable"; -static const char T_HTTP_CODE_407[] PROGMEM = "Proxy Authentication Required"; -static const char T_HTTP_CODE_408[] PROGMEM = "Request Time-out"; -static const char T_HTTP_CODE_409[] PROGMEM = "Conflict"; -static const char T_HTTP_CODE_410[] PROGMEM = "Gone"; -static const char T_HTTP_CODE_411[] PROGMEM = "Length Required"; -static const char T_HTTP_CODE_412[] PROGMEM = "Precondition Failed"; -static const char T_HTTP_CODE_413[] PROGMEM = "Request Entity Too Large"; -static const char T_HTTP_CODE_414[] PROGMEM = "Request-URI Too Large"; -static const char T_HTTP_CODE_415[] PROGMEM = "Unsupported Media Type"; -static const char T_HTTP_CODE_416[] PROGMEM = "Requested range not satisfiable"; -static const char T_HTTP_CODE_417[] PROGMEM = "Expectation Failed"; -static const char T_HTTP_CODE_500[] PROGMEM = "Internal Server Error"; -static const char T_HTTP_CODE_501[] PROGMEM = "Not Implemented"; -static const char T_HTTP_CODE_502[] PROGMEM = "Bad Gateway"; -static const char T_HTTP_CODE_503[] PROGMEM = "Service Unavailable"; -static const char T_HTTP_CODE_504[] PROGMEM = "Gateway Time-out"; -static const char T_HTTP_CODE_505[] PROGMEM = "HTTP Version not supported"; -static const char T_HTTP_CODE_ANY[] PROGMEM = "Unknown code"; - -// other -static const char T__opaque[] PROGMEM = "\", opaque=\""; -static const char T_13[] PROGMEM = "13"; -static const char T_asyncesp[] PROGMEM = "asyncesp"; -static const char T_auth_nonce[] PROGMEM = "\", qop=\"auth\", nonce=\""; -static const char T_cnonce[] PROGMEM = "cnonce"; -static const char T_data_[] PROGMEM = "data: "; -static const char T_event_[] PROGMEM = "event: "; -static const char T_filename[] PROGMEM = "filename"; -static const char T_gzip[] PROGMEM = "gzip"; -static const char T_Host[] PROGMEM = "Host"; -static const char T_id__[] PROGMEM = "id: "; -static const char T_name[] PROGMEM = "name"; -static const char T_nc[] PROGMEM = "nc"; -static const char T_nonce[] PROGMEM = "nonce"; -static const char T_opaque[] PROGMEM = "opaque"; -static const char T_qop[] PROGMEM = "qop"; -static const char T_realm[] PROGMEM = "realm"; -static const char T_realm__[] PROGMEM = "realm=\""; -static const char T_response[] PROGMEM = "response"; -static const char T_retry_[] PROGMEM = "retry: "; -static const char T_rn[] PROGMEM = "\r\n"; -static const char T_rnrn[] PROGMEM = "\r\n\r\n"; -static const char T_uri[] PROGMEM = "uri"; -static const char T_username[] PROGMEM = "username"; - -#endif // ESP8622 - -} // namespace asyncsrv {} diff --git a/lib/ESPAsyncWebServer/src/port/SHA1Builder.cpp b/lib/ESPAsyncWebServer/src/port/SHA1Builder.cpp deleted file mode 100644 index 901fb80..0000000 --- a/lib/ESPAsyncWebServer/src/port/SHA1Builder.cpp +++ /dev/null @@ -1,284 +0,0 @@ -/* - * FIPS-180-1 compliant SHA-1 implementation - * - * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved - * SPDX-License-Identifier: Apache-2.0 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * This file is part of mbed TLS (https://tls.mbed.org) - * Modified for esp32 by Lucas Saavedra Vaz on 11 Jan 2024 - */ - -#include -#if ESP_IDF_VERSION_MAJOR < 5 - -#include "SHA1Builder.h" - -// 32-bit integer manipulation macros (big endian) - -#ifndef GET_UINT32_BE -#define GET_UINT32_BE(n, b, i) \ - { (n) = ((uint32_t)(b)[(i)] << 24) | ((uint32_t)(b)[(i) + 1] << 16) | ((uint32_t)(b)[(i) + 2] << 8) | ((uint32_t)(b)[(i) + 3]); } -#endif - -#ifndef PUT_UINT32_BE -#define PUT_UINT32_BE(n, b, i) \ - { \ - (b)[(i)] = (uint8_t)((n) >> 24); \ - (b)[(i) + 1] = (uint8_t)((n) >> 16); \ - (b)[(i) + 2] = (uint8_t)((n) >> 8); \ - (b)[(i) + 3] = (uint8_t)((n)); \ - } -#endif - -// Constants - -static const uint8_t sha1_padding[64] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - -// Private methods - -void SHA1Builder::process(const uint8_t *data) { - uint32_t temp, W[16], A, B, C, D, E; - - GET_UINT32_BE(W[0], data, 0); - GET_UINT32_BE(W[1], data, 4); - GET_UINT32_BE(W[2], data, 8); - GET_UINT32_BE(W[3], data, 12); - GET_UINT32_BE(W[4], data, 16); - GET_UINT32_BE(W[5], data, 20); - GET_UINT32_BE(W[6], data, 24); - GET_UINT32_BE(W[7], data, 28); - GET_UINT32_BE(W[8], data, 32); - GET_UINT32_BE(W[9], data, 36); - GET_UINT32_BE(W[10], data, 40); - GET_UINT32_BE(W[11], data, 44); - GET_UINT32_BE(W[12], data, 48); - GET_UINT32_BE(W[13], data, 52); - GET_UINT32_BE(W[14], data, 56); - GET_UINT32_BE(W[15], data, 60); - -#define sha1_S(x, n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))) - -#define sha1_R(t) (temp = W[(t - 3) & 0x0F] ^ W[(t - 8) & 0x0F] ^ W[(t - 14) & 0x0F] ^ W[t & 0x0F], (W[t & 0x0F] = sha1_S(temp, 1))) - -#define sha1_P(a, b, c, d, e, x) \ - { \ - e += sha1_S(a, 5) + sha1_F(b, c, d) + sha1_K + x; \ - b = sha1_S(b, 30); \ - } - - A = state[0]; - B = state[1]; - C = state[2]; - D = state[3]; - E = state[4]; - -#define sha1_F(x, y, z) (z ^ (x & (y ^ z))) -#define sha1_K 0x5A827999 - - sha1_P(A, B, C, D, E, W[0]); - sha1_P(E, A, B, C, D, W[1]); - sha1_P(D, E, A, B, C, W[2]); - sha1_P(C, D, E, A, B, W[3]); - sha1_P(B, C, D, E, A, W[4]); - sha1_P(A, B, C, D, E, W[5]); - sha1_P(E, A, B, C, D, W[6]); - sha1_P(D, E, A, B, C, W[7]); - sha1_P(C, D, E, A, B, W[8]); - sha1_P(B, C, D, E, A, W[9]); - sha1_P(A, B, C, D, E, W[10]); - sha1_P(E, A, B, C, D, W[11]); - sha1_P(D, E, A, B, C, W[12]); - sha1_P(C, D, E, A, B, W[13]); - sha1_P(B, C, D, E, A, W[14]); - sha1_P(A, B, C, D, E, W[15]); - sha1_P(E, A, B, C, D, sha1_R(16)); - sha1_P(D, E, A, B, C, sha1_R(17)); - sha1_P(C, D, E, A, B, sha1_R(18)); - sha1_P(B, C, D, E, A, sha1_R(19)); - -#undef sha1_K -#undef sha1_F - -#define sha1_F(x, y, z) (x ^ y ^ z) -#define sha1_K 0x6ED9EBA1 - - sha1_P(A, B, C, D, E, sha1_R(20)); - sha1_P(E, A, B, C, D, sha1_R(21)); - sha1_P(D, E, A, B, C, sha1_R(22)); - sha1_P(C, D, E, A, B, sha1_R(23)); - sha1_P(B, C, D, E, A, sha1_R(24)); - sha1_P(A, B, C, D, E, sha1_R(25)); - sha1_P(E, A, B, C, D, sha1_R(26)); - sha1_P(D, E, A, B, C, sha1_R(27)); - sha1_P(C, D, E, A, B, sha1_R(28)); - sha1_P(B, C, D, E, A, sha1_R(29)); - sha1_P(A, B, C, D, E, sha1_R(30)); - sha1_P(E, A, B, C, D, sha1_R(31)); - sha1_P(D, E, A, B, C, sha1_R(32)); - sha1_P(C, D, E, A, B, sha1_R(33)); - sha1_P(B, C, D, E, A, sha1_R(34)); - sha1_P(A, B, C, D, E, sha1_R(35)); - sha1_P(E, A, B, C, D, sha1_R(36)); - sha1_P(D, E, A, B, C, sha1_R(37)); - sha1_P(C, D, E, A, B, sha1_R(38)); - sha1_P(B, C, D, E, A, sha1_R(39)); - -#undef sha1_K -#undef sha1_F - -#define sha1_F(x, y, z) ((x & y) | (z & (x | y))) -#define sha1_K 0x8F1BBCDC - - sha1_P(A, B, C, D, E, sha1_R(40)); - sha1_P(E, A, B, C, D, sha1_R(41)); - sha1_P(D, E, A, B, C, sha1_R(42)); - sha1_P(C, D, E, A, B, sha1_R(43)); - sha1_P(B, C, D, E, A, sha1_R(44)); - sha1_P(A, B, C, D, E, sha1_R(45)); - sha1_P(E, A, B, C, D, sha1_R(46)); - sha1_P(D, E, A, B, C, sha1_R(47)); - sha1_P(C, D, E, A, B, sha1_R(48)); - sha1_P(B, C, D, E, A, sha1_R(49)); - sha1_P(A, B, C, D, E, sha1_R(50)); - sha1_P(E, A, B, C, D, sha1_R(51)); - sha1_P(D, E, A, B, C, sha1_R(52)); - sha1_P(C, D, E, A, B, sha1_R(53)); - sha1_P(B, C, D, E, A, sha1_R(54)); - sha1_P(A, B, C, D, E, sha1_R(55)); - sha1_P(E, A, B, C, D, sha1_R(56)); - sha1_P(D, E, A, B, C, sha1_R(57)); - sha1_P(C, D, E, A, B, sha1_R(58)); - sha1_P(B, C, D, E, A, sha1_R(59)); - -#undef sha1_K -#undef sha1_F - -#define sha1_F(x, y, z) (x ^ y ^ z) -#define sha1_K 0xCA62C1D6 - - sha1_P(A, B, C, D, E, sha1_R(60)); - sha1_P(E, A, B, C, D, sha1_R(61)); - sha1_P(D, E, A, B, C, sha1_R(62)); - sha1_P(C, D, E, A, B, sha1_R(63)); - sha1_P(B, C, D, E, A, sha1_R(64)); - sha1_P(A, B, C, D, E, sha1_R(65)); - sha1_P(E, A, B, C, D, sha1_R(66)); - sha1_P(D, E, A, B, C, sha1_R(67)); - sha1_P(C, D, E, A, B, sha1_R(68)); - sha1_P(B, C, D, E, A, sha1_R(69)); - sha1_P(A, B, C, D, E, sha1_R(70)); - sha1_P(E, A, B, C, D, sha1_R(71)); - sha1_P(D, E, A, B, C, sha1_R(72)); - sha1_P(C, D, E, A, B, sha1_R(73)); - sha1_P(B, C, D, E, A, sha1_R(74)); - sha1_P(A, B, C, D, E, sha1_R(75)); - sha1_P(E, A, B, C, D, sha1_R(76)); - sha1_P(D, E, A, B, C, sha1_R(77)); - sha1_P(C, D, E, A, B, sha1_R(78)); - sha1_P(B, C, D, E, A, sha1_R(79)); - -#undef sha1_K -#undef sha1_F - - state[0] += A; - state[1] += B; - state[2] += C; - state[3] += D; - state[4] += E; -} - -// Public methods - -void SHA1Builder::begin(void) { - total[0] = 0; - total[1] = 0; - - state[0] = 0x67452301; - state[1] = 0xEFCDAB89; - state[2] = 0x98BADCFE; - state[3] = 0x10325476; - state[4] = 0xC3D2E1F0; - - memset(buffer, 0x00, sizeof(buffer)); - memset(hash, 0x00, sizeof(hash)); -} - -void SHA1Builder::add(const uint8_t *data, size_t len) { - size_t fill; - uint32_t left; - - if (len == 0) { - return; - } - - left = total[0] & 0x3F; - fill = 64 - left; - - total[0] += (uint32_t)len; - total[0] &= 0xFFFFFFFF; - - if (total[0] < (uint32_t)len) { - total[1]++; - } - - if (left && len >= fill) { - memcpy((void *)(buffer + left), data, fill); - process(buffer); - data += fill; - len -= fill; - left = 0; - } - - while (len >= 64) { - process(data); - data += 64; - len -= 64; - } - - if (len > 0) { - memcpy((void *)(buffer + left), data, len); - } -} - -void SHA1Builder::calculate(void) { - uint32_t last, padn; - uint32_t high, low; - uint8_t msglen[8]; - - high = (total[0] >> 29) | (total[1] << 3); - low = (total[0] << 3); - - PUT_UINT32_BE(high, msglen, 0); - PUT_UINT32_BE(low, msglen, 4); - - last = total[0] & 0x3F; - padn = (last < 56) ? (56 - last) : (120 - last); - - add((uint8_t *)sha1_padding, padn); - add(msglen, 8); - - PUT_UINT32_BE(state[0], hash, 0); - PUT_UINT32_BE(state[1], hash, 4); - PUT_UINT32_BE(state[2], hash, 8); - PUT_UINT32_BE(state[3], hash, 12); - PUT_UINT32_BE(state[4], hash, 16); -} - -void SHA1Builder::getBytes(uint8_t *output) { - memcpy(output, hash, SHA1_HASH_SIZE); -} - -#endif // ESP_IDF_VERSION_MAJOR < 5 diff --git a/lib/ESPAsyncWebServer/src/port/SHA1Builder.h b/lib/ESPAsyncWebServer/src/port/SHA1Builder.h deleted file mode 100644 index da9a77a..0000000 --- a/lib/ESPAsyncWebServer/src/port/SHA1Builder.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2024 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef SHA1Builder_h -#define SHA1Builder_h - -#include -#include - -#define SHA1_HASH_SIZE 20 - -class SHA1Builder { - private: - uint32_t total[2]; /* number of bytes processed */ - uint32_t state[5]; /* intermediate digest state */ - unsigned char buffer[64]; /* data block being processed */ - uint8_t hash[SHA1_HASH_SIZE]; /* SHA-1 result */ - - void process(const uint8_t* data); - - public: - void begin(); - void add(const uint8_t* data, size_t len); - void calculate(); - void getBytes(uint8_t* output); -}; - -#endif // SHA1Builder_h diff --git a/lib/PsychicHttp/CHANGELOG.md b/lib/PsychicHttp/CHANGELOG.md new file mode 100644 index 0000000..ca99a11 --- /dev/null +++ b/lib/PsychicHttp/CHANGELOG.md @@ -0,0 +1,34 @@ +# v1.2.1 + +* Fix bug with missing include preventing the HTTPS server from compiling. + +# v1.2 + +* Added TemplatePrinter from https://github.com/Chris--A/PsychicHttp/tree/templatePrint +* Support using as ESP IDF component +* Optional using https server in ESP IDF +* Fixed bug with headers +* Add ESP IDF example + CI script +* Added Arduino Captive Portal example and OTAUpdate from @06GitHub +* HTTPS fix for ESP-IDF v5.0.2+ from @06GitHub +* lots of bugfixes from @mathieucarbou + +Thanks to @Chris--A, @06GitHub, and @dzungpv for your contributions. + +# v1.1 + +* Changed the internal structure to support request handlers on endpoints and generic requests that do not match an endpoint + * websockets, uploads, etc should now create an appropriate handler and attach to an endpoint with the server.on() syntax +* Added PsychicClient to abstract away some of the internals of ESP-IDF sockets + add convenience + * onOpen and onClose callbacks have changed as a result +* Added support for EventSource / SSE +* Added support for multipart file uploads +* changed getParam() to return a PsychicWebParameter in line with ESPAsyncWebserver +* Renamed various classes / files: + * PsychicHttpFileResponse -> PsychicFileResponse + * PsychicHttpServerEndpoint -> PsychicEndpoint + * PsychicHttpServerRequest -> PsychicRequest + * PsychicHttpServerResponse -> PsychicResponse + * PsychicHttpWebsocket.h -> PsychicWebSocket.h + * Websocket => WebSocket +* Quite a few bugfixes from the community. Thank you @glennsky, @gb88, @KastanEr, @kstam, and @zekageri \ No newline at end of file diff --git a/lib/ESPAsyncWebServer/CMakeLists.txt b/lib/PsychicHttp/CMakeLists.txt similarity index 82% rename from lib/ESPAsyncWebServer/CMakeLists.txt rename to lib/PsychicHttp/CMakeLists.txt index 64292ec..4f76477 100644 --- a/lib/ESPAsyncWebServer/CMakeLists.txt +++ b/lib/PsychicHttp/CMakeLists.txt @@ -8,7 +8,9 @@ set(COMPONENT_ADD_INCLUDEDIRS set(COMPONENT_REQUIRES "arduino-esp32" - "AsyncTCP" + "esp_https_server" + "ArduinoJson" + "UrlEncode" ) register_component() diff --git a/lib/PsychicHttp/LICENSE b/lib/PsychicHttp/LICENSE new file mode 100644 index 0000000..8e797d9 --- /dev/null +++ b/lib/PsychicHttp/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2024 Jeremy Poulter, Zachary Smith, and Mathieu Carbou + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/lib/PsychicHttp/README.md b/lib/PsychicHttp/README.md new file mode 100644 index 0000000..5c4289e --- /dev/null +++ b/lib/PsychicHttp/README.md @@ -0,0 +1,826 @@ +# PsychicHttp - HTTP on your ESP 🧙🔮 + +PsychicHttp is a webserver library for ESP32 + Arduino framework which uses the [ESP-IDF HTTP Server](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/esp_http_server.html) library under the hood. It is written in a similar style to the [Arduino WebServer](https://github.com/espressif/arduino-esp32/tree/master/libraries/WebServer), [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer), and [ArduinoMongoose](https://github.com/jeremypoulter/ArduinoMongoose) libraries to make writing code simple and porting from those other libraries straightforward. + +# Features + +* Asynchronous approach (server runs in its own FreeRTOS thread) +* Handles all HTTP methods with lots of convenience functions: + * GET/POST parameters + * get/set headers + * get/set cookies + * basic key/value session data storage + * authentication (basic and digest mode) +* HTTPS / SSL support +* Static fileserving (SPIFFS, LittleFS, etc.) +* Chunked response serving for large files +* File uploads (Basic + Multipart) +* Websocket support with onOpen, onFrame, and onClose callbacks +* EventSource / SSE support with onOpen, and onClose callbacks +* Request filters, including Client vs AP mode (ON_STA_FILTER / ON_AP_FILTER) +* TemplatePrinter class for dynamic variables at runtime + +## Differences from ESPAsyncWebserver + +* No templating system (anyone actually use this?) +* No url rewriting (but you can use request->redirect) + +# Usage + +## Installation + +### Platformio + +[PlatformIO](http://platformio.org) is an open source ecosystem for IoT development. + + Add "PsychicHttp" to project using [Project Configuration File `platformio.ini`](http://docs.platformio.org/page/projectconf.html) and [lib_deps](http://docs.platformio.org/page/projectconf/section_env_library.html#lib-deps) option: + +```ini +[env:myboard] +platform = espressif... +board = ... +framework = arduino + +# using the latest stable version +lib_deps = hoeken/PsychicHttp + +# or using GIT Url (the latest development version) +lib_deps = https://github.com/hoeken/PsychicHttp +``` + +### Installation - Arduino + +Open *Tools -> Manage Libraries...* and search for PsychicHttp. + +# Principles of Operation + +## Things to Note + +* PsychicHttp is a fully asynchronous server and as such does not run on the loop thread. +* You should not use yield or delay or any function that uses them inside the callbacks. +* The server is smart enough to know when to close the connection and free resources. +* You can not send more than one response to a single request. + +## PsychicHttp + +* Listens for connections. +* Wraps the incoming request into PsychicRequest. +* Keeps track of clients + calls optional callbacks on client open and close. +* Find the appropriate handler (if any) for a request and pass it on. + +## Request Life Cycle + +* TCP connection is received by the server. +* HTTP request is wrapped inside ```PsychicRequest``` object + TCP Connection wrapped inside PsychicConnection object. +* When the request head is received, the server goes through all ```PsychicEndpoints``` and finds one that matches the url + method. + * ```handler->filter()``` and ```handler->canHandle()``` are called on the handler to verify the handler should process the request. + * ```handler->needsAuthentication()``` is called and sends an authorization response if required. + * ```handler->handleRequest()``` is called to actually process the HTTP request. +* If the handler cannot process the request, the server will loop through any global handlers and call that handler if it passes filter(), canHandle(), and needsAuthentication(). +* If no global handlers are called, the server.defaultEndpoint handler will be called. +* Each handler is responsible for processing the request and sending a response. +* When the response is sent, the client is closed and freed from the memory. + * Unless its a special handler like websockets or eventsource. + +![Flowchart of Request Lifecycle](/assets/request-flow.svg) + +### Handlers + +* ```PsychicHandler``` is used for processing and responding to specific HTTP requests. +* ```PsychicHandler``` instances can be attached to any endpoint or as global handlers. +* Setting a ```Filter``` to the ```PsychicHandler``` controls when to apply the handler, decision can be based on + request method, url, request host/port/target host, the request client's localIP or remoteIP. +* Two filter callbacks are provided: ```ON_AP_FILTER``` to execute the rewrite when request is made to the AP interface, + ```ON_STA_FILTER``` to execute the rewrite when request is made to the STA interface. +* The ```canHandle``` method is used for handler specific control on whether the requests can be handled. Decision can be based on request method, request url, request host/port/target host. +* Depending on how the handler is implemented, it may provide callbacks for adding your own custom processing code to the handler. +* Global ```Handlers``` are evaluated in the order they are attached to the server. The ```canHandle``` is called only + if the ```Filter``` that was set to the ```Handler``` return true. +* The first global ```Handler``` that can handle the request is selected, no further processing of handlers is called. + +![Flowchart of Request Lifecycle](/assets/handler-callbacks.svg) + +### Responses and how do they work + +* The ```PsychicResponse``` objects are used to send the response data back to the client. +* Typically the response should be fully generated and sent from the callback. +* It may be possible to generate the response outside the callback, but it will be difficult. + * The exceptions are websockets + eventsource where the response is sent, but the connection is maintained and new data can be sent/received outside the handler. + +# Porting From ESPAsyncWebserver + +If you have existing code using ESPAsyncWebserver, you will feel right at home with PsychicHttp. Even if internally it is much different, the external interface is very similar. Some things are mostly cosmetic, like different class names and callback definitions. A few things might require a bit more in-depth approach. If you're porting your code and run into issues that aren't covered here, please post and issue. + +## Globals Stuff + +* Change your #include to ```#include ``` +* Change your server instance: ```PsychicHttpServer server;``` +* Define websocket handler if you have one: ```PsychicWebSocketHandler websocketHandler;``` +* Define eventsource if you have one: ```PsychicEventSource eventSource;``` + +## setup() Stuff + +* no more server.begin(), call server.listen(80), before you add your handlers +* server has a configurable limit on .on() endpoints. change it with ```server.config.max_uri_handlers = 20;``` as needed. +* check your callback function definitions: + * AsyncWebServerRequest -> PsychicRequest + * no more onBody() event + * for small bodies (server.maxRequestBodySize, default 16k) it will be automatically loaded and accessed by request->body() + * for large bodies, use an upload handler and onUpload() + * websocket callbacks are much different (and simpler!) + * websocket / eventsource handlers get attached to url in server.on("/url", &handler) instead of passing url to handler constructor. + * eventsource callbacks are onOpen and onClose now. +* HTTP_ANY is not supported by ESP-IDF, so we can't use it either. +* NO server.onFileUpload(onUpload); (you could attach an UploadHandler to the default endpoint i guess?) +* NO server.onRequestBody(onBody); (same) + +## Requests / Responses + +* request->send is now request->reply() +* if you create a response, call response->send() directly, not request->send(reply) +* request->headers() is not supported by ESP-IDF, you have to just check for the header you need. +* No AsyncCallbackJsonWebHandler (for now... can add if needed) +* No request->beginResponse(). Instanciate a PsychicResponse instead: ```PsychicResponse response(request);``` +* No PROGMEM suppport (its not relevant to ESP32: https://esp32.com/viewtopic.php?t=20595) +* No Stream response support just yet + +# Usage + +## Create the Server + +Here is an example of the typical server setup: + +```cpp +#include +PsychicHttpServer server; + +void setup() +{ + //optional low level setup server config stuff here. + //server.config is an ESP-IDF httpd_config struct + //see: https://docs.espressif.com/projects/esp-idf/en/v4.4.6/esp32/api-reference/protocols/esp_http_server.html#_CPPv412httpd_config + //increase maximum number of uri endpoint handlers (.on() calls) + server.config.max_uri_handlers = 20; + + //connect to wifi + + //start the server listening on port 80 (standard HTTP port) + server.listen(80); + + //call server methods to attach endpoints and handlers + server.on(...); + server.serveStatic(...); + server.attachHandler(...); +} +``` + +## Add Handlers + +One major difference from ESPAsyncWebserver is that handlers can be attached to a specific url (endpoint) or as a global handler. The reason for this, is that attaching to a specific URL is more efficient and makes for cleaner code. + +### Endpoint Handlers + +An endpoint is basically just the URL path (eg. /path/to/file) without any query string. The ```server.on(...)``` function is a convenience function for creating endpoints and attaching a handler to them. There are two main styles: attaching a basic ```WebRequest``` handler and attaching an external handler. + +```cpp +//creates a basic PsychicWebHandler that calls the request_callback callback +server.on("/url", HTTP_GET, request_callback); + +//same as above, but defaults to HTTP_GET +server.on("/url", request_callback); + +//attaches a websocket handler to /ws +PsychicWebSocketHandler websocketHandler; +server.on("/ws", &websocketHandler); +``` + +The ```server.on(...)``` returns a pointer to the endpoint, which can be used to call various functions like ```setHandler()```, ```setFilter()```, and ```setAuthentication()```. + +```cpp +//respond to /url only from requests to the AP +server.on("/url", HTTP_GET, request_callback)->setFilter(ON_AP_FILTER); + +//require authentication on /url +server.on("/url", HTTP_GET, request_callback)->setAuthentication("user", "pass"); + +//attach websocket handler to /ws +PsychicWebSocketHandler websocketHandler; +server.on("/ws")->attachHandler(&websocketHandler); +``` + +### Basic Requests + +The ```PsychicWebHandler``` class is for handling standard web requests. It provides a single callback: ```onRequest()```. This callback is called when the handler receives a valid HTTP request. + +One major difference from ESPAsyncWebserver is that this callback needs to return an esp_err_t variable to let the server know the result of processing the request. The ```response->reply()``` and ```request->send()``` functions will return this. It is a good habit to return the result of these functions as sending the response will close the connection. + +The function definition for the onRequest callback is: + +```cpp +esp_err_t function_name(PsychicRequest *request); +``` + +Here is a simple example that sends back the client's IP on the URL /ip + +```cpp +server.on("/ip", [](PsychicRequest *request) +{ + String output = "Your IP is: " + request->client()->remoteIP().toString(); + return request->reply(output.c_str()); +}); +``` + +### Uploads + +The ```PsychicUploadHandler``` class is for handling uploads, both large POST bodies and multipart encoded forms. It provides two callbacks: ```onUpload()``` and ```onRequest()```. + +```onUpload(...)``` is called when there is new data. This function may be called multiple times so that you can process the data in chunks. The function definition for the onUpload callback is: + +```cpp +esp_err_t function_name(PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool final); +``` + +* request is a pointer to the Request object +* filename is the name of the uploaded file +* index is the overall byte position of the current data +* data is a pointer to the data buffer +* len is the length of the data buffer +* final is a flag to tell if its the last chunk of data + +```onRequest(...)``` is called after the successful handling of the upload. Its definition and usage is the same as the basic request example as above. + +#### Basic Upload (file is the entire POST body) + +It's worth noting that there is no standard way of passing in a filename for this method, so the handler attempts to guess the filename with the following methods: + +* Checking the Content-Disposition header +* Checking the _filename query parameter (eg. /upload?filename=filename.txt becomes filename.txt) +* Checking the url and taking the last part as filename (eg. /upload/filename.txt becomes filename.txt). You must set a wildcard url for this to work as in the example below. + +```cpp +//handle a very basic upload as post body + PsychicUploadHandler *uploadHandler = new PsychicUploadHandler(); + uploadHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { + File file; + String path = "/www/" + filename; + + Serial.printf("Writing %d/%d bytes to: %s\n", (int)index+(int)len, request->contentLength(), path.c_str()); + + if (last) + Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len); + + //our first call? + if (!index) + file = LittleFS.open(path, FILE_WRITE); + else + file = LittleFS.open(path, FILE_APPEND); + + if(!file) { + Serial.println("Failed to open file"); + return ESP_FAIL; + } + + if(!file.write(data, len)) { + Serial.println("Write failed"); + return ESP_FAIL; + } + + return ESP_OK; + }); + + //gets called after upload has been handled + uploadHandler->onRequest([](PsychicRequest *request) + { + String url = "/" + request->getFilename(); + String output = "" + url + ""; + + return request->reply(output.c_str()); + }); + + //wildcard basic file upload - POST to /upload/filename.ext + server.on("/upload/*", HTTP_POST, uploadHandler); +``` + +#### Multipart Upload + +Very similar to the basic upload, with 2 key differences: + +* multipart requests don't know the total size of the file until after it has been fully processed. You can get a rough idea with request->contentLength(), but that is the length of the entire multipart encoded request. +* you can access form variables, including multipart file infor (name + size) in the onRequest handler using request->getParam() + +```cpp + //a little bit more complicated multipart form + PsychicUploadHandler *multipartHandler = new PsychicUploadHandler(); + multipartHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { + File file; + String path = "/www/" + filename; + + //some progress over serial. + Serial.printf("Writing %d bytes to: %s\n", (int)len, path.c_str()); + if (last) + Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len); + + //our first call? + if (!index) + file = LittleFS.open(path, FILE_WRITE); + else + file = LittleFS.open(path, FILE_APPEND); + + if(!file) { + Serial.println("Failed to open file"); + return ESP_FAIL; + } + + if(!file.write(data, len)) { + Serial.println("Write failed"); + return ESP_FAIL; + } + + return ESP_OK; + }); + + //gets called after upload has been handled + multipartHandler->onRequest([](PsychicRequest *request) + { + PsychicWebParameter *file = request->getParam("file_upload"); + + String url = "/" + file->value(); + String output; + + output += "" + url + "
\n"; + output += "Bytes: " + String(file->size()) + "
\n"; + output += "Param 1: " + request->getParam("param1")->value() + "
\n"; + output += "Param 2: " + request->getParam("param2")->value() + "
\n"; + + return request->reply(output.c_str()); + }); + + //upload to /multipart url + server.on("/multipart", HTTP_POST, multipartHandler); +``` + +### Static File Serving + +The ```PsychicStaticFileHandler``` is a special handler that does not provide any callbacks. It is used to serve a file or files from a specific directory in a filesystem to a directory on the webserver. The syntax is exactly the same as ESPAsyncWebserver. Anything that is derived from the ```FS``` class should work (eg. SPIFFS, LittleFS, SD, etc) + +A couple important notes: + +* If it finds a file with an extra .gz extension, it will serve it as gzip encoded (eg: /targetfile.ext -> {targetfile.ext}.gz) +* If the file is larger than FILE_CHUNK_SIZE (default 8kb) then it will send it as a chunked response. +* It will detect most basic filetypes and automatically set the appropriate Content-Type + +The ```server.serveStatic()``` function handles creating the handler and assigning it to the server: + +```cpp +//serve static files from LittleFS/www on / only to clients on same wifi network +//this is where our /index.html file lives +server.serveStatic("/", LittleFS, "/www/")->setFilter(ON_STA_FILTER); + +//serve static files from LittleFS/www-ap on / only to clients on SoftAP +//this is where our /index.html file lives +server.serveStatic("/", LittleFS, "/www-ap/")->setFilter(ON_AP_FILTER); + +//serve static files from LittleFS/img on /img +//it's more efficient to serve everything from a single www directory, but this is also possible. +server.serveStatic("/img", LittleFS, "/img/"); + +//you can also serve single files +server.serveStatic("/myfile.txt", LittleFS, "/custom.txt"); +``` + +You could also theoretically use the file response directly: + +```cpp +server.on("/ip", [](PsychicRequest *request) +{ + String filename = "/path/to/file"; + PsychicFileResponse response(request, LittleFS, filename); + + return response.send(); +}); +PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path) +``` + +### Websockets + +The ```PsychicWebSocketHandler``` class is for handling WebSocket connections. It provides 3 callbacks: + +```onOpen(...)``` is called when a new WebSocket client connects. +```onFrame(...)``` is called when a new WebSocket frame has arrived. +```onClose(...)``` is called when a new WebSocket client disconnects. + +Here are the callback definitions: + +```cpp +void open_function(PsychicWebSocketClient *client); +esp_err_t frame_function(PsychicWebSocketRequest *request, httpd_ws_frame *frame); +void close_function(PsychicWebSocketClient *client); +``` + +WebSockets were the main reason for starting PsychicHttp, so they are well tested. They are also much simplified from the ESPAsyncWebserver style. You do not need to worry about error handling, partial frame assembly, PONG messages, etc. The onFrame() function is called when a complete frame has been received, and can handle frames up to the entire available heap size. + +Here is a basic example of using WebSockets: + +```cpp + //create our handler... note this should be located as a global or somewhere it wont go out of scope and be destroyed. + PsychicWebSocketHandler websocketHandler(); + + websocketHandler.onOpen([](PsychicWebSocketClient *client) { + Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); + client->sendMessage("Hello!"); + }); + + websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) { + Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload); + return request->reply(frame); + }); + + websocketHandler.onClose([](PsychicWebSocketClient *client) { + Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); + }); + + //attach the handler to /ws. You can then connect to ws://ip.address/ws + server.on("/ws", &websocketHandler); +``` + +The onFrame() callback has 2 parameters: + +* ```PsychicWebSocketRequest *request``` a special request with helper functions for replying in websocket format. +* ```httpd_ws_frame *frame``` ESP-IDF websocket struct. The important struct members we care about are: + * ```uint8_t *payload; /*!< Pre-allocated data buffer */``` + * ```size_t len; /*!< Length of the WebSocket data */``` + +For sending data on the websocket connection, there are 3 methods: + +* ```request->reply()``` - only available in the onFrame() callback context. +* ```webSocketHandler.sendAll()``` - can be used anywhere to send websocket messages to all connected clients. +* ```client->send()``` - can be used anywhere* to send a websocket message to a specific client + +All of the above functions either accept simple ```char *``` string of you can construct your own httpd_ws_frame. + +*Special Note:* Do not hold on to the ```PsychicWebSocketClient``` for sending messages to clients outside the callbacks. That pointer is destroyed when a client disconnects. Instead, store the ```int client->socket()```. Then when you want to send a message, use this code: + +```cpp +//make sure our client is still connected. +PsychicWebSocketClient *client = websocketHandler.getClient(socket); +if (client != NULL) + client->send("Your Message") +``` + +### EventSource / SSE + +The ```PsychicEventSource``` class is for handling EventSource / SSE connections. It provides 2 callbacks: + +```onOpen(...)``` is called when a new EventSource client connects. +```onClose(...)``` is called when a new EventSource client disconnects. + +Here are the callback definitions: + +```cpp +void open_function(PsychicEventSourceClient *client); +void close_function(PsychicEventSourceClient *client); +``` + +Here is a basic example of using PsychicEventSource: + +```cpp + //create our handler... note this should be located as a global or somewhere it wont go out of scope and be destroyed. + PsychicEventSource eventSource; + + eventSource.onOpen([](PsychicEventSourceClient *client) { + Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); + client->send("Hello user!", NULL, millis(), 1000); + }); + + eventSource.onClose([](PsychicEventSourceClient *client) { + Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); + }); + + //attach the handler to /events + server.on("/events", &eventSource); +``` + +For sending data on the EventSource connection, there are 2 methods: + +* ```eventSource.send()``` - can be used anywhere to send events to all connected clients. +* ```client->send()``` - can be used anywhere* to send events to a specific client + +All of the above functions accept a simple ```char *``` message, and optionally: ```char *``` event name, id, and reconnect time. + +*Special Note:* Do not hold on to the ```PsychicEventSourceClient``` for sending messages to clients outside the callbacks. That pointer is destroyed when a client disconnects. Instead, store the ```int client->socket()```. Then when you want to send a message, use this code: + +```cpp +//make sure our client is still connected. +PsychicEventSourceClient *client = eventSource.getClient(socket); +if (client != NULL) + client->send("Your Event") +``` + +### HTTPS / SSL + +PsychicHttp supports HTTPS / SSL out of the box, however there are some limitations (see performance below). Enabling it also increases the code size by about 100kb. To use HTTPS, you need to modify your setup like so: + +```cpp +#include +#include +PsychicHttpsServer server; +server.listen(443, server_cert, server_key); +``` + +```server_cert``` and ```server_key``` are both ```const char *``` parameters which contain the server certificate and private key, respectively. + +To generate your own key and self signed certificate, you can use the command below: + +``` +openssl req -x509 -newkey rsa:4096 -nodes -keyout server.key -out server.crt -sha256 -days 365 +``` + +Including the ```PsychicHttpsServer.h``` also defines ```PSY_ENABLE_SSL``` which you can use in your code to allow enabling / disabling calls in your code based on if the HTTPS server is available: + +```cpp +//our main server object +#ifdef PSY_ENABLE_SSL + PsychicHttpsServer server; +#else + PsychicHttpServer server; +#endif +``` + +Last, but not least, you can create a separate HTTP server on port 80 that redirects all requests to the HTTPS server: + +```cpp +//this creates a 2nd server listening on port 80 and redirects all requests HTTPS +PsychicHttpServer *redirectServer = new PsychicHttpServer(); +redirectServer->config.ctrl_port = 20420; // just a random port different from the default one +redirectServer->listen(80); +redirectServer->onNotFound([](PsychicRequest *request) { + String url = "https://" + request->host() + request->url(); + return request->redirect(url.c_str()); +}); +``` + +# TemplatePrinter + +**This is not specific to PsychicHttp, and it works with any `Print` object. You could for example, template data out to `File`, `Serial`, etc...**. + +The template engine is a `Print` interface and can be printed to directly, however, if you are just templating a few short strings, I'd probably just use `response.printf()` instead. **Its benefit will be seen when templating large inputs such as files.** + +One benefit may be **templating a **JSON** file avoiding the need to use ArduinoJson.** + +Before closing the underlying `Print`/`Stream` that this writes to, it must be flushed as small amounts of data can be buffered. A convenience method to take care of this is shows in `example 3`. + +The header file is not currently added to `PsychicHttp.h` and users will have to add it manually: + +```C++ +#include +``` + +## Template parameter definition: + +- Must start and end with a preset delimiter, the default is `%` +- Can only contain `a-z`, `A-Z`, `0-9`, and `_` +- Maximum length of 63 characters (buffer is 64 including `null`). +- A parameter must not be zero length (not including delimiters). +- Spaces or any other character do not match as a parameter, and will be output as is. +- Valid examples + - `%MY_PARAM%` + - `%SOME1%` +- **Invalid** examples + - `%MY PARAM%` + - `%SOME1 %` + - `%UNFINISHED` + - `%%` + +## Template processing +A function or lambda is used to receive the parameter replacement. + +```C++ +bool templateHandler(Print &output, const char *param){ + //... +} + +[](Print &output, const char *param){ + //... +} +``` + +Parameters: +- `Print &output` - the underlying `Print`, print the results of templating to this. +- `const char *param` - a string containing the current parameter. + +The handler must return a `bool`. +- `true`: the parameter was handled, continue as normal. +- `false`: the input detected as a parameter is not, print literal. + +See output in **example 1** regarding the effects of returning `true` or `false`. + +## Template input handler +This is not needed unless using the static convenience function `TemplatePrinter::start()`. See **example 3**. + +```C++ +bool inputHandler(TemplatePrinter &printer){ + //... +} + +[](TemplatePrinter &printer){ + //... +} +``` + +Parameters: +- `TemplatePrinter &printer` - The template engine, print your template text to this for processing. + + +## Example 1 - Simple use with `PsychicStreamResponse`: +This example highlights its most basic usage. + +```C++ + +// Function to handle parameter requests. + +bool templateHandler(Print &output, const char *param){ + + if(strcmp(param, "FREE_HEAP") == 0){ + output.print((double)ESP.getFreeHeap() / 1024.0, 2); + + }else if(strcmp(param, "MIN_FREE_HEAP") == 0){ + output.print((double)ESP.getMinFreeHeap() / 1024.0, 2); + + }else if(strcmp(param, "MAX_ALLOC_HEAP") == 0){ + output.print((double)ESP.getMaxAllocHeap() / 1024.0, 2); + + }else if(strcmp(param, "HEAP_SIZE") == 0){ + output.print((double)ESP.getHeapSize() / 1024.0, 2); + }else{ + return false; + } + output.print("Kb"); + return true; +} + +// Example serving a request +server.on("/template", [](PsychicRequest *request) { + PsychicStreamResponse response(request, "text/plain"); + + response.beginSend(); + + TemplatePrinter printer(response, templateHandler); + + printer.println("My ESP has %FREE_HEAP% left. Its lifetime minimum heap is %MIN_FREE_HEAP%."); + printer.println("The maximum allocation size is %MAX_ALLOC_HEAP%, and its total size is %HEAP_SIZE%."); + printer.println("This is an unhandled parameter: %UNHANDLED_PARAM% and this is an invalid param %INVALID PARAM%."); + printer.println("This line finished with %UNFIN"); + printer.flush(); + + return response.endSend(); +}); +``` + +The output for example looks like: +``` +My ESP has 170.92Kb left. Its lifetime minimum heap is 169.83Kb. +The maximum allocation size is 107.99Kb, and its total size is 284.19Kb. +This is an unhandled parameter: %UNHANDLED_PARAM% and this is an invalid param %INVALID PARAM%. +This line finished with %UNFIN +``` + +## Example 2 - Templating a file + +```C++ +server.on("/home", [](PsychicRequest *request) { + PsychicStreamResponse response(request, "text/html"); + File file = SD.open("/www/index.html"); + + response.beginSend(); + + TemplatePrinter printer(response, templateHandler); + + printer.copyFrom(file); + printer.flush(); + file.close(); + + return response.endSend(); +}); +``` + +## Example 3 - Using the `TemplatePrinter::start` method. +This static method allows an RAII approach, allowing you to template a stream, etc... without needing a `flush()`. The function call is laid out as: + +```C++ +TemplatePrinter::start(host_stream, template_handler, input_handler); +``` + +\*these examples use the `templateHandler` function defined in example 1. + +### Serve a file like example 2 +```C++ +server.on("/home", [](PsychicRequest *request) { + PsychicStreamResponse response(request, "text/html"); + File file = SD.open("/www/index.html"); + + response.beginSend(); + TemplatePrinter::start(response, templateHandler, [&file](TemplatePrinter &printer){ + printer.copyFrom(file); + }); + file.close(); + + return response.endSend(); +}); +``` + +### Template a string like example 1 +```C++ +server.on("/template2", [](PsychicRequest *request) { + + PsychicStreamResponse response(request, "text/plain"); + + response.beginSend(); + + TemplatePrinter::start(response, templateHandler, [](TemplatePrinter &printer){ + printer.println("My ESP has %FREE_HEAP% left. Its lifetime minimum heap is %MIN_FREE_HEAP%."); + printer.println("The maximum allocation size is %MAX_ALLOC_HEAP%, and its total size is %HEAP_SIZE%."); + printer.println("This is an unhandled parameter: %UNHANDLED_PARAM% and this is an invalid param %INVALID PARAM%."); + }); + + return response.endSend(); +}); +``` + +# Performance + +In order to really see the differences between libraries, I created some basic benchmark firmwares for PsychicHttp, ESPAsyncWebserver, and ArduinoMongoose. I then ran the loadtest-http.sh and loadtest-websocket.sh scripts against each firmware to get some real numbers on the performance of each server library. All of the code and results are available in the /benchmark folder. If you want to see the collated data and graphs, there is a [LibreOffice spreadsheet](/benchmark/comparison.ods). + +![Performance graph](/benchmark/performance.png) +![Latency graph](/benchmark/latency.png) + +## HTTPS / SSL + +Yes, PsychicHttp supports SSL out of the box, but there are a few caveats: + +* Due to memory limitations, it can only handle 2 connections at a time. Each SSL connection takes about 45k ram, and a blank PsychicHttp sketch has about 150k ram free. +* Speed and latency are still pretty good (see graph above) but the SSH handshake seems to take 1500ms. With websockets or browser its not an issue since the connection is kept alive, but if you are loading requests in another way it will be a bit slow +* Unless you want to expose your ESP to the internet, you are limited to self signed keys and the annoying browser security warnings that come with them. + +## Analysis + +The results clearly show some of the reasons for writing PsychicHttp: ESPAsyncWebserver crashes under heavy load on each test, across the board in a 60s test. That means in normal usage, you're just rolling the dice with how long it will go until it crashes. Every other number is moot, IMHO. + +ArduinoMongoose doesn't crash under heavy load, but it does bog down with extremely high latency (15s) for web requests and appears to not even respond at the highest loadings as the loadtest script crashes instead. The code itself doesnt crash, so bonus points there. After the high load, it does go back to serving normally. One area ArduinoMongoose does shine, is in websockets where its performance is almost 2x the performance of PsychicHttp. Both in requests per second and latency. Clearly an area of improvement for PsychicHttp. + +PsychicHttp has good performance across the board. No crashes and continously responds during each test. It is a clear winner in requests per second when serving files from memory, dynamic JSON, and has consistent performance when serving files from LittleFS. The only real downside is the lower performance of the websockets with a single connection handling 38rps, and maxing out at 120rps across multiple connections. + +## Takeaways + +With all due respect to @me-no-dev who has done some amazing work in the open source community, I cannot recommend anyone use the ESPAsyncWebserver for anything other than simple projects that don't need to be reliable. Even then, PsychicHttp has taken the arcane api of the ESP-IDF web server library and made it nice and friendly to use with a very similar API to ESPAsyncWebserver. Also, ESPAsyncWebserver is more or less abandoned, with 150 open issues, 77 pending pull requests, and the last commit in over 2 years. + +ArduinoMongoose is a good alternative, although the latency issues when it gets fully loaded can be very annoying. I believe it is also cross platform to other microcontrollers as well, but I haven't tested that. The other issue here is that it is based on an old version of a modified Mongoose library that will be difficult to update as it is a major revision behind and several security updates behind as well. Big thanks to @jeremypoulter though as PsychicHttp is a fork of ArduinoMongoose so it's built on strong bones. + +# Roadmap + +## v1.2: ESPAsyncWebserver Parity + + +Change: +Modify the request handling to bring initail url matching and filtering into PsychicHttpServer itself. + +Benefits: +* Fix a bug with filter() where endpoint is matched, but filter fails and it doesn't continue matching further endpoints (checks are in different codebases) +* HTTP_ANY support +* unlimited endpoints + * we would use a List to store endpoints + * dont have to pre-declare config.max_uri_handlers; +* much more flexibility for future + +Issues +* it would log a warning on every request as if its a 404. (httpd_uri.c:298) +* req->user_ctx is not passed in. (httpd_uri.c:309) + * but... user_ctx is something we could store in the psychicendpoint data + * Websocket support assumes an endpoint with matching url / method (httpd_uri.c:312) + * we could copy and bring this code into our own internal request processor + * would need to manually maintain more code (~100 lines?) and be more prone to esp-idf http_server updates causing problems. + +How to implement +* set config.max_uri_handlers = 1; +* possibly do not register any uri_handlers (looks like it would be fastest way to exit httpd_find_uri_handler (httpd_uri.c:94)) + * looks like 404 is set by default, so should work. +* modify PsychicEndpoint to store the stuff we would pass to http_server +* create a new function handleRequest() before PsychicHttpServer::defaultNotFoundHandler to process incoming requests. + * bring in code from PsychicHttpServer::notFoundHandler + * add new code to loop over endpoints to call match and filter +* bring code from esp-idf library + +* templating system +* regex url matching +* rewrite urls? +* What else are we missing? + + +## Longterm Wants + +* investigate websocket performance gap +* support for esp-idf framework +* support for arduino 3.0 framework +* Enable worker based multithreading with esp-idf v5.x +* 100-continue support? + +If anyone wants to take a crack at implementing any of the above features I am more than happy to accept pull requests. diff --git a/lib/PsychicHttp/RELEASE.md b/lib/PsychicHttp/RELEASE.md new file mode 100644 index 0000000..98db528 --- /dev/null +++ b/lib/PsychicHttp/RELEASE.md @@ -0,0 +1,6 @@ +* Update CHANGELOG +* Bump version in library.json +* Bump version in library.properties +* Make new release + tag + * this will get pulled in automatically by Arduino Library Indexer +* run ```pio pkg publish``` to publish to Platform.io \ No newline at end of file diff --git a/lib/PsychicHttp/assets/handler-callbacks.svg b/lib/PsychicHttp/assets/handler-callbacks.svg new file mode 100644 index 0000000..62fcbdf --- /dev/null +++ b/lib/PsychicHttp/assets/handler-callbacks.svg @@ -0,0 +1,4 @@ + + + +
WebHandler
WebHandler
Handlers with Callbacks
Handlers with Callbacks
onRequest()
onRequest()
WebSocketHandler
WebSocketHandler
onOpen()
onOpen()
onFrame()
onFrame()
onClose()
onClose()
UploadHandler
UploadHandler
onRequest()
onRequest()
onUpload()
onUpload()
EventSource
EventSource
onOpen()
onOpen()
onClose()
onClose()
Text is not SVG - cannot display
\ No newline at end of file diff --git a/lib/PsychicHttp/assets/request-flow.svg b/lib/PsychicHttp/assets/request-flow.svg new file mode 100644 index 0000000..999fb00 --- /dev/null +++ b/lib/PsychicHttp/assets/request-flow.svg @@ -0,0 +1,4 @@ + + + +
HTTP Request
HTTP Request
Yes
Yes
No
No
Endpoint Matched?
Endpoint Matche...
PsychicHandler
PsychicHandler
Yes
Yes
Matching
 Handler?
Matching...
Default Endpoint
Default Endpoint
No
No
Yes
Yes
New Connection?
New Connection?
server.onOpen Callback
server.onOpen Callba...
Connection Closed?
Connection Closed?
handler.onClose
server.onClose
Callbacks
handler.onClose...
Finish
Finish
handler.onOpen + specific callbacks
handler.onOpen + spe...
filter() ?
filter() ?
canHandle() ?
canHandle() ?
authenticate() ?
authenticate() ?
requestAuthentication()
requestAuthentication()
handleRequest()
handleRequest()
response.send()
response.send()
PsychicHttp Request Flow
PsychicHttp Request Flow
Text is not SVG - cannot display
\ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/arduinomongoose/.gitignore b/lib/PsychicHttp/benchmark/arduinomongoose/.gitignore new file mode 100644 index 0000000..9e5f911 --- /dev/null +++ b/lib/PsychicHttp/benchmark/arduinomongoose/.gitignore @@ -0,0 +1,6 @@ +.pio +.vscode/ +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/lib/PsychicHttp/benchmark/arduinomongoose/data/www/alien.png b/lib/PsychicHttp/benchmark/arduinomongoose/data/www/alien.png new file mode 100644 index 0000000..a030da0 Binary files /dev/null and b/lib/PsychicHttp/benchmark/arduinomongoose/data/www/alien.png differ diff --git a/lib/PsychicHttp/benchmark/arduinomongoose/include/README b/lib/PsychicHttp/benchmark/arduinomongoose/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/lib/PsychicHttp/benchmark/arduinomongoose/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/PsychicHttp/benchmark/arduinomongoose/lib/README b/lib/PsychicHttp/benchmark/arduinomongoose/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/PsychicHttp/benchmark/arduinomongoose/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/PsychicHttp/benchmark/arduinomongoose/platformio.ini b/lib/PsychicHttp/benchmark/arduinomongoose/platformio.ini new file mode 100644 index 0000000..b5604fb --- /dev/null +++ b/lib/PsychicHttp/benchmark/arduinomongoose/platformio.ini @@ -0,0 +1,22 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env] +platform = espressif32 +framework = arduino +board = esp32dev +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder +lib_deps = + jeremypoulter/ArduinoMongoose + bblanchon/ArduinoJson +board_build.filesystem = littlefs + +[env:default] \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/arduinomongoose/src/main.cpp b/lib/PsychicHttp/benchmark/arduinomongoose/src/main.cpp new file mode 100644 index 0000000..f7e0a9d --- /dev/null +++ b/lib/PsychicHttp/benchmark/arduinomongoose/src/main.cpp @@ -0,0 +1,234 @@ +/* Wi-Fi STA Connect and Disconnect Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. + +*/ +#include +#include +#include +#include +#include +#include + +const char *ssid = ""; +const char *password = ""; + +MongooseHttpServer server; + +const char *htmlContent = R"( + + + + Sample HTML + + +

Hello, World!

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+ + +)"; + +bool connectToWifi() +{ + Serial.println(); + Serial.print("[WiFi] Connecting to "); + Serial.println(ssid); + + WiFi.setSleep(false); + WiFi.useStaticBuffers(true); + + WiFi.begin(ssid, password); + + // Will try for about 10 seconds (20x 500ms) + int tryDelay = 500; + int numberOfTries = 20; + + // Wait for the WiFi event + while (true) + { + switch (WiFi.status()) + { + case WL_NO_SSID_AVAIL: + Serial.println("[WiFi] SSID not found"); + break; + case WL_CONNECT_FAILED: + Serial.print("[WiFi] Failed - WiFi not connected! Reason: "); + return false; + break; + case WL_CONNECTION_LOST: + Serial.println("[WiFi] Connection was lost"); + break; + case WL_SCAN_COMPLETED: + Serial.println("[WiFi] Scan is completed"); + break; + case WL_DISCONNECTED: + Serial.println("[WiFi] WiFi is disconnected"); + break; + case WL_CONNECTED: + Serial.println("[WiFi] WiFi is connected!"); + Serial.print("[WiFi] IP address: "); + Serial.println(WiFi.localIP()); + return true; + break; + default: + Serial.print("[WiFi] WiFi Status: "); + Serial.println(WiFi.status()); + break; + } + delay(tryDelay); + + if (numberOfTries <= 0) + { + Serial.print("[WiFi] Failed to connect to WiFi!"); + // Use disconnect function to force stop trying to connect + WiFi.disconnect(); + return false; + } + else + { + numberOfTries--; + } + } + + return false; +} + +void setup() +{ + Serial.begin(115200); + delay(10); + Serial.println("ArduinoMongoose Benchmark"); + + // We start by connecting to a WiFi network + // To debug, please enable Core Debug Level to Verbose + if (connectToWifi()) + { + if(!LittleFS.begin()) + { + Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode"); + return; + } + + //start our server + Mongoose.begin(); + server.begin(80); + + //index file + server.on("/", HTTP_GET, [](MongooseHttpServerRequest *request) + { + request->send(200, "text/html", htmlContent); + }); + + //api - parameters passed in via query eg. /api/endpoint?foo=bar + server.on("/api", HTTP_GET, [](MongooseHttpServerRequest *request) + { + //create a response object + StaticJsonDocument<128> output; + output["msg"] = "status"; + output["status"] = "success"; + output["millis"] = millis(); + + //work with some params + if (request->hasParam("foo")) + { + String foo = request->getParam("foo"); + output["foo"] = foo; + } + + //serialize and return + String jsonBuffer; + serializeJson(output, jsonBuffer); + request->send(200, "application/json", jsonBuffer.c_str()); + }); + + //websocket + server.on("/ws$")-> + onFrame([](MongooseHttpWebSocketConnection *connection, int flags, uint8_t *data, size_t len) { + connection->send(WEBSOCKET_OP_TEXT, data, len); + //server.sendAll(connection, (char *)data); + }); + + //hack - no servestatic + server.on("/alien.png", HTTP_GET, [](MongooseHttpServerRequest *request) + { + //open our file + File fp = LittleFS.open("/www/alien.png"); + size_t length = fp.size(); + + //read our data + uint8_t * data = (uint8_t *)malloc(length); + if (data != NULL) + { + fp.readBytes((char *)data, length); + + //send it off + MongooseHttpServerResponseBasic *response = request->beginResponse(); + response->setContent(data, length); + response->setContentType("image/png"); + response->setCode(200); + request->send(response); + + //free the memory + free(data); + } + else + request->send(503); + }); + } +} + +void loop() +{ + Mongoose.poll(1000); +} \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/arduinomongoose/test/README b/lib/PsychicHttp/benchmark/arduinomongoose/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/lib/PsychicHttp/benchmark/arduinomongoose/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/lib/PsychicHttp/benchmark/comparison.ods b/lib/PsychicHttp/benchmark/comparison.ods new file mode 100644 index 0000000..47e6587 Binary files /dev/null and b/lib/PsychicHttp/benchmark/comparison.ods differ diff --git a/lib/PsychicHttp/benchmark/espasyncwebserver/.gitignore b/lib/PsychicHttp/benchmark/espasyncwebserver/.gitignore new file mode 100644 index 0000000..9e5f911 --- /dev/null +++ b/lib/PsychicHttp/benchmark/espasyncwebserver/.gitignore @@ -0,0 +1,6 @@ +.pio +.vscode/ +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/lib/PsychicHttp/benchmark/espasyncwebserver/data/www/alien.png b/lib/PsychicHttp/benchmark/espasyncwebserver/data/www/alien.png new file mode 100644 index 0000000..a030da0 Binary files /dev/null and b/lib/PsychicHttp/benchmark/espasyncwebserver/data/www/alien.png differ diff --git a/lib/PsychicHttp/benchmark/espasyncwebserver/include/README b/lib/PsychicHttp/benchmark/espasyncwebserver/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/lib/PsychicHttp/benchmark/espasyncwebserver/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/PsychicHttp/benchmark/espasyncwebserver/lib/README b/lib/PsychicHttp/benchmark/espasyncwebserver/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/PsychicHttp/benchmark/espasyncwebserver/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/PsychicHttp/benchmark/espasyncwebserver/platformio.ini b/lib/PsychicHttp/benchmark/espasyncwebserver/platformio.ini new file mode 100644 index 0000000..0f883e3 --- /dev/null +++ b/lib/PsychicHttp/benchmark/espasyncwebserver/platformio.ini @@ -0,0 +1,22 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env] +platform = espressif32 +framework = arduino +board = esp32dev +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder +lib_deps = + https://github.com/me-no-dev/ESPAsyncWebServer + bblanchon/ArduinoJson +board_build.filesystem = littlefs + +[env:default] \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/espasyncwebserver/src/main.cpp b/lib/PsychicHttp/benchmark/espasyncwebserver/src/main.cpp new file mode 100644 index 0000000..c76c1cd --- /dev/null +++ b/lib/PsychicHttp/benchmark/espasyncwebserver/src/main.cpp @@ -0,0 +1,276 @@ +/* Wi-Fi STA Connect and Disconnect Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. + +*/ +#include +#include +#include +#include +#include + +const char *ssid = ""; +const char *password = ""; + +AsyncWebServer server(80); +AsyncWebSocket ws("/ws"); + +const char *htmlContent = R"( + + + + Sample HTML + + +

Hello, World!

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+ + +)"; + +bool connectToWifi() +{ + Serial.println(); + Serial.print("[WiFi] Connecting to "); + Serial.println(ssid); + + WiFi.setSleep(false); + WiFi.useStaticBuffers(true); + + WiFi.begin(ssid, password); + + // Will try for about 10 seconds (20x 500ms) + int tryDelay = 500; + int numberOfTries = 20; + + // Wait for the WiFi event + while (true) + { + switch (WiFi.status()) + { + case WL_NO_SSID_AVAIL: + Serial.println("[WiFi] SSID not found"); + break; + case WL_CONNECT_FAILED: + Serial.print("[WiFi] Failed - WiFi not connected! Reason: "); + return false; + break; + case WL_CONNECTION_LOST: + Serial.println("[WiFi] Connection was lost"); + break; + case WL_SCAN_COMPLETED: + Serial.println("[WiFi] Scan is completed"); + break; + case WL_DISCONNECTED: + Serial.println("[WiFi] WiFi is disconnected"); + break; + case WL_CONNECTED: + Serial.println("[WiFi] WiFi is connected!"); + Serial.print("[WiFi] IP address: "); + Serial.println(WiFi.localIP()); + return true; + break; + default: + Serial.print("[WiFi] WiFi Status: "); + Serial.println(WiFi.status()); + break; + } + delay(tryDelay); + + if (numberOfTries <= 0) + { + Serial.print("[WiFi] Failed to connect to WiFi!"); + // Use disconnect function to force stop trying to connect + WiFi.disconnect(); + return false; + } + else + { + numberOfTries--; + } + } + + return false; +} + +void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ + if(type == WS_EVT_CONNECT){ + //client connected + // Serial.printf("ws[%s][%u] connect\n", server->url(), client->id()); + // client->printf("Hello Client %u :)", client->id()); + // client->ping(); + } else if(type == WS_EVT_DISCONNECT){ + //client disconnected + // Serial.printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id()); + } else if(type == WS_EVT_ERROR){ + //error was received from the other end + // Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data); + } else if(type == WS_EVT_PONG){ + //pong message was received (in response to a ping request maybe) + // Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:""); + } else if(type == WS_EVT_DATA){ + //data packet + AwsFrameInfo * info = (AwsFrameInfo*)arg; + if(info->final && info->index == 0 && info->len == len){ + //the whole message is in a single frame and we got all of it's data + // Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len); + if(info->opcode == WS_TEXT){ + data[len] = 0; + // Serial.printf("%s\n", (char*)data); + } else { + // for(size_t i=0; i < info->len; i++){ + // Serial.printf("%02x ", data[i]); + // } + // Serial.printf("\n"); + } + if(info->opcode == WS_TEXT) + { + client->text((char *)data, len); + } + // else + // client->binary("I got your binary message"); + } else { + //message is comprised of multiple frames or the frame is split into multiple packets + if(info->index == 0){ + // if(info->num == 0) + // Serial.printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); + // Serial.printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len); + } + + Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len); + if(info->message_opcode == WS_TEXT){ + data[len] = 0; + // Serial.printf("%s\n", (char*)data); + } else { + // for(size_t i=0; i < len; i++){ + // Serial.printf("%02x ", data[i]); + // } + // Serial.printf("\n"); + } + + if((info->index + len) == info->len){ + // Serial.printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len); + if(info->final){ + // Serial.printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); + if(info->message_opcode == WS_TEXT) + { + client->text((char *)data, info->len); + } + // else + // client->binary("I got your binary message"); + } + } + } + } +} + +void setup() +{ + Serial.begin(115200); + delay(10); + Serial.println("ESPAsyncWebserver Benchmark"); + + // We start by connecting to a WiFi network + // To debug, please enable Core Debug Level to Verbose + if (connectToWifi()) + { + if(!LittleFS.begin()) + { + Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode"); + return; + } + + //api - parameters passed in via query eg. /api/endpoint?foo=bar + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) + { + request->send(200, "text/html", htmlContent); + }); + + //serve static files from LittleFS/www on / + server.serveStatic("/", LittleFS, "/www/"); + + //api - parameters passed in via query eg. /api/endpoint?foo=bar + server.on("/api", HTTP_GET, [](AsyncWebServerRequest *request) + { + //create a response object + StaticJsonDocument<128> output; + output["msg"] = "status"; + output["status"] = "success"; + output["millis"] = millis(); + + //work with some params + if (request->hasParam("foo")) + { + AsyncWebParameter* foo = request->getParam("foo"); + output["foo"] = foo->value(); + } + + //serialize and return + String jsonBuffer; + serializeJson(output, jsonBuffer); + request->send(200, "application/json", jsonBuffer.c_str()); + }); + + ws.onEvent(onEvent); + server.addHandler(&ws); + + server.begin(); + } +} + +void loop() +{ + ws.cleanupClients(); + delay(1000); +} \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/espasyncwebserver/test/README b/lib/PsychicHttp/benchmark/espasyncwebserver/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/lib/PsychicHttp/benchmark/espasyncwebserver/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/lib/PsychicHttp/benchmark/eventsource-client-test.js b/lib/PsychicHttp/benchmark/eventsource-client-test.js new file mode 100644 index 0000000..8253789 --- /dev/null +++ b/lib/PsychicHttp/benchmark/eventsource-client-test.js @@ -0,0 +1,39 @@ +#!/usr/bin/env node + +const EventSource = require('eventsource'); +const url = 'http://192.168.2.131/events'; + +async function eventSourceClient() { + console.log(`Starting test`); + for (let i = 0; i < 1000000; i++) + { + if (i % 100 == 0) + console.log(`Count: ${i}`); + + let eventSource = new EventSource(url); + + eventSource.onopen = () => { + //console.log('EventSource connection opened.'); + }; + + eventSource.onerror = (error) => { + console.error('EventSource error:', error); + + // Close the connection on error + eventSource.close(); + }; + + await new Promise((resolve) => { + eventSource.onmessage = (event) => { + //console.log('Received message:', event.data); + + // Close the connection after receiving the first message + eventSource.close(); + + resolve(); + } + }); + } +} + +eventSourceClient(); \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/http-client-test.js b/lib/PsychicHttp/benchmark/http-client-test.js new file mode 100644 index 0000000..29588b9 --- /dev/null +++ b/lib/PsychicHttp/benchmark/http-client-test.js @@ -0,0 +1,43 @@ +#!/usr/bin/env node + +const axios = require('axios'); + +const url = 'http://192.168.2.131/api'; +const queryParams = { + foo: 'bar', + foo1: 'bar', + foo2: 'bar', + foo3: 'bar', + foo4: 'bar', + foo5: 'bar', + foo6: 'bar', +}; + +const totalRequests = 1000000; +const requestsPerCount = 100; + +let requestCount = 0; + +function fetchData() { + axios.get(url, { params: queryParams }) + .then(response => { + requestCount++; + + if (requestCount % requestsPerCount === 0) { + console.log(`Requests completed: ${requestCount}`); + } + + if (requestCount < totalRequests) { + fetchData(); + } else { + console.log('All requests completed.'); + } + }) + .catch(error => { + console.error('Error making request:', error.message); + }); +} + +// Start making requests +console.log(`Starting test`); +fetchData(); \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/latency.png b/lib/PsychicHttp/benchmark/latency.png new file mode 100644 index 0000000..1cedc93 Binary files /dev/null and b/lib/PsychicHttp/benchmark/latency.png differ diff --git a/lib/PsychicHttp/benchmark/loadtest-http.sh b/lib/PsychicHttp/benchmark/loadtest-http.sh new file mode 100644 index 0000000..4e75b84 --- /dev/null +++ b/lib/PsychicHttp/benchmark/loadtest-http.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +#Command to install the testers: +# npm install -g autocannon + +TEST_IP="192.168.2.131" +TEST_TIME=60 +LOG_FILE=psychic-http-loadtest.log +TIMEOUT=10000 +PROTOCOL=http +#PROTOCOL=https + +if test -f "$LOG_FILE"; then + rm $LOG_FILE +fi + +for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20 +#for CONCURRENCY in 20 +do + printf "\n\nCLIENTS: *** $CONCURRENCY ***\n\n" >> $LOG_FILE + echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/" + #loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/" --quiet >> $LOG_FILE + autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/" >> $LOG_FILE 2>&1 + printf "\n\n----------------\n\n" >> $LOG_FILE + sleep 1 + + echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/api" + #loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/api?foo=bar" --quiet >> $LOG_FILE + autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/api?foo=bar" >> $LOG_FILE 2>&1 + printf "\n\n----------------\n\n" >> $LOG_FILE + sleep 1 + + echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/alien.png" + #loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/alien.png" --quiet >> $LOG_FILE + autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/alien.png" >> $LOG_FILE 2>&1 + printf "\n\n----------------\n\n" >> $LOG_FILE + sleep 1 +done \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/loadtest-websocket.sh b/lib/PsychicHttp/benchmark/loadtest-websocket.sh new file mode 100644 index 0000000..a9b5a41 --- /dev/null +++ b/lib/PsychicHttp/benchmark/loadtest-websocket.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +#Command to install the testers: +# npm install -g loadtest + +TEST_IP="192.168.2.131" +TEST_TIME=60 +LOG_FILE=psychic-websocket-loadtest.log +PROTOCOL=ws +#PROTOCOL=wss + +if test -f "$LOG_FILE"; then + rm $LOG_FILE +fi + +for CONCURRENCY in 1 2 3 4 5 6 7 +do + printf "\n\nCLIENTS: *** $CONCURRENCY ***\n\n" >> $LOG_FILE + echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/ws" + loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE + sleep 1 +done + +for CONNECTIONS in 8 10 16 20 +#for CONNECTIONS in 20 +do + CONCURRENCY=$((CONNECTIONS / 2)) + printf "\n\nCLIENTS: *** $CONNECTIONS ***\n\n" >> $LOG_FILE + echo "Testing $CONNECTIONS clients on $PROTOCOL://$TEST_IP/ws" + loadtest -c $CONCURRENCY --cores 2 -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE + sleep 1 +done \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/package.json b/lib/PsychicHttp/benchmark/package.json new file mode 100644 index 0000000..3237d56 --- /dev/null +++ b/lib/PsychicHttp/benchmark/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "axios": "^1.6.2", + "eventsource": "^2.0.2", + "ws": "^8.14.2" + } +} diff --git a/lib/PsychicHttp/benchmark/performance.png b/lib/PsychicHttp/benchmark/performance.png new file mode 100644 index 0000000..81d2a3f Binary files /dev/null and b/lib/PsychicHttp/benchmark/performance.png differ diff --git a/lib/PsychicHttp/benchmark/psychichttp/.gitignore b/lib/PsychicHttp/benchmark/psychichttp/.gitignore new file mode 100644 index 0000000..9e5f911 --- /dev/null +++ b/lib/PsychicHttp/benchmark/psychichttp/.gitignore @@ -0,0 +1,6 @@ +.pio +.vscode/ +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/lib/PsychicHttp/benchmark/psychichttp/data/www/alien.png b/lib/PsychicHttp/benchmark/psychichttp/data/www/alien.png new file mode 100644 index 0000000..a030da0 Binary files /dev/null and b/lib/PsychicHttp/benchmark/psychichttp/data/www/alien.png differ diff --git a/lib/PsychicHttp/benchmark/psychichttp/include/README b/lib/PsychicHttp/benchmark/psychichttp/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/lib/PsychicHttp/benchmark/psychichttp/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/PsychicHttp/benchmark/psychichttp/lib/README b/lib/PsychicHttp/benchmark/psychichttp/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/PsychicHttp/benchmark/psychichttp/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/PsychicHttp/benchmark/psychichttp/platformio.ini b/lib/PsychicHttp/benchmark/psychichttp/platformio.ini new file mode 100644 index 0000000..868f0ca --- /dev/null +++ b/lib/PsychicHttp/benchmark/psychichttp/platformio.ini @@ -0,0 +1,22 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env] +platform = espressif32 +framework = arduino +board = esp32dev +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder +lib_deps = + https://github.com/hoeken/PsychicHttp + bblanchon/ArduinoJson +board_build.filesystem = littlefs + +[env:default] diff --git a/lib/PsychicHttp/benchmark/psychichttp/src/main.cpp b/lib/PsychicHttp/benchmark/psychichttp/src/main.cpp new file mode 100644 index 0000000..c42d644 --- /dev/null +++ b/lib/PsychicHttp/benchmark/psychichttp/src/main.cpp @@ -0,0 +1,228 @@ +/* Wi-Fi STA Connect and Disconnect Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. + +*/ +#include +#include +#include +#include +#include +#include "_secret.h" + +#ifndef WIFI_SSID + #error "You need to enter your wifi credentials. Copy secret.h to _secret.h and enter your credentials there." +#endif + +//Enter your WIFI credentials in secret.h +const char *ssid = WIFI_SSID; +const char *password = WIFI_PASS; + +PsychicHttpServer server; +PsychicWebSocketHandler websocketHandler; +PsychicEventSource eventSource; + +const char *htmlContent = R"( + + + + Sample HTML + + +

Hello, World!

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+ + +)"; + +bool connectToWifi() +{ + Serial.println(); + Serial.print("[WiFi] Connecting to "); + Serial.println(ssid); + + WiFi.setSleep(false); + WiFi.useStaticBuffers(true); + + WiFi.begin(ssid, password); + + // Will try for about 10 seconds (20x 500ms) + int tryDelay = 500; + int numberOfTries = 20; + + // Wait for the WiFi event + while (true) + { + switch (WiFi.status()) + { + case WL_NO_SSID_AVAIL: + Serial.println("[WiFi] SSID not found"); + break; + case WL_CONNECT_FAILED: + Serial.print("[WiFi] Failed - WiFi not connected! Reason: "); + return false; + break; + case WL_CONNECTION_LOST: + Serial.println("[WiFi] Connection was lost"); + break; + case WL_SCAN_COMPLETED: + Serial.println("[WiFi] Scan is completed"); + break; + case WL_DISCONNECTED: + Serial.println("[WiFi] WiFi is disconnected"); + break; + case WL_CONNECTED: + Serial.println("[WiFi] WiFi is connected!"); + Serial.print("[WiFi] IP address: "); + Serial.println(WiFi.localIP()); + return true; + break; + default: + Serial.print("[WiFi] WiFi Status: "); + Serial.println(WiFi.status()); + break; + } + delay(tryDelay); + + if (numberOfTries <= 0) + { + Serial.print("[WiFi] Failed to connect to WiFi!"); + // Use disconnect function to force stop trying to connect + WiFi.disconnect(); + return false; + } + else + { + numberOfTries--; + } + } + + return false; +} + +void setup() +{ + Serial.begin(115200); + delay(10); + Serial.println("PsychicHTTP Benchmark"); + + if (connectToWifi()) + { + if(!LittleFS.begin()) + { + Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode"); + return; + } + + //start our server + server.listen(80); + + //our index + server.on("/", HTTP_GET, [](PsychicRequest *request) + { + return request->reply(200, "text/html", htmlContent); + }); + + //serve static files from LittleFS/www on / + server.serveStatic("/", LittleFS, "/www/"); + + //a websocket echo server + websocketHandler.onOpen([](PsychicWebSocketClient *client) { + client->sendMessage("Hello!"); + }); + websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) { + request->reply(frame); + return ESP_OK; + }); + server.on("/ws", &websocketHandler); + + //EventSource server + eventSource.onOpen([](PsychicEventSourceClient *client) { + client->send("Hello", NULL, millis(), 1000); + }); + server.on("/events", &eventSource); + + //api - parameters passed in via query eg. /api/endpoint?foo=bar + server.on("/api", HTTP_GET, [](PsychicRequest *request) + { + //create a response object + StaticJsonDocument<128> output; + output["msg"] = "status"; + output["status"] = "success"; + output["millis"] = millis(); + + //work with some params + if (request->hasParam("foo")) + { + String foo = request->getParam("foo")->value(); + output["foo"] = foo; + } + + //serialize and return + String jsonBuffer; + serializeJson(output, jsonBuffer); + return request->reply(200, "application/json", jsonBuffer.c_str()); + }); + } +} + +unsigned long last; +void loop() +{ + if (millis() - last > 1000) + { + Serial.printf("Free Heap: %d\n", esp_get_free_heap_size()); + last = millis(); + } +} \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/psychichttp/src/secret.h b/lib/PsychicHttp/benchmark/psychichttp/src/secret.h new file mode 100644 index 0000000..6d4bb15 --- /dev/null +++ b/lib/PsychicHttp/benchmark/psychichttp/src/secret.h @@ -0,0 +1,2 @@ +#define WIFI_SSID "Your_SSID" +#define WIFI_PASS "Your_PASS" \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/psychichttp/test/README b/lib/PsychicHttp/benchmark/psychichttp/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/lib/PsychicHttp/benchmark/psychichttp/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/lib/PsychicHttp/benchmark/psychichttps/.gitignore b/lib/PsychicHttp/benchmark/psychichttps/.gitignore new file mode 100644 index 0000000..9e5f911 --- /dev/null +++ b/lib/PsychicHttp/benchmark/psychichttps/.gitignore @@ -0,0 +1,6 @@ +.pio +.vscode/ +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/lib/PsychicHttp/benchmark/psychichttps/data/server.crt b/lib/PsychicHttp/benchmark/psychichttps/data/server.crt new file mode 100644 index 0000000..34a1e01 --- /dev/null +++ b/lib/PsychicHttp/benchmark/psychichttps/data/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL +BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMTgx +MDE3MTEzMjU3WhcNMjgxMDE0MTEzMjU3WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ +UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T +sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k +qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd +GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4 +sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb +jAn4PCuR2akdF4G8WLUeDWECAwEAAaNTMFEwHQYDVR0OBBYEFMnmdJKOEepXrHI/ +ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud +EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADiXIGEkSsN0SLSfCF1VNWO3 +emBurfOcDq4EGEaxRKAU0814VEmU87btIDx80+z5Dbf+GGHCPrY7odIkxGNn0DJY +W1WcF+DOcbiWoUN6DTkAML0SMnp8aGj9ffx3x+qoggT+vGdWVVA4pgwqZT7Ybntx +bkzcNFW0sqmCv4IN1t4w6L0A87ZwsNwVpre/j6uyBw7s8YoJHDLRFT6g7qgn0tcN +ZufhNISvgWCVJQy/SZjNBHSpnIdCUSJAeTY2mkM4sGxY0Widk8LnjydxZUSxC3Nl +hb6pnMh3jRq4h0+5CZielA4/a+TdrNPv/qok67ot/XJdY3qHCCd8O2b14OVq9jo= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/psychichttps/data/server.key b/lib/PsychicHttp/benchmark/psychichttps/data/server.key new file mode 100644 index 0000000..a591325 --- /dev/null +++ b/lib/PsychicHttp/benchmark/psychichttps/data/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH +JioMD7U7BitLgpcYPi8Cid1l7snt6Kp546iQsDBJ3l8xnRtPU7ANEsjT8KxIHmyw +h/NGp94FlOKRw3ahh3yUGtowS9vdHv+S+TAfuj07NjSnKIyv5KnGZJ+fDFl4Q1tT +aQJybY1Z4itirL6/2CGEm8g/iYhLNDBsRMfpDpfXe4URyWiM3Rhf7ztqZdveb9al +3pAJZIDTLWCFQI1MvQjKamkAQkES/gZj0iUZFwbGJPBj54nkuLFLKedw7DbwgrVg +0+n3fQ9b/gQepw5PxQjyobY2DsDgGZV+MFjUmaUTa+XX68SrG4wJ+DwrkdmpHReB +vFi1Hg1hAgMBAAECggEAaTCnZkl/7qBjLexIryC/CBBJyaJ70W1kQ7NMYfniWwui +f0aRxJgOdD81rjTvkINsPp+xPRQO6oOadjzdjImYEuQTqrJTEUnntbu924eh+2D9 +Mf2CAanj0mglRnscS9mmljZ0KzoGMX6Z/EhnuS40WiJTlWlH6MlQU/FDnwC6U34y +JKy6/jGryfsx+kGU/NRvKSru6JYJWt5v7sOrymHWD62IT59h3blOiP8GMtYKeQlX +49om9Mo1VTIFASY3lrxmexbY+6FG8YO+tfIe0tTAiGrkb9Pz6tYbaj9FjEWOv4Vc ++3VMBUVdGJjgqvE8fx+/+mHo4Rg69BUPfPSrpEg7sQKBgQDlL85G04VZgrNZgOx6 +pTlCCl/NkfNb1OYa0BELqWINoWaWQHnm6lX8YjrUjwRpBF5s7mFhguFjUjp/NW6D +0EEg5BmO0ePJ3dLKSeOA7gMo7y7kAcD/YGToqAaGljkBI+IAWK5Su5yldrECTQKG +YnMKyQ1MWUfCYEwHtPvFvE5aPwKBgQDFBWXekpxHIvt/B41Cl/TftAzE7/f58JjV +MFo/JCh9TDcH6N5TMTRS1/iQrv5M6kJSSrHnq8pqDXOwfHLwxetpk9tr937VRzoL +CuG1Ar7c1AO6ujNnAEmUVC2DppL/ck5mRPWK/kgLwZSaNcZf8sydRgphsW1ogJin +7g0nGbFwXwKBgQCPoZY07Pr1TeP4g8OwWTu5F6dSvdU2CAbtZthH5q98u1n/cAj1 +noak1Srpa3foGMTUn9CHu+5kwHPIpUPNeAZZBpq91uxa5pnkDMp3UrLIRJ2uZyr8 +4PxcknEEh8DR5hsM/IbDcrCJQglM19ZtQeW3LKkY4BsIxjDf45ymH407IQKBgE/g +Ul6cPfOxQRlNLH4VMVgInSyyxWx1mODFy7DRrgCuh5kTVh+QUVBM8x9lcwAn8V9/ +nQT55wR8E603pznqY/jX0xvAqZE6YVPcw4kpZcwNwL1RhEl8GliikBlRzUL3SsW3 +q30AfqEViHPE3XpE66PPo6Hb1ymJCVr77iUuC3wtAoGBAIBrOGunv1qZMfqmwAY2 +lxlzRgxgSiaev0lTNxDzZkmU/u3dgdTwJ5DDANqPwJc6b8SGYTp9rQ0mbgVHnhIB +jcJQBQkTfq6Z0H6OoTVi7dPs3ibQJFrtkoyvYAbyk36quBmNRjVh6rc8468bhXYr +v/t+MeGJP/0Zw8v/X2CFll96 +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/psychichttps/data/www/alien.png b/lib/PsychicHttp/benchmark/psychichttps/data/www/alien.png new file mode 100644 index 0000000..a030da0 Binary files /dev/null and b/lib/PsychicHttp/benchmark/psychichttps/data/www/alien.png differ diff --git a/lib/PsychicHttp/benchmark/psychichttps/include/README b/lib/PsychicHttp/benchmark/psychichttps/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/lib/PsychicHttp/benchmark/psychichttps/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/PsychicHttp/benchmark/psychichttps/lib/README b/lib/PsychicHttp/benchmark/psychichttps/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/PsychicHttp/benchmark/psychichttps/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/PsychicHttp/benchmark/psychichttps/platformio.ini b/lib/PsychicHttp/benchmark/psychichttps/platformio.ini new file mode 100644 index 0000000..868f0ca --- /dev/null +++ b/lib/PsychicHttp/benchmark/psychichttps/platformio.ini @@ -0,0 +1,22 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env] +platform = espressif32 +framework = arduino +board = esp32dev +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder +lib_deps = + https://github.com/hoeken/PsychicHttp + bblanchon/ArduinoJson +board_build.filesystem = littlefs + +[env:default] diff --git a/lib/PsychicHttp/benchmark/psychichttps/src/main.cpp b/lib/PsychicHttp/benchmark/psychichttps/src/main.cpp new file mode 100644 index 0000000..2639758 --- /dev/null +++ b/lib/PsychicHttp/benchmark/psychichttps/src/main.cpp @@ -0,0 +1,240 @@ +/* Wi-Fi STA Connect and Disconnect Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. + +*/ +#include +#include +#include +#include +#include +#include "_secret.h" +#include +#include + +#ifndef WIFI_SSID + #error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there." +#endif + +//Enter your WIFI credentials in secret.h +const char *ssid = WIFI_SSID; +const char *password = WIFI_PASS; + +PsychicHttpsServer server; +PsychicWebSocketHandler websocketHandler; + +String server_cert; +String server_key; + +const char *htmlContent = R"( + + + + Sample HTML + + +

Hello, World!

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin euismod, purus a euismod + rhoncus, urna ipsum cursus massa, eu dictum tellus justo ac justo. Quisque ullamcorper + arcu nec tortor ullamcorper, vel fermentum justo fermentum. Vivamus sed velit ut elit + accumsan congue ut ut enim. Ut eu justo eu lacus varius gravida ut a tellus. Nulla facilisi. + Integer auctor consectetur ultricies. Fusce feugiat, mi sit amet bibendum viverra, orci leo + dapibus elit, id varius sem dui id lacus.

+ + +)"; + +bool connectToWifi() +{ + Serial.println(); + Serial.print("[WiFi] Connecting to "); + Serial.println(ssid); + + WiFi.setSleep(false); + + WiFi.begin(ssid, password); + + // Will try for about 10 seconds (20x 500ms) + int tryDelay = 500; + int numberOfTries = 20; + + // Wait for the WiFi event + while (true) + { + switch (WiFi.status()) + { + case WL_NO_SSID_AVAIL: + Serial.println("[WiFi] SSID not found"); + break; + case WL_CONNECT_FAILED: + Serial.print("[WiFi] Failed - WiFi not connected! Reason: "); + return false; + break; + case WL_CONNECTION_LOST: + Serial.println("[WiFi] Connection was lost"); + break; + case WL_SCAN_COMPLETED: + Serial.println("[WiFi] Scan is completed"); + break; + case WL_DISCONNECTED: + Serial.println("[WiFi] WiFi is disconnected"); + break; + case WL_CONNECTED: + Serial.println("[WiFi] WiFi is connected!"); + Serial.print("[WiFi] IP address: "); + Serial.println(WiFi.localIP()); + return true; + break; + default: + Serial.print("[WiFi] WiFi Status: "); + Serial.println(WiFi.status()); + break; + } + delay(tryDelay); + + if (numberOfTries <= 0) + { + Serial.print("[WiFi] Failed to connect to WiFi!"); + // Use disconnect function to force stop trying to connect + WiFi.disconnect(); + return false; + } + else + { + numberOfTries--; + } + } + + return false; +} + +void setup() +{ + Serial.begin(115200); + delay(10); + Serial.println("PsychicHTTP Benchmark"); + + if (connectToWifi()) + { + if(!LittleFS.begin()) + { + Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode"); + return; + } + + File fp = LittleFS.open("/server.crt"); + if (fp) { + server_cert = fp.readString(); + } else { + Serial.println("server.pem not found, SSL not available"); + return; + } + fp.close(); + + File fp2 = LittleFS.open("/server.key"); + if (fp2) { + server_key = fp2.readString(); + } else { + Serial.println("server.key not found, SSL not available"); + return; + } + fp2.close(); + + //start our server + server.listen(443, server_cert.c_str(), server_key.c_str()); + + //our index + server.on("/", HTTP_GET, [](PsychicRequest *request) + { + return request->reply(200, "text/html", htmlContent); + }); + + //serve static files from LittleFS/www on / + server.serveStatic("/", LittleFS, "/www/"); + + //a websocket echo server + websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) { + request->reply(frame); + return ESP_OK; + }); + server.on("/ws", &websocketHandler); + + //api - parameters passed in via query eg. /api/endpoint?foo=bar + server.on("/api", HTTP_GET, [](PsychicRequest *request) + { + //create a response object + StaticJsonDocument<128> output; + output["msg"] = "status"; + output["status"] = "success"; + output["millis"] = millis(); + + //work with some params + if (request->hasParam("foo")) + { + String foo = request->getParam("foo")->value(); + output["foo"] = foo; + } + + //serialize and return + String jsonBuffer; + serializeJson(output, jsonBuffer); + return request->reply(200, "application/json", jsonBuffer.c_str()); + }); + } +} + +unsigned long last; +void loop() +{ + if (millis() - last > 1000) + { + Serial.printf("Free Heap: %d\n", esp_get_free_heap_size()); + last = millis(); + } +} \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/psychichttps/src/secret.h b/lib/PsychicHttp/benchmark/psychichttps/src/secret.h new file mode 100644 index 0000000..6d4bb15 --- /dev/null +++ b/lib/PsychicHttp/benchmark/psychichttps/src/secret.h @@ -0,0 +1,2 @@ +#define WIFI_SSID "Your_SSID" +#define WIFI_PASS "Your_PASS" \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/psychichttps/test/README b/lib/PsychicHttp/benchmark/psychichttps/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/lib/PsychicHttp/benchmark/psychichttps/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/lib/PsychicHttp/benchmark/results/arduinomongoose-http-loadtest.log b/lib/PsychicHttp/benchmark/results/arduinomongoose-http-loadtest.log new file mode 100644 index 0000000..24a6e34 --- /dev/null +++ b/lib/PsychicHttp/benchmark/results/arduinomongoose-http-loadtest.log @@ -0,0 +1,1172 @@ + + +CLIENTS: *** 1 *** + +Running 60s test @ http://192.168.2.131/ +1 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 720 ms │ 14880 ms │ 29087 ms │ 29519 ms │ 15024.03 ms │ 8572.14 ms │ 29737 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 11 │ 11 │ 12 │ 14 │ 12.47 │ 0.87 │ 11 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 48.2 kB │ 48.2 kB │ 52.6 kB │ 61.3 kB │ 54.6 kB │ 3.79 kB │ 48.2 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 748 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +1k requests in 60.07s, 3.28 MB read +748 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +1 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 781 ms │ 15061 ms │ 29571 ms │ 30046 ms │ 15137.29 ms │ 8833.98 ms │ 30393 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 17 │ 18 │ 20 │ 23 │ 20.34 │ 1.27 │ 17 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 2.99 kB │ 3.15 kB │ 3.52 kB │ 4.03 kB │ 3.57 kB │ 220 B │ 2.99 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 1220 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +2k requests in 60.06s, 214 kB read +1k errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +1 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 969 ms │ 15300 ms │ 29831 ms │ 30132 ms │ 15143.83 ms │ 8807.54 ms │ 30385 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬─────────┬─────────┬─────────┬────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 3 │ 3 │ 3 │ 4 │ 3.15 │ 0.36 │ 3 │ +├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 86.1 kB │ 86.1 kB │ 86.1 kB │ 115 kB │ 90.4 kB │ 10.3 kB │ 86.1 kB │ +└───────────┴─────────┴─────────┴─────────┴────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 189 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +378 requests in 60.06s, 5.43 MB read +188 errors (0 timeouts) + + +---------------- + + + +CLIENTS: *** 2 *** + +Running 60s test @ http://192.168.2.131/ +2 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 803 ms │ 14773 ms │ 29269 ms │ 29642 ms │ 14925.25 ms │ 8556.82 ms │ 29974 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬─────────┬─────────┬─────────┬───────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼───────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 17 │ 17 │ 19 │ 21 │ 18.9 │ 0.98 │ 17 │ +├───────────┼─────────┼─────────┼─────────┼───────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 74.5 kB │ 74.5 kB │ 83.3 kB │ 92 kB │ 82.8 kB │ 4.29 kB │ 74.5 kB │ +└───────────┴─────────┴─────────┴─────────┴───────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 1134 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +2k requests in 60.06s, 4.97 MB read +29 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +2 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 772 ms │ 15252 ms │ 29307 ms │ 29709 ms │ 15094.92 ms │ 8732.51 ms │ 30070 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 26 │ 27 │ 29 │ 32 │ 29.72 │ 1.43 │ 26 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 4.58 kB │ 4.75 kB │ 5.11 kB │ 5.63 kB │ 5.23 kB │ 251 B │ 4.58 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 1783 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.06s, 314 kB read +1k errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +2 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 856 ms │ 15635 ms │ 28880 ms │ 29310 ms │ 15261.99 ms │ 8577.65 ms │ 29700 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 4 │ 4 │ 5 │ 6 │ 4.64 │ 0.64 │ 4 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 115 kB │ 115 kB │ 144 kB │ 172 kB │ 133 kB │ 18.1 kB │ 115 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 278 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +558 requests in 60.06s, 7.98 MB read +17 errors (0 timeouts) + + +---------------- + + + +CLIENTS: *** 3 *** + +Running 60s test @ http://192.168.2.131/ +3 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 794 ms │ 15166 ms │ 29323 ms │ 29697 ms │ 15066.78 ms │ 8676.25 ms │ 30114 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬─────────┬────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 22 │ 23 │ 26 │ 29 │ 25.99 │ 1.42 │ 22 │ +├───────────┼─────────┼────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 96.4 kB │ 101 kB │ 114 kB │ 127 kB │ 114 kB │ 6.22 kB │ 96.4 kB │ +└───────────┴─────────┴────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 1559 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +3k requests in 60.06s, 6.83 MB read +14 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +3 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 826 ms │ 14949 ms │ 29377 ms │ 29790 ms │ 15049.95 ms │ 8720.15 ms │ 30168 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 32 │ 32 │ 36 │ 39 │ 36.1 │ 1.82 │ 32 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 5.63 kB │ 5.63 kB │ 6.34 kB │ 6.87 kB │ 6.36 kB │ 319 B │ 5.63 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 2166 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.1s, 381 kB read +1k errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +3 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬─────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼─────────┼──────────┤ +│ Latency │ 995 ms │ 15127 ms │ 29464 ms │ 29993 ms │ 15219.94 ms │ 8704 ms │ 30258 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴─────────┴──────────┘ +┌───────────┬─────────┬─────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 3 │ 3 │ 6 │ 6 │ 5.62 │ 0.99 │ 3 │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 86.1 kB │ 86.1 kB │ 172 kB │ 172 kB │ 161 kB │ 28.3 kB │ 86.1 kB │ +└───────────┴─────────┴─────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 337 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +677 requests in 60.07s, 9.67 MB read +54 errors (0 timeouts) + + +---------------- + + + +CLIENTS: *** 4 *** + +Running 60s test @ http://192.168.2.131/ +4 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 702 ms │ 15255 ms │ 29811 ms │ 30226 ms │ 15144.93 ms │ 8830.16 ms │ 30616 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┤ +│ Req/Sec │ 26 │ 27 │ 29 │ 32 │ 29.05 │ 1.58 │ 26 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┤ +│ Bytes/Sec │ 114 kB │ 118 kB │ 127 kB │ 140 kB │ 127 kB │ 6.9 kB │ 114 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 1743 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +3k requests in 60.06s, 7.63 MB read +20 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +4 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬───────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼───────────┼──────────┤ +│ Latency │ 762 ms │ 15231 ms │ 29096 ms │ 29534 ms │ 15112.66 ms │ 8660.6 ms │ 29997 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴───────────┴──────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼────────┼───────┼─────────┤ +│ Req/Sec │ 35 │ 35 │ 41 │ 44 │ 40.89 │ 1.9 │ 35 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼────────┼───────┼─────────┤ +│ Bytes/Sec │ 6.16 kB │ 6.16 kB │ 7.22 kB │ 7.75 kB │ 7.2 kB │ 334 B │ 6.16 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 2453 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.06s, 432 kB read +977 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +4 connections +1 workers + + +┌─────────┬─────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼─────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 1175 ms │ 15413 ms │ 29485 ms │ 30066 ms │ 15217.07 ms │ 8604.35 ms │ 30126 ms │ +└─────────┴─────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 4 │ 4 │ 8 │ 8 │ 6.47 │ 1.87 │ 4 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 115 kB │ 115 kB │ 230 kB │ 230 kB │ 186 kB │ 53.6 kB │ 115 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 388 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +780 requests in 60.05s, 11.1 MB read +39 errors (0 timeouts) + + +---------------- + + + +CLIENTS: *** 5 *** + +Running 60s test @ http://192.168.2.131/ +5 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 802 ms │ 14978 ms │ 29391 ms │ 29863 ms │ 15042.57 ms │ 8642.88 ms │ 30280 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┤ +│ Req/Sec │ 26 │ 29 │ 33 │ 37 │ 33.19 │ 2.13 │ 26 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┤ +│ Bytes/Sec │ 114 kB │ 127 kB │ 145 kB │ 162 kB │ 145 kB │ 9.3 kB │ 114 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 1991 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.07s, 8.72 MB read +10 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +5 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 724 ms │ 15356 ms │ 29304 ms │ 29791 ms │ 15198.71 ms │ 8761.23 ms │ 30174 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 40 │ 40 │ 45 │ 50 │ 44.84 │ 2.78 │ 40 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 7.04 kB │ 7.04 kB │ 7.92 kB │ 8.81 kB │ 7.89 kB │ 489 B │ 7.04 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 2690 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.07s, 473 kB read +603 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +5 connections +1 workers + + +┌─────────┬─────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼─────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 1250 ms │ 15289 ms │ 29607 ms │ 30193 ms │ 15345.82 ms │ 8719.55 ms │ 30354 ms │ +└─────────┴─────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 5 │ 5 │ 6 │ 10 │ 7.25 │ 2.25 │ 5 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 144 kB │ 144 kB │ 172 kB │ 287 kB │ 208 kB │ 64.5 kB │ 144 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 435 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +875 requests in 60.06s, 12.5 MB read +36 errors (0 timeouts) + + +---------------- + + + +CLIENTS: *** 6 *** + +Running 60s test @ http://192.168.2.131/ +6 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 847 ms │ 14920 ms │ 28899 ms │ 29392 ms │ 15008.54 ms │ 8456.33 ms │ 29870 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 33 │ 33 │ 36 │ 39 │ 35.75 │ 1.75 │ 33 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 145 kB │ 145 kB │ 158 kB │ 171 kB │ 157 kB │ 7.65 kB │ 145 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 2145 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.06s, 9.4 MB read +3 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +6 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 890 ms │ 14807 ms │ 29142 ms │ 29732 ms │ 14934.33 ms │ 8513.39 ms │ 30168 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬─────────┬─────────┬────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 44 │ 44 │ 48 │ 53 │ 48.3 │ 2.22 │ 44 │ +├───────────┼─────────┼─────────┼────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 7.75 kB │ 7.75 kB │ 8.5 kB │ 9.38 kB │ 8.54 kB │ 395 B │ 7.74 kB │ +└───────────┴─────────┴─────────┴────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 2898 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +6k requests in 60.07s, 512 kB read +386 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +6 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼────────────┼────────────┼──────────┤ +│ Latency │ 853 ms │ 15325 ms │ 29742 ms │ 30578 ms │ 15287.1 ms │ 8714.92 ms │ 30650 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴────────────┴────────────┴──────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬───────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Req/Sec │ 6 │ 6 │ 6 │ 12 │ 7.7 │ 2.44 │ 6 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Bytes/Sec │ 172 kB │ 172 kB │ 172 kB │ 345 kB │ 221 kB │ 70 kB │ 172 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴───────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 462 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +930 requests in 60.06s, 13.3 MB read +24 errors (0 timeouts) + + +---------------- + + + +CLIENTS: *** 7 *** + +Running 60s test @ http://192.168.2.131/ +7 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 785 ms │ 15141 ms │ 29506 ms │ 29981 ms │ 15124.91 ms │ 8769.84 ms │ 30379 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┤ +│ Req/Sec │ 32 │ 33 │ 37 │ 40 │ 36.64 │ 2.04 │ 32 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┤ +│ Bytes/Sec │ 140 kB │ 145 kB │ 162 kB │ 175 kB │ 160 kB │ 8.9 kB │ 140 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 2198 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.06s, 9.63 MB read +1 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +7 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 773 ms │ 15322 ms │ 29472 ms │ 29911 ms │ 15145.75 ms │ 8792.14 ms │ 30238 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 45 │ 45 │ 51 │ 56 │ 50.62 │ 3.07 │ 45 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 7.97 kB │ 7.97 kB │ 9.03 kB │ 9.92 kB │ 8.96 kB │ 541 B │ 7.96 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3037 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +6k requests in 60.06s, 538 kB read +252 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +7 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 903 ms │ 15426 ms │ 29337 ms │ 30187 ms │ 15395.14 ms │ 8592.24 ms │ 30239 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬───────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Req/Sec │ 7 │ 7 │ 7 │ 14 │ 8.06 │ 2.03 │ 7 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Bytes/Sec │ 201 kB │ 201 kB │ 201 kB │ 402 kB │ 231 kB │ 58 kB │ 201 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴───────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 483 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +973 requests in 60.06s, 13.9 MB read +27 errors (0 timeouts) + + +---------------- + + + +CLIENTS: *** 8 *** + +Running 60s test @ http://192.168.2.131/ +8 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 947 ms │ 15230 ms │ 29194 ms │ 29710 ms │ 15139.81 ms │ 8698.37 ms │ 30418 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 31 │ 33 │ 38 │ 43 │ 37.92 │ 2.7 │ 31 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 136 kB │ 145 kB │ 167 kB │ 188 kB │ 166 kB │ 11.8 kB │ 136 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 2275 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.06s, 9.96 MB read +1 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +8 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 818 ms │ 15102 ms │ 29043 ms │ 29502 ms │ 14999.69 ms │ 8454.75 ms │ 30295 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 44 │ 45 │ 51 │ 60 │ 51.37 │ 3.35 │ 44 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 7.79 kB │ 7.97 kB │ 9.03 kB │ 10.6 kB │ 9.09 kB │ 591 B │ 7.79 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3082 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +6k requests in 60s, 546 kB read +222 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +8 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 951 ms │ 15565 ms │ 30182 ms │ 30252 ms │ 15396.59 ms │ 8860.08 ms │ 31138 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 7 │ 8 │ 8 │ 14 │ 8.56 │ 1.59 │ 7 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 201 kB │ 230 kB │ 230 kB │ 402 kB │ 245 kB │ 45.5 kB │ 201 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 513 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +1k requests in 60.06s, 14.7 MB read +31 errors (0 timeouts) + + +---------------- + + + +CLIENTS: *** 9 *** + +Running 60s test @ http://192.168.2.131/ +9 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬───────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼───────────┼──────────┤ +│ Latency │ 951 ms │ 15246 ms │ 28914 ms │ 29402 ms │ 15108.58 ms │ 8543.6 ms │ 30524 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴───────────┴──────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 31 │ 32 │ 37 │ 43 │ 37.42 │ 2.9 │ 31 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 136 kB │ 140 kB │ 162 kB │ 188 kB │ 164 kB │ 12.7 kB │ 136 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 2245 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.05s, 9.83 MB read +1 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +9 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼────────────┼────────────┼──────────┤ +│ Latency │ 885 ms │ 15043 ms │ 29217 ms │ 29730 ms │ 14953.4 ms │ 8530.16 ms │ 30399 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴────────────┴────────────┴──────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 44 │ 46 │ 52 │ 58 │ 52.1 │ 3.22 │ 44 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 7.79 kB │ 8.14 kB │ 9.21 kB │ 10.3 kB │ 9.22 kB │ 570 B │ 7.79 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3126 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +6k requests in 60.07s, 553 kB read +189 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +9 connections +1 workers + + +┌─────────┬─────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼─────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 1079 ms │ 15444 ms │ 28978 ms │ 29935 ms │ 15389.63 ms │ 8469.15 ms │ 30003 ms │ +└─────────┴─────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬─────┬──────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 9 │ 10 │ 8.56 │ 2.21 │ 1 │ +├───────────┼─────┼──────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 258 kB │ 287 kB │ 245 kB │ 63.4 kB │ 28.7 kB │ +└───────────┴─────┴──────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 513 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +1k requests in 60.06s, 14.7 MB read +20 errors (0 timeouts) + + +---------------- + + + +CLIENTS: *** 10 *** + +Running 60s test @ http://192.168.2.131/ +10 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 996 ms │ 15183 ms │ 29343 ms │ 29946 ms │ 15135.07 ms │ 8665.13 ms │ 30794 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 32 │ 32 │ 37 │ 43 │ 37.59 │ 2.43 │ 32 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 140 kB │ 140 kB │ 162 kB │ 188 kB │ 165 kB │ 10.6 kB │ 140 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 2255 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.06s, 9.88 MB read +5 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +10 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 876 ms │ 15390 ms │ 29322 ms │ 29837 ms │ 15193.95 ms │ 8726.45 ms │ 30887 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 46 │ 47 │ 52 │ 60 │ 52.42 │ 3.63 │ 46 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 8.14 kB │ 8.32 kB │ 9.21 kB │ 10.6 kB │ 9.28 kB │ 642 B │ 8.14 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3145 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +6k requests in 60.07s, 557 kB read +193 errors (0 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +10 connections +1 workers + + +┌─────────┬─────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼─────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 1126 ms │ 15812 ms │ 30288 ms │ 31239 ms │ 15913.56 ms │ 8747.22 ms │ 32439 ms │ +└─────────┴─────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬─────┬─────────┬────────┬────────┬────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼─────────┼────────┼────────┼────────┼───────┼─────────┤ +│ Req/Sec │ 0 │ 2 │ 10 │ 10 │ 8.84 │ 2.37 │ 2 │ +├───────────┼─────┼─────────┼────────┼────────┼────────┼───────┼─────────┤ +│ Bytes/Sec │ 0 B │ 57.4 kB │ 287 kB │ 287 kB │ 254 kB │ 68 kB │ 57.4 kB │ +└───────────┴─────┴─────────┴────────┴────────┴────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 530 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +1k requests in 60.06s, 15.2 MB read +42 errors (0 timeouts) + + +---------------- + + + +CLIENTS: *** 15 *** + +Running 60s test @ http://192.168.2.131/ +15 connections +1 workers + +node:internal/event_target:1084 + process.nextTick(() => { throw err; }); + ^ + +TypeError: colorize is not a function + at /Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:46:31 + at Array.forEach () + at printResult (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:43:43) + at EventEmitter. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/progressTracker.js:79:28) + at EventEmitter.emit (node:events:527:35) + at _cb (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/init.js:76:13) + at handleFinish (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:41:5) + at Worker. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:78:13) + at Worker.emit (node:events:515:28) + at MessagePort. (node:internal/worker:263:53) + +Node.js v21.1.0 + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +15 connections +1 workers + + +┌─────────┬────────┬──────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼──────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 690 ms │ 12131 ms │ 28685 ms │ 29798 ms │ 13051.88 ms │ 8385.32 ms │ 32739 ms │ +└─────────┴────────┴──────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 46 │ 50 │ 56 │ 63 │ 55.99 │ 3.53 │ 46 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 8.14 kB │ 8.86 kB │ 9.92 kB │ 11.2 kB │ 9.91 kB │ 624 B │ 8.14 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3359 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +7k requests in 60.06s, 595 kB read +269 errors (5 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +15 connections +1 workers + +node:internal/event_target:1084 + process.nextTick(() => { throw err; }); + ^ + +TypeError: colorize is not a function + at /Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:46:31 + at Array.forEach () + at printResult (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:43:43) + at EventEmitter. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/progressTracker.js:79:28) + at EventEmitter.emit (node:events:527:35) + at _cb (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/init.js:76:13) + at handleFinish (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:41:5) + at Worker. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:78:13) + at Worker.emit (node:events:515:28) + at MessagePort. (node:internal/worker:263:53) + +Node.js v21.1.0 + + +---------------- + + + +CLIENTS: *** 20 *** + +Running 60s test @ http://192.168.2.131/ +20 connections +1 workers + +node:internal/event_target:1084 + process.nextTick(() => { throw err; }); + ^ + +TypeError: colorize is not a function + at /Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:46:31 + at Array.forEach () + at printResult (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:43:43) + at EventEmitter. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/progressTracker.js:79:28) + at EventEmitter.emit (node:events:527:35) + at _cb (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/init.js:76:13) + at handleFinish (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:41:5) + at Worker. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:78:13) + at Worker.emit (node:events:515:28) + at MessagePort. (node:internal/worker:263:53) + +Node.js v21.1.0 + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +20 connections +1 workers + + +┌─────────┬────────┬─────────┬──────────┬──────────┬────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼──────────┼──────────┼────────────┼────────────┼──────────┤ +│ Latency │ 377 ms │ 5749 ms │ 19598 ms │ 21657 ms │ 6953.55 ms │ 5299.16 ms │ 23969 ms │ +└─────────┴────────┴─────────┴──────────┴──────────┴────────────┴────────────┴──────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 45 │ 45 │ 55 │ 60 │ 54.27 │ 3.9 │ 45 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 7.88 kB │ 7.88 kB │ 9.63 kB │ 10.6 kB │ 9.53 kB │ 687 B │ 7.88 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3256 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +7k requests in 60.06s, 571 kB read +312 errors (30 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +20 connections +1 workers + +node:internal/event_target:1084 + process.nextTick(() => { throw err; }); + ^ + +TypeError: colorize is not a function + at /Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:46:31 + at Array.forEach () + at printResult (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:43:43) + at EventEmitter. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/progressTracker.js:79:28) + at EventEmitter.emit (node:events:527:35) + at _cb (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/init.js:76:13) + at handleFinish (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:41:5) + at Worker. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:78:13) + at Worker.emit (node:events:515:28) + at MessagePort. (node:internal/worker:263:53) + +Node.js v21.1.0 + + +---------------- + diff --git a/lib/PsychicHttp/benchmark/results/arduinomongoose-websocket-loadtest.log b/lib/PsychicHttp/benchmark/results/arduinomongoose-websocket-loadtest.log new file mode 100644 index 0000000..8c18045 --- /dev/null +++ b/lib/PsychicHttp/benchmark/results/arduinomongoose-websocket-loadtest.log @@ -0,0 +1,246 @@ + + +CLIENTS: *** 1 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 1 +Agent: none + +Completed requests: 3750 +Total errors: 0 +Total time: 60.001 s +Mean latency: 15.5 ms +Effective rps: 62 + +Percentage of requests served within a certain time + 50% 12 ms + 90% 18 ms + 95% 36 ms + 99% 80 ms + 100% 223 ms (longest request) + + +CLIENTS: *** 2 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 2 +Agent: none + +Completed requests: 5795 +Total errors: 0 +Total time: 60.004 s +Mean latency: 20.2 ms +Effective rps: 97 + +Percentage of requests served within a certain time + 50% 16 ms + 90% 27 ms + 95% 64 ms + 99% 86 ms + 100% 108 ms (longest request) + + +CLIENTS: *** 3 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 3 +Agent: none + +Completed requests: 7445 +Total errors: 0 +Total time: 60.003 s +Mean latency: 23.6 ms +Effective rps: 124 + +Percentage of requests served within a certain time + 50% 19 ms + 90% 32 ms + 95% 70 ms + 99% 92 ms + 100% 121 ms (longest request) + + +CLIENTS: *** 4 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 4 +Agent: none + +Completed requests: 8751 +Total errors: 0 +Total time: 60.005 s +Mean latency: 26.9 ms +Effective rps: 146 + +Percentage of requests served within a certain time + 50% 22 ms + 90% 38 ms + 95% 73 ms + 99% 95 ms + 100% 115 ms (longest request) + + +CLIENTS: *** 5 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 5 +Agent: none + +Completed requests: 9953 +Total errors: 0 +Total time: 60.004 s +Mean latency: 29.6 ms +Effective rps: 166 + +Percentage of requests served within a certain time + 50% 25 ms + 90% 42 ms + 95% 74 ms + 99% 93 ms + 100% 116 ms (longest request) + + +CLIENTS: *** 6 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 6 +Agent: none + +Completed requests: 10871 +Total errors: 0 +Total time: 60.005 s +Mean latency: 32.6 ms +Effective rps: 181 + +Percentage of requests served within a certain time + 50% 27 ms + 90% 50 ms + 95% 82 ms + 99% 100 ms + 100% 116 ms (longest request) + + +CLIENTS: *** 7 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 7 +Agent: none + +Completed requests: 11777 +Total errors: 0 +Total time: 60.003 s +Mean latency: 35.1 ms +Effective rps: 196 + +Percentage of requests served within a certain time + 50% 30 ms + 90% 66 ms + 95% 83 ms + 99% 101 ms + 100% 137 ms (longest request) + + +CLIENTS: *** 8 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 8 +Running on cores: 2 +Agent: none + +Completed requests: 11639 +Total errors: 0 +Total time: 60.004 s +Mean latency: 35.4 ms +Effective rps: 194 + +Percentage of requests served within a certain time + 50% 30 ms + 90% 67 ms + 95% 86 ms + 99% 106 ms + 100% 135 ms (longest request) + + +CLIENTS: *** 10 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 10 +Running on cores: 2 +Agent: none + +Completed requests: 11619 +Total errors: 0 +Total time: 60.004 s +Mean latency: 35.6 ms +Effective rps: 194 + +Percentage of requests served within a certain time + 50% 30 ms + 90% 71 ms + 95% 87 ms + 99% 105 ms + 100% 125 ms (longest request) + + +CLIENTS: *** 16 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 16 +Running on cores: 2 +Agent: none + +Completed requests: 15314 +Total errors: 0 +Total time: 60.005 s +Mean latency: 54.2 ms +Effective rps: 255 + +Percentage of requests served within a certain time + 50% 46 ms + 90% 91 ms + 95% 105 ms + 99% 127 ms + 100% 826 ms (longest request) + + +CLIENTS: *** 20 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 20 +Running on cores: 2 +Agent: none + +Completed requests: 15370 +Total errors: 0 +Total time: 60.005 s +Mean latency: 57.7 ms +Effective rps: 256 + +Percentage of requests served within a certain time + 50% 48 ms + 90% 96 ms + 95% 110 ms + 99% 132 ms + 100% 851 ms (longest request) diff --git a/lib/PsychicHttp/benchmark/results/espasync-http-loadtest.log b/lib/PsychicHttp/benchmark/results/espasync-http-loadtest.log new file mode 100644 index 0000000..cafa123 --- /dev/null +++ b/lib/PsychicHttp/benchmark/results/espasync-http-loadtest.log @@ -0,0 +1,1165 @@ + + +CLIENTS: *** 1 *** + +Running 60s test @ http://192.168.2.131/ +1 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 32 ms │ 40 ms │ 118 ms │ 118 ms │ 54.67 ms │ 29.29 ms │ 118 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬───────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼───────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 1 │ 0.1 │ 0.31 │ 1 │ +├───────────┼─────┼──────┼─────┼─────────┼───────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 4.38 kB │ 438 B │ 1.31 kB │ 4.38 kB │ +└───────────┴─────┴──────┴─────┴─────────┴───────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 6 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +12 requests in 60.08s, 26.3 kB read +5 errors (5 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +1 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 61 ms │ 64 ms │ 131 ms │ 131 ms │ 93.84 ms │ 31.05 ms │ 131 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬───────┬────────┬────────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼───────┼────────┼────────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 1 │ 0.1 │ 0.31 │ 1 │ +├───────────┼─────┼──────┼─────┼───────┼────────┼────────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 174 B │ 17.4 B │ 52.2 B │ 174 B │ +└───────────┴─────┴──────┴─────┴───────┴────────┴────────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 6 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +12 requests in 60.08s, 1.04 kB read +5 errors (5 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +1 connections +1 workers + + +┌─────────┬────────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 135 ms │ 142 ms │ 162 ms │ 162 ms │ 145.84 ms │ 10.32 ms │ 162 ms │ +└─────────┴────────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 1 │ 0.1 │ 0.31 │ 1 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 28.8 kB │ 2.88 kB │ 8.63 kB │ 28.8 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 6 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +12 requests in 60.07s, 173 kB read +5 errors (5 timeouts) + + +---------------- + + + +CLIENTS: *** 2 *** + +Running 60s test @ http://192.168.2.131/ +2 connections +1 workers + + +┌─────────┬───────┬───────┬───────┬───────┬──────────┬──────────┬───────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼───────┼───────┼──────────┼──────────┼───────┤ +│ Latency │ 29 ms │ 52 ms │ 93 ms │ 93 ms │ 56.09 ms │ 17.92 ms │ 93 ms │ +└─────────┴───────┴───────┴───────┴───────┴──────────┴──────────┴───────┘ +┌───────────┬─────┬──────┬─────┬─────────┬───────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼───────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 2 │ 0.2 │ 0.61 │ 2 │ +├───────────┼─────┼──────┼─────┼─────────┼───────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 8.76 kB │ 876 B │ 2.63 kB │ 8.76 kB │ +└───────────┴─────┴──────┴─────┴─────────┴───────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 12 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +24 requests in 60.08s, 52.5 kB read +10 errors (10 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +2 connections +1 workers + + +┌─────────┬───────┬────────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 60 ms │ 106 ms │ 126 ms │ 126 ms │ 102.5 ms │ 21.86 ms │ 126 ms │ +└─────────┴───────┴────────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬───────┬────────┬───────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼───────┼────────┼───────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 2 │ 0.2 │ 0.61 │ 2 │ +├───────────┼─────┼──────┼─────┼───────┼────────┼───────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 348 B │ 34.8 B │ 104 B │ 348 B │ +└───────────┴─────┴──────┴─────┴───────┴────────┴───────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 12 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +24 requests in 60.08s, 2.09 kB read +10 errors (10 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +2 connections +1 workers + + +┌─────────┬────────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 228 ms │ 245 ms │ 268 ms │ 268 ms │ 245.34 ms │ 13.38 ms │ 268 ms │ +└─────────┴────────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 2 │ 0.2 │ 0.61 │ 2 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 57.5 kB │ 5.75 kB │ 17.3 kB │ 57.5 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 12 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +24 requests in 60.07s, 345 kB read +10 errors (10 timeouts) + + +---------------- + + + +CLIENTS: *** 3 *** + +Running 60s test @ http://192.168.2.131/ +3 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 40 ms │ 97 ms │ 133 ms │ 133 ms │ 95.45 ms │ 30.82 ms │ 133 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 3 │ 0.3 │ 0.91 │ 3 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 13.1 kB │ 1.31 kB │ 3.94 kB │ 13.1 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 18 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +36 requests in 60.08s, 78.8 kB read +15 errors (15 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +3 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 55 ms │ 66 ms │ 211 ms │ 211 ms │ 82.78 ms │ 37.03 ms │ 211 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬───────┬────────┬───────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼───────┼────────┼───────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 3 │ 0.3 │ 0.91 │ 3 │ +├───────────┼─────┼──────┼─────┼───────┼────────┼───────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 522 B │ 52.2 B │ 157 B │ 522 B │ +└───────────┴─────┴──────┴─────┴───────┴────────┴───────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 18 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +36 requests in 60.1s, 3.13 kB read +15 errors (15 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +3 connections +1 workers + + +┌─────────┬────────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 215 ms │ 278 ms │ 348 ms │ 348 ms │ 280.56 ms │ 30.51 ms │ 348 ms │ +└─────────┴────────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 3 │ 0.3 │ 0.91 │ 3 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 86.3 kB │ 8.62 kB │ 25.9 kB │ 86.3 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 18 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +36 requests in 60.15s, 518 kB read +15 errors (15 timeouts) + + +---------------- + + + +CLIENTS: *** 4 *** + +Running 60s test @ http://192.168.2.131/ +4 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 29 ms │ 76 ms │ 137 ms │ 137 ms │ 86.55 ms │ 35.16 ms │ 137 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 4 │ 0.4 │ 1.21 │ 4 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 17.5 kB │ 1.75 kB │ 5.25 kB │ 17.5 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 24 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +48 requests in 60.13s, 105 kB read +20 errors (20 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +4 connections +1 workers + + +┌─────────┬───────┬────────┬────────┬────────┬───────────┬───────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼────────┼────────┼───────────┼───────────┼────────┤ +│ Latency │ 54 ms │ 108 ms │ 523 ms │ 523 ms │ 164.71 ms │ 130.96 ms │ 523 ms │ +└─────────┴───────┴────────┴────────┴────────┴───────────┴───────────┴────────┘ +┌───────────┬─────┬──────┬─────┬───────┬────────┬───────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼───────┼────────┼───────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 4 │ 0.4 │ 1.21 │ 4 │ +├───────────┼─────┼──────┼─────┼───────┼────────┼───────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 696 B │ 69.6 B │ 209 B │ 696 B │ +└───────────┴─────┴──────┴─────┴───────┴────────┴───────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 24 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +48 requests in 60.09s, 4.18 kB read +20 errors (20 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +4 connections +1 workers + + +┌─────────┬────────┬────────┬────────┬────────┬───────────┬─────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼────────┼────────┼───────────┼─────────┼────────┤ +│ Latency │ 322 ms │ 379 ms │ 405 ms │ 405 ms │ 372.55 ms │ 20.5 ms │ 405 ms │ +└─────────┴────────┴────────┴────────┴────────┴───────────┴─────────┴────────┘ +┌───────────┬─────┬──────┬─────┬────────┬─────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼────────┼─────────┼─────────┼────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 4 │ 0.4 │ 1.21 │ 4 │ +├───────────┼─────┼──────┼─────┼────────┼─────────┼─────────┼────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 115 kB │ 11.5 kB │ 34.5 kB │ 115 kB │ +└───────────┴─────┴──────┴─────┴────────┴─────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 24 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +48 requests in 60.08s, 690 kB read +20 errors (20 timeouts) + + +---------------- + + + +CLIENTS: *** 5 *** + +Running 60s test @ http://192.168.2.131/ +5 connections +1 workers + + +┌─────────┬───────┬────────┬────────┬────────┬─────────┬─────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼────────┼────────┼─────────┼─────────┼────────┤ +│ Latency │ 36 ms │ 104 ms │ 148 ms │ 148 ms │ 94.7 ms │ 35.9 ms │ 148 ms │ +└─────────┴───────┴────────┴────────┴────────┴─────────┴─────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 5 │ 0.5 │ 1.5 │ 5 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 21.9 kB │ 2.19 kB │ 6.57 kB │ 21.9 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 30 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +60 requests in 60.05s, 131 kB read +25 errors (25 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +5 connections +1 workers + + +┌─────────┬───────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 66 ms │ 123 ms │ 263 ms │ 263 ms │ 127.67 ms │ 37.33 ms │ 263 ms │ +└─────────┴───────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬───────┬──────┬───────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼───────┼──────┼───────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 5 │ 0.5 │ 1.5 │ 5 │ +├───────────┼─────┼──────┼─────┼───────┼──────┼───────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 870 B │ 87 B │ 261 B │ 870 B │ +└───────────┴─────┴──────┴─────┴───────┴──────┴───────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 30 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +60 requests in 60.07s, 5.22 kB read +25 errors (25 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +5 connections +1 workers + + +┌─────────┬────────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 348 ms │ 398 ms │ 465 ms │ 465 ms │ 399.27 ms │ 31.99 ms │ 465 ms │ +└─────────┴────────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬────────┬─────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼────────┼─────────┼─────────┼────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 5 │ 0.5 │ 1.5 │ 5 │ +├───────────┼─────┼──────┼─────┼────────┼─────────┼─────────┼────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 144 kB │ 14.4 kB │ 43.1 kB │ 144 kB │ +└───────────┴─────┴──────┴─────┴────────┴─────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 30 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +60 requests in 60.08s, 863 kB read +25 errors (25 timeouts) + + +---------------- + + + +CLIENTS: *** 6 *** + +Running 60s test @ http://192.168.2.131/ +6 connections +1 workers + + +┌─────────┬───────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 34 ms │ 111 ms │ 188 ms │ 188 ms │ 113.25 ms │ 41.29 ms │ 188 ms │ +└─────────┴───────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 6 │ 0.6 │ 1.81 │ 6 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 26.3 kB │ 2.63 kB │ 7.88 kB │ 26.3 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 36 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +72 requests in 60.07s, 158 kB read +30 errors (30 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +6 connections +1 workers + + +┌─────────┬───────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 61 ms │ 121 ms │ 296 ms │ 296 ms │ 134.95 ms │ 46.95 ms │ 296 ms │ +└─────────┴───────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬───────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼───────┼───────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 6 │ 0.6 │ 1.81 │ 6 │ +├───────────┼─────┼──────┼─────┼─────────┼───────┼───────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 1.05 kB │ 105 B │ 315 B │ 1.05 kB │ +└───────────┴─────┴──────┴─────┴─────────┴───────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 36 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +72 requests in 60.09s, 6.3 kB read +30 errors (30 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +6 connections +1 workers + + +┌─────────┬────────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 322 ms │ 422 ms │ 522 ms │ 522 ms │ 422.98 ms │ 51.58 ms │ 522 ms │ +└─────────┴────────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬────────┬─────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼────────┼─────────┼─────────┼────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 6 │ 0.6 │ 1.81 │ 6 │ +├───────────┼─────┼──────┼─────┼────────┼─────────┼─────────┼────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 173 kB │ 17.2 kB │ 51.7 kB │ 173 kB │ +└───────────┴─────┴──────┴─────┴────────┴─────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 36 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +72 requests in 60.08s, 1.04 MB read +30 errors (30 timeouts) + + +---------------- + + + +CLIENTS: *** 7 *** + +Running 60s test @ http://192.168.2.131/ +7 connections +1 workers + + +┌─────────┬───────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 60 ms │ 129 ms │ 220 ms │ 280 ms │ 127.81 ms │ 51.08 ms │ 280 ms │ +└─────────┴───────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 7 │ 0.7 │ 2.1 │ 7 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 30.7 kB │ 3.06 kB │ 9.19 kB │ 30.6 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 42 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +84 requests in 60.08s, 184 kB read +35 errors (35 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +7 connections +1 workers + + +┌─────────┬───────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 63 ms │ 125 ms │ 252 ms │ 289 ms │ 126.22 ms │ 46.41 ms │ 289 ms │ +└─────────┴───────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬───────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼───────┼───────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 7 │ 0.7 │ 2.1 │ 7 │ +├───────────┼─────┼──────┼─────┼─────────┼───────┼───────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 1.23 kB │ 123 B │ 368 B │ 1.23 kB │ +└───────────┴─────┴──────┴─────┴─────────┴───────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 42 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +84 requests in 60.07s, 7.35 kB read +35 errors (35 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +7 connections +1 workers + + +┌─────────┬────────┬────────┬────────┬────────┬──────────┬───────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼────────┼────────┼──────────┼───────────┼────────┤ +│ Latency │ 144 ms │ 439 ms │ 877 ms │ 921 ms │ 439.3 ms │ 138.35 ms │ 921 ms │ +└─────────┴────────┴────────┴────────┴────────┴──────────┴───────────┴────────┘ +┌───────────┬─────┬──────┬─────┬────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 7 │ 0.69 │ 1.87 │ 1 │ +├───────────┼─────┼──────┼─────┼────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 201 kB │ 19.6 kB │ 53.7 kB │ 28.8 kB │ +└───────────┴─────┴──────┴─────┴────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 41 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +83 requests in 60.07s, 1.18 MB read +35 errors (35 timeouts) + + +---------------- + + + +CLIENTS: *** 8 *** + +Running 60s test @ http://192.168.2.131/ +8 connections +1 workers + + +┌─────────┬───────┬────────┬────────┬────────┬────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼────────┼────────┼────────┼──────────┼────────┤ +│ Latency │ 74 ms │ 141 ms │ 193 ms │ 198 ms │ 143 ms │ 31.11 ms │ 198 ms │ +└─────────┴───────┴────────┴────────┴────────┴────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬───────┬────────┬─────────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼───────┼────────┼─────────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 8 │ 0.8 │ 2.41 │ 8 │ +├───────────┼─────┼──────┼─────┼───────┼────────┼─────────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 35 kB │ 3.5 kB │ 10.5 kB │ 35 kB │ +└───────────┴─────┴──────┴─────┴───────┴────────┴─────────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 48 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +96 requests in 60.08s, 210 kB read +40 errors (40 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +8 connections +1 workers + + +┌─────────┬───────┬────────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 59 ms │ 138 ms │ 284 ms │ 319 ms │ 139.8 ms │ 53.96 ms │ 319 ms │ +└─────────┴───────┴────────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬────────┬───────┬───────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼────────┼───────┼───────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 8 │ 0.8 │ 2.36 │ 1 │ +├───────────┼─────┼──────┼─────┼────────┼───────┼───────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 1.4 kB │ 140 B │ 411 B │ 175 B │ +└───────────┴─────┴──────┴─────┴────────┴───────┴───────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 48 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +96 requests in 60.07s, 8.4 kB read +40 errors (40 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +8 connections +1 workers + +node:internal/event_target:1084 + process.nextTick(() => { throw err; }); + ^ + +TypeError: colorize is not a function + at /Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:46:31 + at Array.forEach () + at printResult (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:43:43) + at EventEmitter. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/progressTracker.js:79:28) + at EventEmitter.emit (node:events:527:35) + at _cb (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/init.js:76:13) + at handleFinish (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:41:5) + at Worker. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:78:13) + at Worker.emit (node:events:515:28) + at MessagePort. (node:internal/worker:263:53) + +Node.js v21.1.0 + + +---------------- + + + +CLIENTS: *** 9 *** + +Running 60s test @ http://192.168.2.131/ +9 connections +1 workers + + +┌─────────┬───────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 31 ms │ 116 ms │ 206 ms │ 210 ms │ 109.58 ms │ 45.11 ms │ 210 ms │ +└─────────┴───────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 9 │ 0.9 │ 2.7 │ 9 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 39.4 kB │ 3.94 kB │ 11.8 kB │ 39.4 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 54 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +108 requests in 60.08s, 236 kB read +45 errors (45 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +9 connections +1 workers + + +┌─────────┬───────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 66 ms │ 131 ms │ 313 ms │ 349 ms │ 139.95 ms │ 60.66 ms │ 349 ms │ +└─────────┴───────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬───────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼───────┼───────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 9 │ 0.9 │ 2.7 │ 9 │ +├───────────┼─────┼──────┼─────┼─────────┼───────┼───────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 1.57 kB │ 158 B │ 473 B │ 1.57 kB │ +└───────────┴─────┴──────┴─────┴─────────┴───────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 54 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +108 requests in 60.08s, 9.45 kB read +45 errors (45 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +9 connections +1 workers + +node:internal/event_target:1084 + process.nextTick(() => { throw err; }); + ^ + +TypeError: colorize is not a function + at /Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:46:31 + at Array.forEach () + at printResult (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:43:43) + at EventEmitter. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/progressTracker.js:79:28) + at EventEmitter.emit (node:events:527:35) + at _cb (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/init.js:76:13) + at handleFinish (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:41:5) + at Worker. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:78:13) + at Worker.emit (node:events:515:28) + at MessagePort. (node:internal/worker:263:53) + +Node.js v21.1.0 + + +---------------- + + + +CLIENTS: *** 10 *** + +Running 60s test @ http://192.168.2.131/ +10 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 35 ms │ 96 ms │ 264 ms │ 267 ms │ 103.97 ms │ 54.56 ms │ 267 ms │ +└─────────┴───────┴───────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 10 │ 1 │ 3 │ 10 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 43.8 kB │ 4.38 kB │ 13.1 kB │ 43.8 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 60 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +120 requests in 60.08s, 263 kB read +50 errors (50 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +10 connections +1 workers + + +┌─────────┬───────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 64 ms │ 144 ms │ 411 ms │ 437 ms │ 156.64 ms │ 80.67 ms │ 437 ms │ +└─────────┴───────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬───────┬───────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼───────┼───────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 10 │ 1 │ 2.83 │ 1 │ +├───────────┼─────┼──────┼─────┼─────────┼───────┼───────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 1.75 kB │ 175 B │ 495 B │ 175 B │ +└───────────┴─────┴──────┴─────┴─────────┴───────┴───────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 60 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +120 requests in 60.08s, 10.5 kB read +50 errors (50 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +10 connections +1 workers + +node:internal/event_target:1084 + process.nextTick(() => { throw err; }); + ^ + +TypeError: colorize is not a function + at /Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:46:31 + at Array.forEach () + at printResult (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:43:43) + at EventEmitter. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/progressTracker.js:79:28) + at EventEmitter.emit (node:events:527:35) + at _cb (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/init.js:76:13) + at handleFinish (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:41:5) + at Worker. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:78:13) + at Worker.emit (node:events:515:28) + at MessagePort. (node:internal/worker:263:53) + +Node.js v21.1.0 + + +---------------- + + + +CLIENTS: *** 15 *** + +Running 60s test @ http://192.168.2.131/ +15 connections +1 workers + + +┌─────────┬───────┬────────┬────────┬────────┬──────────┬─────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼────────┼────────┼──────────┼─────────┼────────┤ +│ Latency │ 29 ms │ 104 ms │ 359 ms │ 362 ms │ 121.5 ms │ 77.5 ms │ 362 ms │ +└─────────┴───────┴────────┴────────┴────────┴──────────┴─────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 14 │ 1.34 │ 4.02 │ 12 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 52.6 kB │ 5.12 kB │ 15.4 kB │ 48.3 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 80 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +170 requests in 60.09s, 307 kB read +75 errors (75 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +15 connections +1 workers + + +┌─────────┬───────┬────────┬────────┬────────┬───────────┬───────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼────────┼────────┼───────────┼───────────┼────────┤ +│ Latency │ 91 ms │ 148 ms │ 505 ms │ 574 ms │ 174.38 ms │ 100.31 ms │ 574 ms │ +└─────────┴───────┴────────┴────────┴────────┴───────────┴───────────┴────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬───────┬───────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼───────┼───────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 15 │ 1.5 │ 4.06 │ 1 │ +├───────────┼─────┼──────┼─────┼─────────┼───────┼───────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 2.63 kB │ 263 B │ 710 B │ 175 B │ +└───────────┴─────┴──────┴─────┴─────────┴───────┴───────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 90 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +180 requests in 60.08s, 15.8 kB read +75 errors (75 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +15 connections +1 workers + +node:internal/event_target:1084 + process.nextTick(() => { throw err; }); + ^ + +TypeError: colorize is not a function + at /Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:46:31 + at Array.forEach () + at printResult (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:43:43) + at EventEmitter. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/progressTracker.js:79:28) + at EventEmitter.emit (node:events:527:35) + at _cb (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/init.js:76:13) + at handleFinish (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:41:5) + at Worker. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:78:13) + at Worker.emit (node:events:515:28) + at MessagePort. (node:internal/worker:263:53) + +Node.js v21.1.0 + + +---------------- + + + +CLIENTS: *** 20 *** + +Running 60s test @ http://192.168.2.131/ +20 connections +1 workers + + +┌─────────┬───────┬─────────┬─────────┬─────────┬────────────┬────────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼─────────┼─────────┼─────────┼────────────┼────────────┼─────────┤ +│ Latency │ 62 ms │ 1134 ms │ 6942 ms │ 6976 ms │ 1884.35 ms │ 1805.12 ms │ 6976 ms │ +└─────────┴───────┴─────────┴─────────┴─────────┴────────────┴────────────┴─────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 15 │ 1.44 │ 4.15 │ 4 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 57.2 kB │ 4.42 kB │ 14.2 kB │ 5.82 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 86 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +211 requests in 60.08s, 265 kB read +105 errors (100 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +20 connections +1 workers + + +┌─────────┬───────┬────────┬─────────┬─────────┬───────────┬────────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼─────────┼─────────┼───────────┼────────────┼─────────┤ +│ Latency │ 62 ms │ 232 ms │ 4030 ms │ 6972 ms │ 586.75 ms │ 1218.03 ms │ 6972 ms │ +└─────────┴───────┴────────┴─────────┴─────────┴───────────┴────────────┴─────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬───────┬───────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼───────┼───────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 11 │ 1.39 │ 3.08 │ 1 │ +├───────────┼─────┼──────┼─────┼─────────┼───────┼───────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 1.89 kB │ 238 B │ 529 B │ 172 B │ +└───────────┴─────┴──────┴─────┴─────────┴───────┴───────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 83 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +203 requests in 60.09s, 14.3 kB read +100 errors (100 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +20 connections +1 workers + +node:internal/event_target:1084 + process.nextTick(() => { throw err; }); + ^ + +TypeError: colorize is not a function + at /Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:46:31 + at Array.forEach () + at printResult (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:43:43) + at EventEmitter. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/progressTracker.js:79:28) + at EventEmitter.emit (node:events:527:35) + at _cb (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/init.js:76:13) + at handleFinish (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:41:5) + at Worker. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:78:13) + at Worker.emit (node:events:515:28) + at MessagePort. (node:internal/worker:263:53) + +Node.js v21.1.0 + + +---------------- + diff --git a/lib/PsychicHttp/benchmark/results/espasync-websocket-loadtest.log b/lib/PsychicHttp/benchmark/results/espasync-websocket-loadtest.log new file mode 100644 index 0000000..acc21d1 --- /dev/null +++ b/lib/PsychicHttp/benchmark/results/espasync-websocket-loadtest.log @@ -0,0 +1,252 @@ + + +CLIENTS: *** 1 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 1 +Agent: none + +Completed requests: 4231 +Total errors: 0 +Total time: 60.002 s +Mean latency: 13.6 ms +Effective rps: 71 + +Percentage of requests served within a certain time + 50% 10 ms + 90% 16 ms + 95% 24 ms + 99% 81 ms + 100% 280 ms (longest request) + + +CLIENTS: *** 2 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 2 +Agent: none + +Completed requests: 5914 +Total errors: 0 +Total time: 60.001 s +Mean latency: 19.7 ms +Effective rps: 99 + +Percentage of requests served within a certain time + 50% 15 ms + 90% 26 ms + 95% 67 ms + 99% 86 ms + 100% 109 ms (longest request) + + +CLIENTS: *** 3 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 3 +Agent: none + +Completed requests: 8204 +Total errors: 0 +Total time: 60.003 s +Mean latency: 21.4 ms +Effective rps: 137 + +Percentage of requests served within a certain time + 50% 17 ms + 90% 29 ms + 95% 68 ms + 99% 87 ms + 100% 104 ms (longest request) + + +CLIENTS: *** 4 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 4 +Agent: none + +Completed requests: 9634 +Total errors: 0 +Total time: 60.004 s +Mean latency: 24.4 ms +Effective rps: 161 + +Percentage of requests served within a certain time + 50% 19 ms + 90% 33 ms + 95% 73 ms + 99% 91 ms + 100% 145 ms (longest request) + + +CLIENTS: *** 5 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 5 +Agent: none + +Completed requests: 10759 +Total errors: 0 +Total time: 60.003 s +Mean latency: 27.3 ms +Effective rps: 179 + +Percentage of requests served within a certain time + 50% 22 ms + 90% 39 ms + 95% 76 ms + 99% 95 ms + 100% 117 ms (longest request) + + +CLIENTS: *** 6 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 6 +Agent: none + +Completed requests: 11302 +Total errors: 0 +Total time: 60.004 s +Mean latency: 31.3 ms +Effective rps: 188 + +Percentage of requests served within a certain time + 50% 26 ms + 90% 58 ms + 95% 81 ms + 99% 100 ms + 100% 122 ms (longest request) + + +CLIENTS: *** 7 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 7 +Agent: none + +Completed requests: 12713 +Total errors: 0 +Total time: 60.003 s +Mean latency: 32.5 ms +Effective rps: 212 + +Percentage of requests served within a certain time + 50% 27 ms + 90% 52 ms + 95% 81 ms + 99% 99 ms + 100% 125 ms (longest request) + + +CLIENTS: *** 8 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 8 +Running on cores: 2 +Agent: none + +Completed requests: 13157 +Total errors: 0 +Total time: 60.003 s +Mean latency: 35.9 ms +Effective rps: 219 + +Percentage of requests served within a certain time + 50% 30 ms + 90% 71 ms + 95% 88 ms + 99% 107 ms + 100% 132 ms (longest request) + + +CLIENTS: *** 10 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 10 +Running on cores: 2 +Agent: none + +Completed requests: 13417 +Total errors: 2 +Total time: 60.001 s +Mean latency: 34.4 ms +Effective rps: 224 + +Percentage of requests served within a certain time + 50% 30 ms + 90% 53 ms + 95% 81 ms + 99% 101 ms + 100% 124 ms (longest request) + + -1: 2 errors + + +CLIENTS: *** 16 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 16 +Running on cores: 2 +Agent: none + +Completed requests: 12804 +Total errors: 7 +Total time: 60.001 s +Mean latency: 36.4 ms +Effective rps: 213 + +Percentage of requests served within a certain time + 50% 30 ms + 90% 70 ms + 95% 86 ms + 99% 106 ms + 100% 135 ms (longest request) + + -1: 7 errors + + +CLIENTS: *** 20 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 20 +Running on cores: 2 +Agent: none + +Completed requests: 8421 +Total errors: 13 +Total time: 60.003 s +Mean latency: 37.2 ms +Effective rps: 140 + +Percentage of requests served within a certain time + 50% 20 ms + 90% 50 ms + 95% 77 ms + 99% 105 ms + 100% 9227 ms (longest request) + + -1: 13 errors diff --git a/lib/PsychicHttp/benchmark/results/psychic-http-loadtest.log b/lib/PsychicHttp/benchmark/results/psychic-http-loadtest.log new file mode 100644 index 0000000..148d378 --- /dev/null +++ b/lib/PsychicHttp/benchmark/results/psychic-http-loadtest.log @@ -0,0 +1,1172 @@ + + +CLIENTS: *** 1 *** + +Running 60s test @ http://192.168.2.131/ +1 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 24 ms │ 31 ms │ 100 ms │ 134 ms │ 39.16 ms │ 22.82 ms │ 270 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 18 │ 21 │ 25 │ 30 │ 25.19 │ 2.28 │ 18 │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 78.1 kB │ 91.1 kB │ 108 kB │ 130 kB │ 109 kB │ 9.88 kB │ 78.1 kB │ +└───────────┴─────────┴─────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 1511 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +2k requests in 60.05s, 6.55 MB read + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +1 connections +1 workers + + +┌─────────┬───────┬───────┬───────┬───────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼───────┼───────┼──────────┼──────────┼────────┤ +│ Latency │ 19 ms │ 25 ms │ 91 ms │ 99 ms │ 31.42 ms │ 18.78 ms │ 116 ms │ +└─────────┴───────┴───────┴───────┴───────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 26 │ 28 │ 31 │ 34 │ 31.29 │ 1.79 │ 26 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 3.46 kB │ 3.73 kB │ 4.16 kB │ 4.56 kB │ 4.19 kB │ 241 B │ 3.46 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 1877 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +2k requests in 60.06s, 251 kB read + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +1 connections +1 workers + + +┌─────────┬────────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 141 ms │ 201 ms │ 317 ms │ 331 ms │ 206.85 ms │ 51.73 ms │ 366 ms │ +└─────────┴────────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 4 │ 4 │ 5 │ 6 │ 4.82 │ 0.62 │ 4 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 115 kB │ 115 kB │ 144 kB │ 173 kB │ 139 kB │ 17.8 kB │ 115 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 289 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +290 requests in 60.07s, 8.32 MB read + + +---------------- + + + +CLIENTS: *** 2 *** + +Running 60s test @ http://192.168.2.131/ +2 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 26 ms │ 41 ms │ 167 ms │ 203 ms │ 55.52 ms │ 37.23 ms │ 373 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 29 │ 31 │ 35 │ 40 │ 35.64 │ 2.36 │ 29 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 126 kB │ 135 kB │ 152 kB │ 174 kB │ 155 kB │ 10.2 kB │ 126 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 2138 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +2k requests in 60.06s, 9.27 MB read + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +2 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 25 ms │ 35 ms │ 103 ms │ 112 ms │ 43.19 ms │ 21.44 ms │ 142 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 40 │ 40 │ 46 │ 50 │ 45.7 │ 2.76 │ 40 │ +├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 5.36 kB │ 5.36 kB │ 6.17 kB │ 6.7 kB │ 6.12 kB │ 370 B │ 5.36 kB │ +└───────────┴─────────┴─────────┴─────────┴────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 2742 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +3k requests in 60.06s, 367 kB read + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +2 connections +1 workers + + +┌─────────┬────────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 188 ms │ 322 ms │ 475 ms │ 507 ms │ 324.51 ms │ 70.51 ms │ 535 ms │ +└─────────┴────────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬───────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Req/Sec │ 5 │ 5 │ 6 │ 7 │ 6.15 │ 0.63 │ 5 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Bytes/Sec │ 144 kB │ 144 kB │ 173 kB │ 201 kB │ 177 kB │ 18 kB │ 144 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴───────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 369 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +371 requests in 60.06s, 10.6 MB read + + +---------------- + + + +CLIENTS: *** 3 *** + +Running 60s test @ http://192.168.2.131/ +3 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 31 ms │ 54 ms │ 185 ms │ 212 ms │ 68.63 ms │ 39.36 ms │ 386 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬───────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Req/Sec │ 38 │ 39 │ 43 │ 47 │ 43.32 │ 2.31 │ 38 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Bytes/Sec │ 165 kB │ 169 kB │ 187 kB │ 204 kB │ 188 kB │ 10 kB │ 165 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴───────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 2599 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +3k requests in 60.06s, 11.3 MB read + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +3 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 29 ms │ 42 ms │ 116 ms │ 126 ms │ 51.59 ms │ 23.91 ms │ 175 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 40 │ 51 │ 58 │ 64 │ 57.59 │ 4.25 │ 40 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 5.36 kB │ 6.83 kB │ 7.78 kB │ 8.58 kB │ 7.72 kB │ 569 B │ 5.36 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3455 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +3k requests in 60.06s, 463 kB read + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +3 connections +1 workers + + +┌─────────┬────────┬────────┬────────┬────────┬───────────┬───────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼────────┼────────┼───────────┼───────────┼────────┤ +│ Latency │ 203 ms │ 458 ms │ 761 ms │ 814 ms │ 474.54 ms │ 168.16 ms │ 902 ms │ +└─────────┴────────┴────────┴────────┴────────┴───────────┴───────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬───────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Req/Sec │ 4 │ 5 │ 6 │ 7 │ 6.29 │ 0.67 │ 4 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Bytes/Sec │ 115 kB │ 144 kB │ 173 kB │ 201 kB │ 181 kB │ 19 kB │ 115 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴───────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 377 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +380 requests in 60.06s, 10.9 MB read + + +---------------- + + + +CLIENTS: *** 4 *** + +Running 60s test @ http://192.168.2.131/ +4 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 37 ms │ 63 ms │ 206 ms │ 239 ms │ 81.32 ms │ 45.45 ms │ 396 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬───────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Req/Sec │ 42 │ 44 │ 49 │ 56 │ 48.87 │ 3.01 │ 42 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Bytes/Sec │ 182 kB │ 191 kB │ 213 kB │ 243 kB │ 212 kB │ 13 kB │ 182 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴───────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 2932 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +3k requests in 60.05s, 12.7 MB read + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +4 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 33 ms │ 50 ms │ 123 ms │ 131 ms │ 58.29 ms │ 23.99 ms │ 159 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 59 │ 59 │ 68 │ 78 │ 68 │ 4.3 │ 59 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 7.91 kB │ 7.91 kB │ 9.12 kB │ 10.5 kB │ 9.11 kB │ 576 B │ 7.91 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 4080 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.05s, 547 kB read + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +4 connections +1 workers + + +┌─────────┬────────┬────────┬─────────┬─────────┬───────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼─────────┼─────────┼───────────┼───────────┼─────────┤ +│ Latency │ 326 ms │ 534 ms │ 1064 ms │ 1141 ms │ 635.61 ms │ 238.71 ms │ 1210 ms │ +└─────────┴────────┴────────┴─────────┴─────────┴───────────┴───────────┴─────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 5 │ 5 │ 6 │ 7 │ 6.27 │ 0.63 │ 5 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 144 kB │ 144 kB │ 173 kB │ 201 kB │ 180 kB │ 18.1 kB │ 144 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 376 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +380 requests in 60.05s, 10.8 MB read + + +---------------- + + + +CLIENTS: *** 5 *** + +Running 60s test @ http://192.168.2.131/ +5 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 44 ms │ 74 ms │ 222 ms │ 261 ms │ 91.54 ms │ 47.88 ms │ 417 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 47 │ 47 │ 54 │ 60 │ 54.29 │ 3.05 │ 47 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 204 kB │ 204 kB │ 234 kB │ 260 kB │ 235 kB │ 13.2 kB │ 204 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3257 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +3k requests in 60.06s, 14.1 MB read + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +5 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 36 ms │ 57 ms │ 126 ms │ 138 ms │ 64.99 ms │ 24.42 ms │ 195 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 64 │ 69 │ 76 │ 84 │ 76.32 │ 3.97 │ 64 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 8.58 kB │ 9.25 kB │ 10.2 kB │ 11.3 kB │ 10.2 kB │ 532 B │ 8.58 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 4579 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.06s, 614 kB read + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +5 connections +1 workers + + +┌─────────┬────────┬────────┬─────────┬─────────┬───────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼─────────┼─────────┼───────────┼───────────┼─────────┤ +│ Latency │ 439 ms │ 652 ms │ 1393 ms │ 1458 ms │ 780.62 ms │ 290.11 ms │ 2018 ms │ +└─────────┴────────┴────────┴─────────┴─────────┴───────────┴───────────┴─────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 4 │ 5 │ 6 │ 8 │ 6.35 │ 0.78 │ 4 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 115 kB │ 144 kB │ 173 kB │ 230 kB │ 183 kB │ 22.1 kB │ 115 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 381 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +386 requests in 60.06s, 11 MB read + + +---------------- + + + +CLIENTS: *** 6 *** + +Running 60s test @ http://192.168.2.131/ +6 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 50 ms │ 85 ms │ 238 ms │ 268 ms │ 102.35 ms │ 50.03 ms │ 517 ms │ +└─────────┴───────┴───────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 49 │ 53 │ 58 │ 65 │ 58.3 │ 3.13 │ 49 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 213 kB │ 230 kB │ 252 kB │ 282 kB │ 253 kB │ 13.6 kB │ 213 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3498 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.05s, 15.2 MB read + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +6 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 41 ms │ 64 ms │ 138 ms │ 151 ms │ 74.53 ms │ 27.06 ms │ 286 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 71 │ 71 │ 80 │ 90 │ 79.92 │ 4.07 │ 71 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 9.59 kB │ 9.59 kB │ 10.8 kB │ 12.2 kB │ 10.8 kB │ 548 B │ 9.59 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 4795 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.06s, 647 kB read + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +6 connections +1 workers + + +┌─────────┬────────┬────────┬─────────┬─────────┬───────────┬──────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼─────────┼─────────┼───────────┼──────────┼─────────┤ +│ Latency │ 579 ms │ 840 ms │ 1766 ms │ 1816 ms │ 973.38 ms │ 355.3 ms │ 2392 ms │ +└─────────┴────────┴────────┴─────────┴─────────┴───────────┴──────────┴─────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬───────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Req/Sec │ 4 │ 4 │ 6 │ 8 │ 6.1 │ 0.87 │ 4 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Bytes/Sec │ 115 kB │ 115 kB │ 173 kB │ 230 kB │ 176 kB │ 25 kB │ 115 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴───────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 366 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +372 requests in 60.06s, 10.5 MB read + + +---------------- + + + +CLIENTS: *** 7 *** + +Running 60s test @ http://192.168.2.131/ +7 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 57 ms │ 96 ms │ 249 ms │ 293 ms │ 113.4 ms │ 53.05 ms │ 640 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 53 │ 54 │ 62 │ 68 │ 61.44 │ 3.82 │ 53 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 230 kB │ 234 kB │ 269 kB │ 295 kB │ 266 kB │ 16.6 kB │ 230 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3686 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.05s, 16 MB read + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +7 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 45 ms │ 69 ms │ 145 ms │ 154 ms │ 79.11 ms │ 27.06 ms │ 274 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬───────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼───────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 78 │ 80 │ 89 │ 99 │ 87.9 │ 4.78 │ 78 │ +├───────────┼─────────┼─────────┼───────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 10.5 kB │ 10.8 kB │ 12 kB │ 13.4 kB │ 11.9 kB │ 644 B │ 10.5 kB │ +└───────────┴─────────┴─────────┴───────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 5274 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.07s, 712 kB read + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +7 connections +1 workers + + +┌─────────┬────────┬────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 671 ms │ 952 ms │ 1965 ms │ 2059 ms │ 1094.09 ms │ 402.31 ms │ 3654 ms │ +└─────────┴────────┴────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 5 │ 5 │ 6 │ 8 │ 6.34 │ 0.79 │ 5 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 144 kB │ 144 kB │ 173 kB │ 230 kB │ 182 kB │ 22.7 kB │ 144 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 380 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +387 requests in 60.07s, 10.9 MB read + + +---------------- + + + +CLIENTS: *** 8 *** + +Running 60s test @ http://192.168.2.131/ +8 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬───────────┬──────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼───────────┼──────────┼─────────┤ +│ Latency │ 56 ms │ 95 ms │ 243 ms │ 275 ms │ 110.53 ms │ 52.27 ms │ 1027 ms │ +└─────────┴───────┴───────┴────────┴────────┴───────────┴──────────┴─────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 52 │ 54 │ 62 │ 73 │ 62.99 │ 4.89 │ 52 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 226 kB │ 234 kB │ 269 kB │ 317 kB │ 273 kB │ 21.2 kB │ 226 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3779 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.06s, 16.4 MB read +6 errors (6 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +8 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬─────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼─────────┼──────────┼────────┤ +│ Latency │ 45 ms │ 70 ms │ 146 ms │ 156 ms │ 80.9 ms │ 28.68 ms │ 308 ms │ +└─────────┴───────┴───────┴────────┴────────┴─────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 77 │ 77 │ 86 │ 95 │ 85.92 │ 4.89 │ 77 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 10.4 kB │ 10.4 kB │ 11.6 kB │ 12.8 kB │ 11.6 kB │ 659 B │ 10.4 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 5155 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.07s, 696 kB read +6 errors (6 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +8 connections +1 workers + + +┌─────────┬────────┬────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 699 ms │ 979 ms │ 2038 ms │ 2128 ms │ 1111.14 ms │ 406.26 ms │ 3506 ms │ +└─────────┴────────┴────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 5 │ 5 │ 6 │ 7 │ 6.25 │ 0.6 │ 5 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 144 kB │ 144 kB │ 173 kB │ 201 kB │ 180 kB │ 17.1 kB │ 144 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 375 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +389 requests in 60.06s, 10.8 MB read +6 errors (6 timeouts) + + +---------------- + + + +CLIENTS: *** 9 *** + +Running 60s test @ http://192.168.2.131/ +9 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 57 ms │ 96 ms │ 249 ms │ 277 ms │ 112.81 ms │ 51.23 ms │ 626 ms │ +└─────────┴───────┴───────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 52 │ 57 │ 61 │ 68 │ 61.7 │ 3.18 │ 52 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 226 kB │ 247 kB │ 265 kB │ 295 kB │ 268 kB │ 13.8 kB │ 226 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3702 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.06s, 16.1 MB read +12 errors (12 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +9 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 45 ms │ 70 ms │ 144 ms │ 154 ms │ 79.64 ms │ 27.25 ms │ 239 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬───────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼───────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 76 │ 79 │ 87 │ 96 │ 87.34 │ 4.79 │ 76 │ +├───────────┼─────────┼─────────┼─────────┼───────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 10.3 kB │ 10.7 kB │ 11.8 kB │ 13 kB │ 11.8 kB │ 647 B │ 10.3 kB │ +└───────────┴─────────┴─────────┴─────────┴───────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 5240 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.1s, 707 kB read +12 errors (12 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +9 connections +1 workers + + +┌─────────┬────────┬────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 664 ms │ 952 ms │ 2001 ms │ 2055 ms │ 1092.56 ms │ 399.94 ms │ 3588 ms │ +└─────────┴────────┴────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬───────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Req/Sec │ 5 │ 5 │ 6 │ 7 │ 6.35 │ 0.63 │ 5 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Bytes/Sec │ 144 kB │ 144 kB │ 173 kB │ 201 kB │ 183 kB │ 18 kB │ 144 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴───────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 381 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +402 requests in 60.06s, 11 MB read +12 errors (12 timeouts) + + +---------------- + + + +CLIENTS: *** 10 *** + +Running 60s test @ http://192.168.2.131/ +10 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 56 ms │ 97 ms │ 244 ms │ 277 ms │ 113.43 ms │ 51.43 ms │ 616 ms │ +└─────────┴───────┴───────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬───────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Req/Sec │ 48 │ 51 │ 61 │ 69 │ 61.42 │ 3.92 │ 48 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Bytes/Sec │ 208 kB │ 221 kB │ 265 kB │ 300 kB │ 266 kB │ 17 kB │ 208 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴───────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3685 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.06s, 16 MB read +18 errors (18 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +10 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 45 ms │ 71 ms │ 147 ms │ 154 ms │ 81.57 ms │ 28.91 ms │ 335 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 75 │ 76 │ 86 │ 92 │ 85.32 │ 4.46 │ 75 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 10.1 kB │ 10.3 kB │ 11.6 kB │ 12.4 kB │ 11.5 kB │ 601 B │ 10.1 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 5119 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.06s, 691 kB read +18 errors (18 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +10 connections +1 workers + + +┌─────────┬────────┬────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 705 ms │ 940 ms │ 1962 ms │ 2052 ms │ 1075.72 ms │ 385.45 ms │ 3313 ms │ +└─────────┴────────┴────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 5 │ 5 │ 6 │ 7 │ 6.45 │ 0.65 │ 5 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 144 kB │ 144 kB │ 173 kB │ 201 kB │ 186 kB │ 18.5 kB │ 144 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 387 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +415 requests in 60.05s, 11.1 MB read +18 errors (18 timeouts) + + +---------------- + + + +CLIENTS: *** 15 *** + +Running 60s test @ http://192.168.2.131/ +15 connections +1 workers + +node:internal/event_target:1084 + process.nextTick(() => { throw err; }); + ^ + +TypeError: colorize is not a function + at /Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:46:31 + at Array.forEach () + at printResult (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/printResult.js:43:43) + at EventEmitter. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/progressTracker.js:79:28) + at EventEmitter.emit (node:events:527:35) + at _cb (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/init.js:76:13) + at handleFinish (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:41:5) + at Worker. (/Users/hoeken/.nvm/versions/node/v21.1.0/lib/node_modules/autocannon/lib/manager.js:78:13) + at Worker.emit (node:events:515:28) + at MessagePort. (node:internal/worker:263:53) + +Node.js v21.1.0 + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +15 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼─────────┤ +│ Latency │ 44 ms │ 69 ms │ 158 ms │ 318 ms │ 82.99 ms │ 44.17 ms │ 1074 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴─────────┘ +┌───────────┬─────────┬────────┬─────────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 22 │ 23 │ 88 │ 98 │ 83.74 │ 16.45 │ 22 │ +├───────────┼─────────┼────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 2.97 kB │ 3.1 kB │ 11.9 kB │ 13.2 kB │ 11.3 kB │ 2.22 kB │ 2.97 kB │ +└───────────┴─────────┴────────┴─────────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 5024 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.06s, 678 kB read +48 errors (48 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +15 connections +1 workers + + +┌─────────┬────────┬────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 665 ms │ 954 ms │ 1989 ms │ 2105 ms │ 1092.91 ms │ 396.34 ms │ 3476 ms │ +└─────────┴────────┴────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 5 │ 5 │ 6 │ 7 │ 6.35 │ 0.66 │ 5 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 144 kB │ 144 kB │ 173 kB │ 201 kB │ 183 kB │ 18.8 kB │ 144 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 381 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +444 requests in 60.06s, 11 MB read +48 errors (48 timeouts) + + +---------------- + + + +CLIENTS: *** 20 *** + +Running 60s test @ http://192.168.2.131/ +20 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 57 ms │ 97 ms │ 255 ms │ 322 ms │ 114.69 ms │ 55.79 ms │ 683 ms │ +└─────────┴───────┴───────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 26 │ 48 │ 61 │ 67 │ 60.75 │ 5.8 │ 26 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 113 kB │ 208 kB │ 265 kB │ 291 kB │ 264 kB │ 25.2 kB │ 113 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3645 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.05s, 15.8 MB read +78 errors (78 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +20 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 44 ms │ 69 ms │ 142 ms │ 153 ms │ 78.14 ms │ 26.88 ms │ 256 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬───────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼───────┼───────┼─────────┤ +│ Req/Sec │ 76 │ 78 │ 88 │ 100 │ 88.92 │ 5.59 │ 76 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼───────┼───────┼─────────┤ +│ Bytes/Sec │ 10.3 kB │ 10.5 kB │ 11.9 kB │ 13.5 kB │ 12 kB │ 754 B │ 10.3 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴───────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 5335 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.07s, 720 kB read +78 errors (78 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +20 connections +1 workers + + +┌─────────┬────────┬────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 705 ms │ 940 ms │ 1991 ms │ 2070 ms │ 1085.97 ms │ 389.59 ms │ 3357 ms │ +└─────────┴────────┴────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 5 │ 5 │ 6 │ 7 │ 6.39 │ 0.64 │ 5 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 144 kB │ 144 kB │ 173 kB │ 201 kB │ 184 kB │ 18.2 kB │ 144 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 383 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +481 requests in 60.06s, 11 MB read +78 errors (78 timeouts) + + +---------------- + diff --git a/lib/PsychicHttp/benchmark/results/psychic-ssl-http-loadtest.log b/lib/PsychicHttp/benchmark/results/psychic-ssl-http-loadtest.log new file mode 100644 index 0000000..17e3414 --- /dev/null +++ b/lib/PsychicHttp/benchmark/results/psychic-ssl-http-loadtest.log @@ -0,0 +1,1194 @@ + + +CLIENTS: *** 1 *** + +Running 60s test @ https://192.168.2.131/ +1 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬─────────┬─────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼─────────┼─────────┼─────────┤ +│ Latency │ 33 ms │ 40 ms │ 139 ms │ 157 ms │ 51.6 ms │ 58.1 ms │ 1757 ms │ +└─────────┴───────┴───────┴────────┴────────┴─────────┴─────────┴─────────┘ +┌───────────┬─────┬─────────┬─────────┬────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 5 │ 20 │ 24 │ 19.19 │ 4.05 │ 5 │ +├───────────┼─────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 21.7 kB │ 86.8 kB │ 104 kB │ 83.2 kB │ 17.5 kB │ 21.7 kB │ +└───────────┴─────┴─────────┴─────────┴────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 1151 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +1k requests in 60.07s, 4.99 MB read + + +---------------- + +Running 60s test @ https://192.168.2.131/api?foo=bar +1 connections +1 workers + + +┌─────────┬───────┬───────┬───────┬────────┬──────────┬──────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼───────┼────────┼──────────┼──────────┼─────────┤ +│ Latency │ 21 ms │ 27 ms │ 89 ms │ 100 ms │ 32.17 ms │ 43.71 ms │ 1745 ms │ +└─────────┴───────┴───────┴───────┴────────┴──────────┴──────────┴─────────┘ +┌───────────┬─────┬─────────┬─────────┬─────────┬────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼─────────┼─────────┼─────────┼────────┼───────┼─────────┤ +│ Req/Sec │ 0 │ 8 │ 32 │ 38 │ 30.57 │ 5.91 │ 8 │ +├───────────┼─────┼─────────┼─────────┼─────────┼────────┼───────┼─────────┤ +│ Bytes/Sec │ 0 B │ 1.07 kB │ 4.29 kB │ 5.09 kB │ 4.1 kB │ 791 B │ 1.07 kB │ +└───────────┴─────┴─────────┴─────────┴─────────┴────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 1834 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +2k requests in 60.07s, 246 kB read + + +---------------- + +Running 60s test @ https://192.168.2.131/alien.png +1 connections +1 workers + + +┌─────────┬────────┬────────┬────────┬────────┬───────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼────────┼────────┼───────────┼───────────┼─────────┤ +│ Latency │ 216 ms │ 252 ms │ 400 ms │ 408 ms │ 281.55 ms │ 122.23 ms │ 1871 ms │ +└─────────┴────────┴────────┴────────┴────────┴───────────┴───────────┴─────────┘ +┌───────────┬─────┬─────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 1 │ 4 │ 5 │ 3.54 │ 0.83 │ 1 │ +├───────────┼─────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 28.8 kB │ 115 kB │ 144 kB │ 102 kB │ 23.8 kB │ 28.8 kB │ +└───────────┴─────┴─────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 212 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +213 requests in 60.07s, 6.1 MB read + + +---------------- + + + +CLIENTS: *** 2 *** + +Running 60s test @ https://192.168.2.131/ +2 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼───────────┼─────────┤ +│ Latency │ 37 ms │ 54 ms │ 149 ms │ 164 ms │ 64.14 ms │ 106.26 ms │ 3230 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴───────────┴─────────┘ +┌───────────┬─────┬──────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 0 │ 0 │ 33 │ 36 │ 30.92 │ 7.43 │ 27 │ +├───────────┼─────┼──────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 143 kB │ 156 kB │ 134 kB │ 32.2 kB │ 117 kB │ +└───────────┴─────┴──────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 1855 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +2k requests in 60.06s, 8.05 MB read + + +---------------- + +Running 60s test @ https://192.168.2.131/api?foo=bar +2 connections +1 workers + + +┌─────────┬───────┬───────┬───────┬────────┬──────────┬─────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼───────┼────────┼──────────┼─────────┼─────────┤ +│ Latency │ 27 ms │ 38 ms │ 95 ms │ 106 ms │ 44.65 ms │ 87.8 ms │ 3211 ms │ +└─────────┴───────┴───────┴───────┴────────┴──────────┴─────────┴─────────┘ +┌───────────┬─────┬──────┬─────────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 47 │ 52 │ 44.29 │ 10.57 │ 35 │ +├───────────┼─────┼──────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 6.35 kB │ 7.02 kB │ 5.98 kB │ 1.43 kB │ 4.72 kB │ +└───────────┴─────┴──────┴─────────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 2657 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +3k requests in 60.07s, 359 kB read + + +---------------- + +Running 60s test @ https://192.168.2.131/alien.png +2 connections +1 workers + + +┌─────────┬────────┬────────┬────────┬────────┬───────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼────────┼────────┼───────────┼───────────┼─────────┤ +│ Latency │ 320 ms │ 456 ms │ 633 ms │ 701 ms │ 488.51 ms │ 296.92 ms │ 3820 ms │ +└─────────┴────────┴────────┴────────┴────────┴───────────┴───────────┴─────────┘ +┌───────────┬─────┬──────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 4 │ 5 │ 4.09 │ 1.09 │ 3 │ +├───────────┼─────┼──────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 115 kB │ 144 kB │ 118 kB │ 31.2 kB │ 86.3 kB │ +└───────────┴─────┴──────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 245 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +247 requests in 60.07s, 7.05 MB read + + +---------------- + + + +CLIENTS: *** 3 *** + +Running 60s test @ https://192.168.2.131/ +3 connections +1 workers + + +┌─────────┬─────────┬─────────┬──────────┬──────────┬────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼─────────┼─────────┼──────────┼──────────┼────────────┼────────────┼──────────┤ +│ Latency │ 1535 ms │ 6477 ms │ 14517 ms │ 14517 ms │ 6462.25 ms │ 2849.18 ms │ 14517 ms │ +└─────────┴─────────┴─────────┴──────────┴──────────┴────────────┴────────────┴──────────┘ +┌───────────┬─────┬──────┬─────────┬─────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────────┼─────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.6 │ 0.53 │ 1 │ +├───────────┼─────┼──────┼─────────┼─────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 4.34 kB │ 4.34 kB │ 2.6 kB │ 2.27 kB │ 4.34 kB │ +└───────────┴─────┴──────┴─────────┴─────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 36 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +75 requests in 60.1s, 156 kB read +5 errors (0 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/api?foo=bar +3 connections +1 workers + + +┌─────────┬─────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼─────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 1517 ms │ 1638 ms │ 3675 ms │ 3675 ms │ 1697.95 ms │ 343.44 ms │ 3675 ms │ +└─────────┴─────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────┬──────┬───────┬───────┬────────┬────────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼───────┼───────┼────────┼────────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.59 │ 0.5 │ 1 │ +├───────────┼─────┼──────┼───────┼───────┼────────┼────────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 135 B │ 135 B │ 78.8 B │ 66.6 B │ 135 B │ +└───────────┴─────┴──────┴───────┴───────┴────────┴────────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 35 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +73 requests in 60.11s, 4.72 kB read +1 errors (0 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/alien.png +3 connections +1 workers + + +┌─────────┬────────┬─────────┬──────────┬──────────┬─────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼──────────┼──────────┼─────────────┼────────────┼──────────┤ +│ Latency │ 386 ms │ 9117 ms │ 50313 ms │ 50313 ms │ 12942.77 ms │ 11833.9 ms │ 50313 ms │ +└─────────┴────────┴─────────┴──────────┴──────────┴─────────────┴────────────┴──────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 3 │ 0.57 │ 0.7 │ 1 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 86.4 kB │ 16.3 kB │ 19.9 kB │ 28.8 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 34 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +68 requests in 60.11s, 979 kB read +28 errors (0 timeouts) + + +---------------- + + + +CLIENTS: *** 4 *** + +Running 60s test @ https://192.168.2.131/ +4 connections +1 workers + + +┌─────────┬─────────┬─────────┬──────────┬──────────┬─────────────┬─────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼─────────┼─────────┼──────────┼──────────┼─────────────┼─────────────┼──────────┤ +│ Latency │ 1530 ms │ 1686 ms │ 40733 ms │ 40733 ms │ 10336.06 ms │ 12608.67 ms │ 40733 ms │ +└─────────┴─────────┴─────────┴──────────┴──────────┴─────────────┴─────────────┴──────────┘ +┌───────────┬─────┬──────┬─────────┬─────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────────┼─────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.6 │ 0.53 │ 1 │ +├───────────┼─────┼──────┼─────────┼─────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 4.34 kB │ 4.34 kB │ 2.6 kB │ 2.27 kB │ 4.34 kB │ +└───────────┴─────┴──────┴─────────┴─────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 36 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +76 requests in 60.15s, 156 kB read +11 errors (0 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/api?foo=bar +4 connections +1 workers + + +┌─────────┬─────────┬─────────┬─────────┬─────────┬────────────┬──────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼─────────┼─────────┼─────────┼─────────┼────────────┼──────────┼─────────┤ +│ Latency │ 1495 ms │ 1586 ms │ 4490 ms │ 4490 ms │ 1673.35 ms │ 485.3 ms │ 4490 ms │ +└─────────┴─────────┴─────────┴─────────┴─────────┴────────────┴──────────┴─────────┘ +┌───────────┬─────┬──────┬───────┬───────┬────────┬────────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼───────┼───────┼────────┼────────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.59 │ 0.5 │ 1 │ +├───────────┼─────┼──────┼───────┼───────┼────────┼────────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 135 B │ 135 B │ 78.8 B │ 66.6 B │ 135 B │ +└───────────┴─────┴──────┴───────┴───────┴────────┴────────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 35 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +74 requests in 60.15s, 4.72 kB read +3 errors (0 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/alien.png +4 connections +1 workers + + +┌─────────┬────────┬─────────┬──────────┬──────────┬─────────────┬─────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼──────────┼──────────┼─────────────┼─────────────┼──────────┤ +│ Latency │ 340 ms │ 3642 ms │ 49950 ms │ 49950 ms │ 13180.63 ms │ 14189.21 ms │ 49950 ms │ +└─────────┴────────┴─────────┴──────────┴──────────┴─────────────┴─────────────┴──────────┘ +┌───────────┬─────┬──────┬─────────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 2 │ 0.59 │ 0.65 │ 1 │ +├───────────┼─────┼──────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 28.8 kB │ 57.6 kB │ 16.8 kB │ 18.4 kB │ 28.8 kB │ +└───────────┴─────┴──────┴─────────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 35 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +72 requests in 60.15s, 1.01 MB read +30 errors (0 timeouts) + + +---------------- + + + +CLIENTS: *** 5 *** + +Running 60s test @ https://192.168.2.131/ +5 connections +1 workers + + +┌─────────┬─────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼─────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 1449 ms │ 1597 ms │ 4810 ms │ 4810 ms │ 1691.72 ms │ 537.09 ms │ 4810 ms │ +└─────────┴─────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────┬──────┬─────────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.59 │ 0.5 │ 1 │ +├───────────┼─────┼──────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 4.34 kB │ 4.34 kB │ 2.53 kB │ 2.14 kB │ 4.34 kB │ +└───────────┴─────┴──────┴─────────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 35 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +75 requests in 60.13s, 152 kB read +11 errors (0 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/api?foo=bar +5 connections +1 workers + + +┌─────────┬─────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼─────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 1510 ms │ 1584 ms │ 4971 ms │ 4971 ms │ 1685.03 ms │ 564.99 ms │ 4971 ms │ +└─────────┴─────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────┬──────┬───────┬───────┬────────┬────────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼───────┼───────┼────────┼────────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.59 │ 0.5 │ 1 │ +├───────────┼─────┼──────┼───────┼───────┼────────┼────────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 135 B │ 135 B │ 78.8 B │ 66.6 B │ 135 B │ +└───────────┴─────┴──────┴───────┴───────┴────────┴────────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 35 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +75 requests in 60.14s, 4.72 kB read +1 errors (0 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/alien.png +5 connections +1 workers + + +┌─────────┬────────┬─────────┬──────────┬──────────┬────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼──────────┼──────────┼────────────┼────────────┼──────────┤ +│ Latency │ 409 ms │ 5565 ms │ 38132 ms │ 38132 ms │ 9430.76 ms │ 9263.66 ms │ 38132 ms │ +└─────────┴────────┴─────────┴──────────┴──────────┴────────────┴────────────┴──────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 2 │ 0.56 │ 0.62 │ 1 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 57.6 kB │ 15.8 kB │ 17.8 kB │ 28.8 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 33 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +69 requests in 60.14s, 950 kB read +29 errors (1 timeouts) + + +---------------- + + + +CLIENTS: *** 6 *** + +Running 60s test @ https://192.168.2.131/ +6 connections +1 workers + + +┌─────────┬─────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼─────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 1007 ms │ 1603 ms │ 5373 ms │ 5373 ms │ 1696.69 ms │ 639.15 ms │ 5373 ms │ +└─────────┴─────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────┬──────┬─────────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.59 │ 0.5 │ 1 │ +├───────────┼─────┼──────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 4.34 kB │ 4.34 kB │ 2.53 kB │ 2.14 kB │ 4.34 kB │ +└───────────┴─────┴──────┴─────────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 35 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +76 requests in 60.14s, 152 kB read +15 errors (2 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/api?foo=bar +6 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 976 ms │ 1586 ms │ 6118 ms │ 6118 ms │ 1683.15 ms │ 773.87 ms │ 6118 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────┬──────┬───────┬───────┬────────┬────────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼───────┼───────┼────────┼────────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.59 │ 0.5 │ 1 │ +├───────────┼─────┼──────┼───────┼───────┼────────┼────────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 135 B │ 135 B │ 78.8 B │ 66.6 B │ 135 B │ +└───────────┴─────┴──────┴───────┴───────┴────────┴────────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 35 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +76 requests in 60.14s, 4.72 kB read +4 errors (2 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/alien.png +6 connections +1 workers + + +┌─────────┬────────┬─────────┬──────────┬──────────┬────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼──────────┼──────────┼────────────┼────────────┼──────────┤ +│ Latency │ 437 ms │ 3543 ms │ 18174 ms │ 18174 ms │ 4264.16 ms │ 3629.94 ms │ 18174 ms │ +└─────────┴────────┴─────────┴──────────┴──────────┴────────────┴────────────┴──────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 1 │ 0.54 │ 0.6 │ 1 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 28.8 kB │ 15.3 kB │ 17 kB │ 28.8 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 32 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +69 requests in 60.15s, 921 kB read +30 errors (5 timeouts) + + +---------------- + + + +CLIENTS: *** 7 *** + +Running 60s test @ https://192.168.2.131/ +7 connections +1 workers + + +┌─────────┬────────┬─────────┬──────────┬──────────┬────────────┬───────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼──────────┼──────────┼────────────┼───────────┼──────────┤ +│ Latency │ 979 ms │ 1600 ms │ 37778 ms │ 37778 ms │ 2654.48 ms │ 5987.7 ms │ 37778 ms │ +└─────────┴────────┴─────────┴──────────┴──────────┴────────────┴───────────┴──────────┘ +┌───────────┬─────┬──────┬─────────┬─────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────────┼─────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.6 │ 0.53 │ 1 │ +├───────────┼─────┼──────┼─────────┼─────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 4.34 kB │ 4.34 kB │ 2.6 kB │ 2.27 kB │ 4.34 kB │ +└───────────┴─────┴──────┴─────────┴─────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 36 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +80 requests in 60.1s, 156 kB read +17 errors (7 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/api?foo=bar +7 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 928 ms │ 1591 ms │ 7128 ms │ 7128 ms │ 1648.53 ms │ 955.92 ms │ 7128 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────┬──────┬───────┬───────┬──────┬────────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼───────┼───────┼──────┼────────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.6 │ 0.49 │ 1 │ +├───────────┼─────┼──────┼───────┼───────┼──────┼────────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 135 B │ 135 B │ 81 B │ 66.1 B │ 135 B │ +└───────────┴─────┴──────┴───────┴───────┴──────┴────────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 36 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +79 requests in 60.09s, 4.86 kB read +13 errors (8 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/alien.png +7 connections +1 workers + + +┌─────────┬────────┬─────────┬──────────┬──────────┬───────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼──────────┼──────────┼───────────┼────────────┼──────────┤ +│ Latency │ 487 ms │ 1740 ms │ 24134 ms │ 24134 ms │ 3069.3 ms │ 4835.41 ms │ 24134 ms │ +└─────────┴────────┴─────────┴──────────┴──────────┴───────────┴────────────┴──────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 1 │ 0.4 │ 0.53 │ 1 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 28.8 kB │ 11.5 kB │ 15 kB │ 28.8 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 24 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +69 requests in 60.08s, 691 kB read +37 errors (21 timeouts) + + +---------------- + + + +CLIENTS: *** 8 *** + +Running 60s test @ https://192.168.2.131/ +8 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 892 ms │ 1585 ms │ 6973 ms │ 6973 ms │ 1613.52 ms │ 927.38 ms │ 6973 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────┬──────┬─────────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.62 │ 0.49 │ 1 │ +├───────────┼─────┼──────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 4.34 kB │ 4.34 kB │ 2.68 kB │ 2.11 kB │ 4.34 kB │ +└───────────┴─────┴──────┴─────────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 37 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +89 requests in 60.09s, 161 kB read +25 errors (16 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/api?foo=bar +8 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 964 ms │ 1593 ms │ 7380 ms │ 7380 ms │ 1645.62 ms │ 998.66 ms │ 7380 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────┬──────┬───────┬───────┬──────┬────────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼───────┼───────┼──────┼────────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.6 │ 0.53 │ 1 │ +├───────────┼─────┼──────┼───────┼───────┼──────┼────────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 135 B │ 135 B │ 81 B │ 70.6 B │ 135 B │ +└───────────┴─────┴──────┴───────┴───────┴──────┴────────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 36 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +86 requests in 60.1s, 4.86 kB read +14 errors (14 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/alien.png +8 connections +1 workers + + +┌─────────┬────────┬─────────┬──────────┬──────────┬────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼──────────┼──────────┼────────────┼────────────┼──────────┤ +│ Latency │ 341 ms │ 4820 ms │ 26999 ms │ 26999 ms │ 7220.49 ms │ 6874.47 ms │ 26999 ms │ +└─────────┴────────┴─────────┴──────────┴──────────┴────────────┴────────────┴──────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 3 │ 0.56 │ 0.67 │ 1 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 86.4 kB │ 15.8 kB │ 19.3 kB │ 28.8 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 33 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +84 requests in 60.08s, 950 kB read +41 errors (27 timeouts) + + +---------------- + + + +CLIENTS: *** 9 *** + +Running 60s test @ https://192.168.2.131/ +9 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬────────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼────────────┼─────────┤ +│ Latency │ 960 ms │ 1598 ms │ 7751 ms │ 7751 ms │ 1665.89 ms │ 1056.66 ms │ 7751 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴────────────┴─────────┘ +┌───────────┬─────┬──────┬─────────┬─────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────────┼─────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.6 │ 0.49 │ 1 │ +├───────────┼─────┼──────┼─────────┼─────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 4.34 kB │ 4.34 kB │ 2.6 kB │ 2.13 kB │ 4.34 kB │ +└───────────┴─────┴──────┴─────────┴─────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 36 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +93 requests in 60.1s, 156 kB read +25 errors (21 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/api?foo=bar +9 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬────────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼────────────┼─────────┤ +│ Latency │ 933 ms │ 1589 ms │ 7703 ms │ 7703 ms │ 1649.14 ms │ 1051.77 ms │ 7703 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴────────────┴─────────┘ +┌───────────┬─────┬──────┬───────┬───────┬──────┬────────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼───────┼───────┼──────┼────────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.6 │ 0.49 │ 1 │ +├───────────┼─────┼──────┼───────┼───────┼──────┼────────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 135 B │ 135 B │ 81 B │ 66.1 B │ 135 B │ +└───────────┴─────┴──────┴───────┴───────┴──────┴────────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 36 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +93 requests in 60.09s, 4.86 kB read +21 errors (20 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/alien.png +9 connections +1 workers + + +┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬────────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼────────────┼─────────┤ +│ Latency │ 1049 ms │ 1783 ms │ 7382 ms │ 7382 ms │ 1758 ms │ 1027.58 ms │ 7382 ms │ +└─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴────────────┴─────────┘ +┌───────────┬─────┬──────┬─────────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.57 │ 0.5 │ 1 │ +├───────────┼─────┼──────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 28.8 kB │ 28.8 kB │ 16.3 kB │ 14.3 kB │ 28.8 kB │ +└───────────┴─────┴──────┴─────────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 34 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +91 requests in 60.1s, 979 kB read +48 errors (29 timeouts) + + +---------------- + + + +CLIENTS: *** 10 *** + +Running 60s test @ https://192.168.2.131/ +10 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬────────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼────────────┼─────────┤ +│ Latency │ 979 ms │ 1597 ms │ 7548 ms │ 7548 ms │ 1660.23 ms │ 1024.01 ms │ 7548 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴────────────┴─────────┘ +┌───────────┬─────┬──────┬─────────┬─────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────────┼─────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.6 │ 0.49 │ 1 │ +├───────────┼─────┼──────┼─────────┼─────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 4.34 kB │ 4.34 kB │ 2.6 kB │ 2.13 kB │ 4.34 kB │ +└───────────┴─────┴──────┴─────────┴─────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 36 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +100 requests in 60.09s, 156 kB read +29 errors (26 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/api?foo=bar +10 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 976 ms │ 1590 ms │ 7480 ms │ 7480 ms │ 1628.95 ms │ 1021.2 ms │ 7480 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────┬──────┬───────┬───────┬──────┬────────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼───────┼───────┼──────┼────────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.6 │ 0.49 │ 1 │ +├───────────┼─────┼──────┼───────┼───────┼──────┼────────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 135 B │ 135 B │ 81 B │ 66.1 B │ 135 B │ +└───────────┴─────┴──────┴───────┴───────┴──────┴────────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 36 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +101 requests in 60.12s, 4.86 kB read +28 errors (28 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/alien.png +10 connections +1 workers + + +┌─────────┬────────┬─────────┬──────────┬──────────┬────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼──────────┼──────────┼────────────┼────────────┼──────────┤ +│ Latency │ 585 ms │ 5882 ms │ 27503 ms │ 27503 ms │ 6338.82 ms │ 6773.89 ms │ 27503 ms │ +└─────────┴────────┴─────────┴──────────┴──────────┴────────────┴────────────┴──────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 1 │ 0.54 │ 0.6 │ 1 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 28.8 kB │ 15.3 kB │ 17 kB │ 28.8 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 32 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +97 requests in 60.14s, 921 kB read +54 errors (38 timeouts) + + +---------------- + + + +CLIENTS: *** 15 *** + +Running 60s test @ https://192.168.2.131/ +15 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬────────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼────────────┼─────────┤ +│ Latency │ 957 ms │ 1601 ms │ 7609 ms │ 7609 ms │ 1654.73 ms │ 1034.04 ms │ 7609 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴────────────┴─────────┘ +┌───────────┬─────┬──────┬─────────┬─────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────────┼─────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.6 │ 0.49 │ 1 │ +├───────────┼─────┼──────┼─────────┼─────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 4.34 kB │ 4.34 kB │ 2.6 kB │ 2.13 kB │ 4.34 kB │ +└───────────┴─────┴──────┴─────────┴─────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 36 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +135 requests in 60.14s, 156 kB read +64 errors (56 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/api?foo=bar +15 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 966 ms │ 1576 ms │ 7369 ms │ 7369 ms │ 1612.68 ms │ 991.59 ms │ 7369 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────┬──────┬───────┬───────┬────────┬────────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼───────┼───────┼────────┼────────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.62 │ 0.49 │ 1 │ +├───────────┼─────┼──────┼───────┼───────┼────────┼────────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 135 B │ 135 B │ 83.3 B │ 65.6 B │ 135 B │ +└───────────┴─────┴──────┴───────┴───────┴────────┴────────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 37 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +136 requests in 60.14s, 5 kB read +61 errors (60 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/alien.png +15 connections +1 workers + + +┌─────────┬────────┬─────────┬──────────┬──────────┬──────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼──────────┼──────────┼──────────┼────────────┼──────────┤ +│ Latency │ 300 ms │ 7281 ms │ 34953 ms │ 34953 ms │ 10153 ms │ 9647.72 ms │ 34953 ms │ +└─────────┴────────┴─────────┴──────────┴──────────┴──────────┴────────────┴──────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 3 │ 0.52 │ 0.68 │ 1 │ +├───────────┼─────┼──────┼─────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 86.4 kB │ 14.9 kB │ 19.3 kB │ 28.8 kB │ +└───────────┴─────┴──────┴─────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 31 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +132 requests in 60.11s, 892 kB read +84 errors (70 timeouts) + + +---------------- + + + +CLIENTS: *** 20 *** + +Running 60s test @ https://192.168.2.131/ +20 connections +1 workers + + +┌─────────┬────────┬─────────┬──────────┬──────────┬────────────┬────────────┬──────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼──────────┼──────────┼────────────┼────────────┼──────────┤ +│ Latency │ 944 ms │ 1600 ms │ 36783 ms │ 36783 ms │ 2881.21 ms │ 6487.79 ms │ 36783 ms │ +└─────────┴────────┴─────────┴──────────┴──────────┴────────────┴────────────┴──────────┘ +┌───────────┬─────┬──────┬─────┬─────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────┼─────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 0 │ 1 │ 0.49 │ 0.54 │ 1 │ +├───────────┼─────┼──────┼─────┼─────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 0 B │ 4.34 kB │ 2.1 kB │ 2.31 kB │ 4.34 kB │ +└───────────┴─────┴──────┴─────┴─────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 29 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +165 requests in 60.1s, 126 kB read +96 errors (92 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/api?foo=bar +20 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 931 ms │ 1568 ms │ 7097 ms │ 7097 ms │ 1637.53 ms │ 950.49 ms │ 7097 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────┬──────┬───────┬───────┬──────┬────────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼───────┼───────┼──────┼────────┼───────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.6 │ 0.49 │ 1 │ +├───────────┼─────┼──────┼───────┼───────┼──────┼────────┼───────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 135 B │ 135 B │ 81 B │ 66.1 B │ 135 B │ +└───────────┴─────┴──────┴───────┴───────┴──────┴────────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 36 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +170 requests in 60.12s, 4.86 kB read +87 errors (85 timeouts) + + +---------------- + +Running 60s test @ https://192.168.2.131/alien.png +20 connections +1 workers + + +┌─────────┬─────────┬─────────┬─────────┬─────────┬────────────┬────────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼─────────┼─────────┼─────────┼─────────┼────────────┼────────────┼─────────┤ +│ Latency │ 1116 ms │ 1707 ms │ 7481 ms │ 7481 ms │ 1754.83 ms │ 1032.66 ms │ 7481 ms │ +└─────────┴─────────┴─────────┴─────────┴─────────┴────────────┴────────────┴─────────┘ +┌───────────┬─────┬──────┬─────────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────┼──────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 0 │ 0 │ 1 │ 1 │ 0.57 │ 0.5 │ 1 │ +├───────────┼─────┼──────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 0 B │ 0 B │ 28.8 kB │ 28.8 kB │ 16.3 kB │ 14.3 kB │ 28.8 kB │ +└───────────┴─────┴──────┴─────────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 34 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +168 requests in 60.09s, 979 kB read +114 errors (95 timeouts) + + +---------------- + diff --git a/lib/PsychicHttp/benchmark/results/psychic-ssl-websocket-loadtest.log b/lib/PsychicHttp/benchmark/results/psychic-ssl-websocket-loadtest.log new file mode 100644 index 0000000..4e9aa5c --- /dev/null +++ b/lib/PsychicHttp/benchmark/results/psychic-ssl-websocket-loadtest.log @@ -0,0 +1,246 @@ + + +CLIENTS: *** 1 *** + + +Target URL: wss://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 1 +Agent: none + +Completed requests: 2039 +Total errors: 0 +Total time: 60.002 s +Mean latency: 27.8 ms +Effective rps: 34 + +Percentage of requests served within a certain time + 50% 24 ms + 90% 34 ms + 95% 62 ms + 99% 97 ms + 100% 109 ms (longest request) + + +CLIENTS: *** 2 *** + + +Target URL: wss://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 2 +Agent: none + +Completed requests: 2969 +Total errors: 0 +Total time: 60.003 s +Mean latency: 37.7 ms +Effective rps: 49 + +Percentage of requests served within a certain time + 50% 32 ms + 90% 52 ms + 95% 92 ms + 99% 110 ms + 100% 126 ms (longest request) + + +CLIENTS: *** 3 *** + + +Target URL: wss://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 3 +Agent: none + +Completed requests: 2883 +Total errors: 0 +Total time: 60.003 s +Mean latency: 38.2 ms +Effective rps: 48 + +Percentage of requests served within a certain time + 50% 32 ms + 90% 52 ms + 95% 86 ms + 99% 109 ms + 100% 1711 ms (longest request) + + +CLIENTS: *** 4 *** + + +Target URL: wss://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 4 +Agent: none + +Completed requests: 2858 +Total errors: 0 +Total time: 60.003 s +Mean latency: 37.9 ms +Effective rps: 48 + +Percentage of requests served within a certain time + 50% 32 ms + 90% 49 ms + 95% 76 ms + 99% 104 ms + 100% 1740 ms (longest request) + + +CLIENTS: *** 5 *** + + +Target URL: wss://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 5 +Agent: none + +Completed requests: 2772 +Total errors: 0 +Total time: 60.003 s +Mean latency: 38.6 ms +Effective rps: 46 + +Percentage of requests served within a certain time + 50% 32 ms + 90% 49 ms + 95% 79 ms + 99% 106 ms + 100% 1634 ms (longest request) + + +CLIENTS: *** 6 *** + + +Target URL: wss://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 6 +Agent: none + +Completed requests: 2722 +Total errors: 0 +Total time: 60.003 s +Mean latency: 38.7 ms +Effective rps: 45 + +Percentage of requests served within a certain time + 50% 32 ms + 90% 49 ms + 95% 72 ms + 99% 102 ms + 100% 1694 ms (longest request) + + +CLIENTS: *** 7 *** + + +Target URL: wss://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 7 +Agent: none + +Completed requests: 2552 +Total errors: 0 +Total time: 60.003 s +Mean latency: 40.7 ms +Effective rps: 43 + +Percentage of requests served within a certain time + 50% 32 ms + 90% 52 ms + 95% 86 ms + 99% 112 ms + 100% 1816 ms (longest request) + + +CLIENTS: *** 8 *** + + +Target URL: wss://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 8 +Running on cores: 2 +Agent: none + +Completed requests: 2507 +Total errors: 0 +Total time: 60.005 s +Mean latency: 40.8 ms +Effective rps: 42 + +Percentage of requests served within a certain time + 50% 32 ms + 90% 50 ms + 95% 80 ms + 99% 112 ms + 100% 1646 ms (longest request) + + +CLIENTS: *** 10 *** + + +Target URL: wss://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 10 +Running on cores: 2 +Agent: none + +Completed requests: 2265 +Total errors: 0 +Total time: 60.008 s +Mean latency: 43.7 ms +Effective rps: 38 + +Percentage of requests served within a certain time + 50% 33 ms + 90% 52 ms + 95% 79 ms + 99% 114 ms + 100% 1675 ms (longest request) + + +CLIENTS: *** 16 *** + + +Target URL: wss://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 16 +Running on cores: 2 +Agent: none + +Completed requests: 1795 +Total errors: 0 +Total time: 60.003 s +Mean latency: 49.7 ms +Effective rps: 30 + +Percentage of requests served within a certain time + 50% 33 ms + 90% 51 ms + 95% 77 ms + 99% 112 ms + 100% 1741 ms (longest request) + + +CLIENTS: *** 20 *** + + +Target URL: wss://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 20 +Running on cores: 2 +Agent: none + +Completed requests: 1603 +Total errors: 0 +Total time: 60.004 s +Mean latency: 54.1 ms +Effective rps: 27 + +Percentage of requests served within a certain time + 50% 33 ms + 90% 60 ms + 95% 94 ms + 99% 133 ms + 100% 1729 ms (longest request) diff --git a/lib/PsychicHttp/benchmark/results/psychic-v1.1-http-loadtest.log b/lib/PsychicHttp/benchmark/results/psychic-v1.1-http-loadtest.log new file mode 100644 index 0000000..9fc5dae --- /dev/null +++ b/lib/PsychicHttp/benchmark/results/psychic-v1.1-http-loadtest.log @@ -0,0 +1,1179 @@ + + +CLIENTS: *** 1 *** + +Running 60s test @ http://192.168.2.131/ +1 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 25 ms │ 34 ms │ 137 ms │ 175 ms │ 44.75 ms │ 33.04 ms │ 363 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 13 │ 14 │ 23 │ 27 │ 22.05 │ 3.62 │ 13 │ +├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 56.4 kB │ 60.7 kB │ 99.8 kB │ 117 kB │ 95.6 kB │ 15.7 kB │ 56.4 kB │ +└───────────┴─────────┴─────────┴─────────┴────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 1323 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +1k requests in 60.06s, 5.74 MB read + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +1 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 21 ms │ 29 ms │ 122 ms │ 173 ms │ 40.61 ms │ 38.19 ms │ 646 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬───────┬───────┬─────────┬─────────┬─────────┬─────────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼───────┼───────┼─────────┼─────────┼─────────┼─────────┼───────┤ +│ Req/Sec │ 2 │ 6 │ 27 │ 34 │ 24.29 │ 7.62 │ 2 │ +├───────────┼───────┼───────┼─────────┼─────────┼─────────┼─────────┼───────┤ +│ Bytes/Sec │ 268 B │ 804 B │ 3.62 kB │ 4.56 kB │ 3.25 kB │ 1.02 kB │ 268 B │ +└───────────┴───────┴───────┴─────────┴─────────┴─────────┴─────────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 1457 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +1k requests in 60.07s, 195 kB read + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +1 connections +1 workers + + +┌─────────┬────────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 254 ms │ 291 ms │ 441 ms │ 487 ms │ 311.18 ms │ 52.89 ms │ 503 ms │ +└─────────┴────────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 2 │ 2 │ 3 │ 4 │ 3.2 │ 0.58 │ 2 │ +├───────────┼─────────┼─────────┼─────────┼────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 57.6 kB │ 57.6 kB │ 86.4 kB │ 115 kB │ 92.1 kB │ 16.4 kB │ 57.6 kB │ +└───────────┴─────────┴─────────┴─────────┴────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 192 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +193 requests in 60.07s, 5.53 MB read + + +---------------- + + + +CLIENTS: *** 2 *** + +Running 60s test @ http://192.168.2.131/ +2 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 29 ms │ 46 ms │ 184 ms │ 213 ms │ 63.03 ms │ 43.98 ms │ 437 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 13 │ 13 │ 34 │ 43 │ 31.44 │ 7.44 │ 13 │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 56.4 kB │ 56.4 kB │ 148 kB │ 187 kB │ 136 kB │ 32.2 kB │ 56.4 kB │ +└───────────┴─────────┴─────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 1886 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +2k requests in 60.07s, 8.18 MB read + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +2 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 27 ms │ 37 ms │ 107 ms │ 120 ms │ 44.09 ms │ 20.34 ms │ 188 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬───────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼───────┼───────┼─────────┤ +│ Req/Sec │ 24 │ 25 │ 46 │ 52 │ 44.79 │ 6.1 │ 24 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼───────┼───────┼─────────┤ +│ Bytes/Sec │ 3.22 kB │ 3.35 kB │ 6.17 kB │ 6.97 kB │ 6 kB │ 817 B │ 3.22 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴───────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 2687 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +3k requests in 60.06s, 360 kB read + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +2 connections +1 workers + + +┌─────────┬────────┬────────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 375 ms │ 552 ms │ 759 ms │ 848 ms │ 556.76 ms │ 95.75 ms │ 875 ms │ +└─────────┴────────┴────────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 3 │ 3 │ 4 │ 4 │ 3.59 │ 0.53 │ 3 │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 86.4 kB │ 86.4 kB │ 115 kB │ 115 kB │ 103 kB │ 15.1 kB │ 86.3 kB │ +└───────────┴─────────┴─────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 215 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +217 requests in 60.09s, 6.19 MB read + + +---------------- + + + +CLIENTS: *** 3 *** + +Running 60s test @ http://192.168.2.131/ +3 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬─────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼─────────┼────────┤ +│ Latency │ 36 ms │ 56 ms │ 167 ms │ 193 ms │ 67.22 ms │ 33.6 ms │ 343 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴─────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 32 │ 34 │ 45 │ 52 │ 44.25 │ 4.31 │ 32 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 139 kB │ 148 kB │ 195 kB │ 226 kB │ 192 kB │ 18.7 kB │ 139 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 2655 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +3k requests in 60.08s, 11.5 MB read + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +3 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬─────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼─────────┼────────┤ +│ Latency │ 30 ms │ 45 ms │ 114 ms │ 128 ms │ 51.89 ms │ 22.2 ms │ 294 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴─────────┴────────┘ +┌───────────┬─────────┬────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 37 │ 44 │ 58 │ 67 │ 57.24 │ 6.16 │ 37 │ +├───────────┼─────────┼────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 4.96 kB │ 5.9 kB │ 7.78 kB │ 8.98 kB │ 7.67 kB │ 825 B │ 4.96 kB │ +└───────────┴─────────┴────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3434 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +3k requests in 60.1s, 460 kB read + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +3 connections +1 workers + + +┌─────────┬────────┬────────┬─────────┬─────────┬───────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼─────────┼─────────┼───────────┼───────────┼─────────┤ +│ Latency │ 412 ms │ 786 ms │ 1293 ms │ 1397 ms │ 829.09 ms │ 295.53 ms │ 1412 ms │ +└─────────┴────────┴────────┴─────────┴─────────┴───────────┴───────────┴─────────┘ +┌───────────┬─────────┬─────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 2 │ 3 │ 4 │ 4 │ 3.59 │ 0.53 │ 2 │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 57.6 kB │ 86.4 kB │ 115 kB │ 115 kB │ 103 kB │ 15.1 kB │ 57.6 kB │ +└───────────┴─────────┴─────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 215 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +218 requests in 60.11s, 6.19 MB read + + +---------------- + + + +CLIENTS: *** 4 *** + +Running 60s test @ http://192.168.2.131/ +4 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 41 ms │ 65 ms │ 189 ms │ 221 ms │ 79.49 ms │ 39.69 ms │ 385 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 41 │ 41 │ 51 │ 56 │ 50 │ 4.07 │ 41 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 178 kB │ 178 kB │ 221 kB │ 243 kB │ 217 kB │ 17.6 kB │ 178 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3000 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +3k requests in 60.1s, 13 MB read + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +4 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 35 ms │ 51 ms │ 128 ms │ 154 ms │ 58.79 ms │ 24.56 ms │ 347 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 38 │ 50 │ 69 │ 77 │ 67.49 │ 7.34 │ 38 │ +├───────────┼─────────┼────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 5.09 kB │ 6.7 kB │ 9.25 kB │ 10.3 kB │ 9.04 kB │ 983 B │ 5.09 kB │ +└───────────┴─────────┴────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 4049 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.11s, 543 kB read + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +4 connections +1 workers + + +┌─────────┬────────┬────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 630 ms │ 885 ms │ 1840 ms │ 1877 ms │ 1082.82 ms │ 406.47 ms │ 2050 ms │ +└─────────┴────────┴────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────────┬─────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 3 │ 3 │ 4 │ 5 │ 3.65 │ 0.55 │ 3 │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 86.4 kB │ 86.4 kB │ 115 kB │ 144 kB │ 105 kB │ 15.6 kB │ 86.3 kB │ +└───────────┴─────────┴─────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 219 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +223 requests in 60.08s, 6.3 MB read + + +---------------- + + + +CLIENTS: *** 5 *** + +Running 60s test @ http://192.168.2.131/ +5 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 47 ms │ 77 ms │ 213 ms │ 242 ms │ 91.79 ms │ 44.06 ms │ 398 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 41 │ 42 │ 55 │ 62 │ 54.12 │ 4.67 │ 41 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 178 kB │ 182 kB │ 239 kB │ 269 kB │ 235 kB │ 20.2 kB │ 178 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3247 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +3k requests in 60.08s, 14.1 MB read + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +5 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 39 ms │ 60 ms │ 126 ms │ 141 ms │ 65.76 ms │ 21.03 ms │ 172 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬────────┬─────────┬─────────┬─────────┬─────────┬───────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼─────────┼─────────┼─────────┼─────────┼───────┼────────┤ +│ Req/Sec │ 53 │ 58 │ 76 │ 86 │ 75.34 │ 7.04 │ 53 │ +├───────────┼────────┼─────────┼─────────┼─────────┼─────────┼───────┼────────┤ +│ Bytes/Sec │ 7.1 kB │ 7.78 kB │ 10.2 kB │ 11.5 kB │ 10.1 kB │ 943 B │ 7.1 kB │ +└───────────┴────────┴─────────┴─────────┴─────────┴─────────┴───────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 4520 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.08s, 606 kB read + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +5 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 832 ms │ 1125 ms │ 2405 ms │ 2530 ms │ 1344.04 ms │ 504.13 ms │ 3485 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────────┬─────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 2 │ 3 │ 4 │ 5 │ 3.69 │ 0.57 │ 2 │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 57.6 kB │ 86.4 kB │ 115 kB │ 144 kB │ 106 kB │ 16.2 kB │ 57.6 kB │ +└───────────┴─────────┴─────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 221 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +226 requests in 60.08s, 6.36 MB read + + +---------------- + + + +CLIENTS: *** 6 *** + +Running 60s test @ http://192.168.2.131/ +6 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 51 ms │ 84 ms │ 214 ms │ 249 ms │ 98.05 ms │ 44.48 ms │ 553 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 40 │ 50 │ 62 │ 66 │ 60.87 │ 4.59 │ 40 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 174 kB │ 217 kB │ 269 kB │ 286 kB │ 264 kB │ 19.9 kB │ 174 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3652 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.09s, 15.8 MB read + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +6 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 45 ms │ 68 ms │ 144 ms │ 159 ms │ 75.43 ms │ 24.46 ms │ 223 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 53 │ 63 │ 80 │ 91 │ 78.95 │ 7.74 │ 53 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 7.16 kB │ 8.51 kB │ 10.8 kB │ 12.3 kB │ 10.7 kB │ 1.04 kB │ 7.16 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 4737 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.08s, 639 kB read + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +6 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 893 ms │ 1385 ms │ 3045 ms │ 3336 ms │ 1630.97 ms │ 621.58 ms │ 4350 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────────┬─────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 2 │ 3 │ 4 │ 5 │ 3.62 │ 0.58 │ 2 │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 57.6 kB │ 86.4 kB │ 115 kB │ 144 kB │ 104 kB │ 16.7 kB │ 57.6 kB │ +└───────────┴─────────┴─────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 217 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +223 requests in 60.1s, 6.25 MB read + + +---------------- + + + +CLIENTS: *** 7 *** + +Running 60s test @ http://192.168.2.131/ +7 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 59 ms │ 96 ms │ 232 ms │ 260 ms │ 109.85 ms │ 47.04 ms │ 531 ms │ +└─────────┴───────┴───────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬───────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Req/Sec │ 44 │ 53 │ 65 │ 72 │ 63.39 │ 5.76 │ 44 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼───────┼────────┤ +│ Bytes/Sec │ 191 kB │ 230 kB │ 282 kB │ 313 kB │ 275 kB │ 25 kB │ 191 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴───────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3803 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.08s, 16.5 MB read + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +7 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 48 ms │ 75 ms │ 139 ms │ 152 ms │ 80.37 ms │ 22.69 ms │ 291 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬───────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼───────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 52 │ 67 │ 88 │ 96 │ 86.59 │ 7.62 │ 52 │ +├───────────┼─────────┼─────────┼─────────┼───────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 7.02 kB │ 9.05 kB │ 11.9 kB │ 13 kB │ 11.7 kB │ 1.03 kB │ 7.02 kB │ +└───────────┴─────────┴─────────┴─────────┴───────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 5195 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.11s, 701 kB read + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +7 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 845 ms │ 1642 ms │ 3537 ms │ 3656 ms │ 1887.17 ms │ 728.73 ms │ 6130 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────────┬─────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 3 │ 3 │ 4 │ 4 │ 3.64 │ 0.52 │ 3 │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 86.4 kB │ 86.4 kB │ 115 kB │ 115 kB │ 105 kB │ 14.8 kB │ 86.3 kB │ +└───────────┴─────────┴─────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 218 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +225 requests in 60.1s, 6.27 MB read + + +---------------- + + + +CLIENTS: *** 8 *** + +Running 60s test @ http://192.168.2.131/ +8 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬───────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼───────────┼──────────┼────────┤ +│ Latency │ 58 ms │ 96 ms │ 225 ms │ 256 ms │ 108.89 ms │ 45.19 ms │ 571 ms │ +└─────────┴───────┴───────┴────────┴────────┴───────────┴──────────┴────────┘ +┌───────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Req/Sec │ 51 │ 54 │ 65 │ 70 │ 63.92 │ 4.63 │ 51 │ +├───────────┼────────┼────────┼────────┼────────┼────────┼─────────┼────────┤ +│ Bytes/Sec │ 221 kB │ 234 kB │ 282 kB │ 304 kB │ 277 kB │ 20.1 kB │ 221 kB │ +└───────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3835 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.07s, 16.6 MB read +6 errors (6 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +8 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬─────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼─────────┼────────┤ +│ Latency │ 50 ms │ 76 ms │ 148 ms │ 166 ms │ 83.61 ms │ 26.5 ms │ 282 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴─────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 63 │ 66 │ 82 │ 98 │ 83.15 │ 8.78 │ 63 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 8.51 kB │ 8.91 kB │ 11.1 kB │ 13.2 kB │ 11.2 kB │ 1.18 kB │ 8.51 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 4989 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.08s, 674 kB read +6 errors (6 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +8 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬───────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼───────────┼───────────┼─────────┤ +│ Latency │ 806 ms │ 1614 ms │ 3526 ms │ 3769 ms │ 1851.3 ms │ 712.64 ms │ 5877 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴───────────┴───────────┴─────────┘ +┌───────────┬─────────┬─────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 3 │ 3 │ 4 │ 5 │ 3.72 │ 0.52 │ 3 │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 86.4 kB │ 86.4 kB │ 115 kB │ 144 kB │ 107 kB │ 14.9 kB │ 86.3 kB │ +└───────────┴─────────┴─────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 223 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +237 requests in 60.09s, 6.42 MB read +6 errors (6 timeouts) + + +---------------- + + + +CLIENTS: *** 9 *** + +Running 60s test @ http://192.168.2.131/ +9 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬───────────┬─────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼───────────┼─────────┼─────────┤ +│ Latency │ 60 ms │ 98 ms │ 232 ms │ 268 ms │ 111.69 ms │ 57.8 ms │ 1065 ms │ +└─────────┴───────┴───────┴────────┴────────┴───────────┴─────────┴─────────┘ +┌───────────┬───────┬────────┬────────┬────────┬────────┬───────┬───────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼───────┼────────┼────────┼────────┼────────┼───────┼───────┤ +│ Req/Sec │ 3 │ 54 │ 63 │ 72 │ 62.37 │ 8.98 │ 3 │ +├───────────┼───────┼────────┼────────┼────────┼────────┼───────┼───────┤ +│ Bytes/Sec │ 13 kB │ 234 kB │ 273 kB │ 313 kB │ 271 kB │ 39 kB │ 13 kB │ +└───────────┴───────┴────────┴────────┴────────┴────────┴───────┴───────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3742 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.07s, 16.2 MB read +12 errors (12 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +9 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 50 ms │ 77 ms │ 148 ms │ 160 ms │ 83.55 ms │ 24.72 ms │ 275 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬───────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Req/Sec │ 63 │ 67 │ 85 │ 95 │ 83.27 │ 7.2 │ 63 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼───────┼─────────┤ +│ Bytes/Sec │ 8.51 kB │ 9.05 kB │ 11.5 kB │ 12.8 kB │ 11.2 kB │ 972 B │ 8.51 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴───────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 4996 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.08s, 674 kB read +12 errors (12 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +9 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 857 ms │ 1631 ms │ 3541 ms │ 3814 ms │ 1883.14 ms │ 730.83 ms │ 6021 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────────┬─────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 3 │ 3 │ 4 │ 4 │ 3.67 │ 0.48 │ 3 │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 86.4 kB │ 86.4 kB │ 115 kB │ 115 kB │ 106 kB │ 13.5 kB │ 86.3 kB │ +└───────────┴─────────┴─────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 220 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +241 requests in 60.08s, 6.33 MB read +12 errors (12 timeouts) + + +---------------- + + + +CLIENTS: *** 10 *** + +Running 60s test @ http://192.168.2.131/ +10 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬───────────┬──────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼───────────┼──────────┼─────────┤ +│ Latency │ 59 ms │ 95 ms │ 235 ms │ 276 ms │ 111.77 ms │ 57.35 ms │ 1010 ms │ +└─────────┴───────┴───────┴────────┴────────┴───────────┴──────────┴─────────┘ +┌───────────┬─────────┬────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 17 │ 47 │ 64 │ 70 │ 62.32 │ 8.14 │ 17 │ +├───────────┼─────────┼────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 73.8 kB │ 204 kB │ 278 kB │ 304 kB │ 270 kB │ 35.3 kB │ 73.7 kB │ +└───────────┴─────────┴────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3739 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.08s, 16.2 MB read +18 errors (18 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +10 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 49 ms │ 82 ms │ 179 ms │ 209 ms │ 91.77 ms │ 33.92 ms │ 312 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 46 │ 48 │ 76 │ 92 │ 75.85 │ 11.6 │ 46 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 6.21 kB │ 6.48 kB │ 10.3 kB │ 12.4 kB │ 10.2 kB │ 1.57 kB │ 6.21 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 4551 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.07s, 614 kB read +18 errors (18 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +10 connections +1 workers + + +┌─────────┬─────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼─────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 1062 ms │ 1673 ms │ 3588 ms │ 3643 ms │ 1903.02 ms │ 693.11 ms │ 5495 ms │ +└─────────┴─────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────────┬─────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 2 │ 2 │ 4 │ 5 │ 3.6 │ 0.64 │ 2 │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 57.6 kB │ 57.6 kB │ 115 kB │ 144 kB │ 104 kB │ 18.3 kB │ 57.6 kB │ +└───────────┴─────────┴─────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 216 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +244 requests in 60.08s, 6.22 MB read +18 errors (18 timeouts) + + +---------------- + + + +CLIENTS: *** 15 *** + +Running 60s test @ http://192.168.2.131/ +15 connections +1 workers + + +┌─────────┬───────┬────────┬────────┬────────┬───────────┬─────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼────────┼────────┼────────┼───────────┼─────────┼─────────┤ +│ Latency │ 62 ms │ 103 ms │ 252 ms │ 297 ms │ 119.53 ms │ 58.1 ms │ 1050 ms │ +└─────────┴───────┴────────┴────────┴────────┴───────────┴─────────┴─────────┘ +┌───────────┬─────────┬────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 22 │ 41 │ 60 │ 70 │ 58.27 │ 8.6 │ 22 │ +├───────────┼─────────┼────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 95.5 kB │ 178 kB │ 260 kB │ 304 kB │ 253 kB │ 37.3 kB │ 95.4 kB │ +└───────────┴─────────┴────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3496 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.06s, 15.2 MB read +48 errors (48 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +15 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬──────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼──────────┼──────────┼────────┤ +│ Latency │ 49 ms │ 75 ms │ 151 ms │ 164 ms │ 82.32 ms │ 25.95 ms │ 313 ms │ +└─────────┴───────┴───────┴────────┴────────┴──────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 61 │ 66 │ 85 │ 99 │ 84.52 │ 8.42 │ 61 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 8.24 kB │ 8.91 kB │ 11.5 kB │ 13.4 kB │ 11.4 kB │ 1.14 kB │ 8.23 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 5071 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.07s, 685 kB read +48 errors (48 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +15 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 801 ms │ 1709 ms │ 3676 ms │ 3762 ms │ 1929.08 ms │ 744.64 ms │ 5846 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────────┬─────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 2 │ 3 │ 4 │ 5 │ 3.57 │ 0.59 │ 2 │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 57.6 kB │ 86.4 kB │ 115 kB │ 144 kB │ 103 kB │ 16.9 kB │ 57.6 kB │ +└───────────┴─────────┴─────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 214 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +277 requests in 60.09s, 6.16 MB read +48 errors (48 timeouts) + + +---------------- + + + +CLIENTS: *** 20 *** + +Running 60s test @ http://192.168.2.131/ +20 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬───────────┬──────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼───────────┼──────────┼─────────┤ +│ Latency │ 60 ms │ 97 ms │ 232 ms │ 267 ms │ 112.02 ms │ 53.32 ms │ 1054 ms │ +└─────────┴───────┴───────┴────────┴────────┴───────────┴──────────┴─────────┘ +┌───────────┬─────────┬────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 21 │ 52 │ 63 │ 69 │ 62.12 │ 6.59 │ 21 │ +├───────────┼─────────┼────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 91.1 kB │ 226 kB │ 273 kB │ 300 kB │ 269 kB │ 28.6 kB │ 91.1 kB │ +└───────────┴─────────┴────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 3727 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +4k requests in 60.1s, 16.2 MB read +78 errors (78 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/api?foo=bar +20 connections +1 workers + + +┌─────────┬───────┬───────┬────────┬────────┬─────────┬──────────┬────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼───────┼───────┼────────┼────────┼─────────┼──────────┼────────┤ +│ Latency │ 49 ms │ 78 ms │ 164 ms │ 178 ms │ 87.8 ms │ 35.83 ms │ 658 ms │ +└─────────┴───────┴───────┴────────┴────────┴─────────┴──────────┴────────┘ +┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Req/Sec │ 35 │ 55 │ 81 │ 95 │ 79.27 │ 10.5 │ 35 │ +├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤ +│ Bytes/Sec │ 4.73 kB │ 7.43 kB │ 10.9 kB │ 12.8 kB │ 10.7 kB │ 1.42 kB │ 4.72 kB │ +└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 4756 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +5k requests in 60.07s, 642 kB read +78 errors (78 timeouts) + + +---------------- + +Running 60s test @ http://192.168.2.131/alien.png +20 connections +1 workers + + +┌─────────┬────────┬─────────┬─────────┬─────────┬────────────┬───────────┬─────────┐ +│ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ +├─────────┼────────┼─────────┼─────────┼─────────┼────────────┼───────────┼─────────┤ +│ Latency │ 806 ms │ 1613 ms │ 3348 ms │ 4009 ms │ 1854.07 ms │ 733.32 ms │ 6606 ms │ +└─────────┴────────┴─────────┴─────────┴─────────┴────────────┴───────────┴─────────┘ +┌───────────┬─────────┬─────────┬────────┬────────┬────────┬─────────┬─────────┐ +│ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Req/Sec │ 2 │ 3 │ 4 │ 5 │ 3.7 │ 0.62 │ 2 │ +├───────────┼─────────┼─────────┼────────┼────────┼────────┼─────────┼─────────┤ +│ Bytes/Sec │ 57.6 kB │ 86.4 kB │ 115 kB │ 144 kB │ 106 kB │ 17.7 kB │ 57.6 kB │ +└───────────┴─────────┴─────────┴────────┴────────┴────────┴─────────┴─────────┘ +┌──────┬───────┐ +│ Code │ Count │ +├──────┼───────┤ +│ 200 │ 222 │ +└──────┴───────┘ + +Req/Bytes counts sampled once per second. +# of samples: 60 + +320 requests in 60.05s, 6.39 MB read +78 errors (78 timeouts) + + +---------------- + diff --git a/lib/PsychicHttp/benchmark/results/psychic-v1.1-websocket-loadtest.log b/lib/PsychicHttp/benchmark/results/psychic-v1.1-websocket-loadtest.log new file mode 100644 index 0000000..8c507a9 --- /dev/null +++ b/lib/PsychicHttp/benchmark/results/psychic-v1.1-websocket-loadtest.log @@ -0,0 +1,246 @@ + + +CLIENTS: *** 1 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 1 +Agent: none + +Completed requests: 1972 +Total errors: 0 +Total time: 60.003 s +Mean latency: 29.8 ms +Effective rps: 33 + +Percentage of requests served within a certain time + 50% 25 ms + 90% 40 ms + 95% 66 ms + 99% 96 ms + 100% 147 ms (longest request) + + +CLIENTS: *** 2 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 2 +Agent: none + +Completed requests: 3144 +Total errors: 0 +Total time: 60.003 s +Mean latency: 37.6 ms +Effective rps: 52 + +Percentage of requests served within a certain time + 50% 32 ms + 90% 58 ms + 95% 82 ms + 99% 114 ms + 100% 160 ms (longest request) + + +CLIENTS: *** 3 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 3 +Agent: none + +Completed requests: 4113 +Total errors: 0 +Total time: 60.005 s +Mean latency: 43.2 ms +Effective rps: 69 + +Percentage of requests served within a certain time + 50% 38 ms + 90% 63 ms + 95% 88 ms + 99% 119 ms + 100% 339 ms (longest request) + + +CLIENTS: *** 4 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 4 +Agent: none + +Completed requests: 4902 +Total errors: 0 +Total time: 60.004 s +Mean latency: 48.3 ms +Effective rps: 82 + +Percentage of requests served within a certain time + 50% 42 ms + 90% 74 ms + 95% 97 ms + 99% 125 ms + 100% 217 ms (longest request) + + +CLIENTS: *** 5 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 5 +Agent: none + +Completed requests: 5522 +Total errors: 0 +Total time: 60.003 s +Mean latency: 53.7 ms +Effective rps: 92 + +Percentage of requests served within a certain time + 50% 48 ms + 90% 81 ms + 95% 102 ms + 99% 122 ms + 100% 324 ms (longest request) + + +CLIENTS: *** 6 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 6 +Agent: none + +Completed requests: 5808 +Total errors: 0 +Total time: 60.004 s +Mean latency: 61.4 ms +Effective rps: 97 + +Percentage of requests served within a certain time + 50% 54 ms + 90% 94 ms + 95% 117 ms + 99% 142 ms + 100% 348 ms (longest request) + + +CLIENTS: *** 7 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 7 +Agent: none + +Completed requests: 6478 +Total errors: 0 +Total time: 60.006 s +Mean latency: 64.1 ms +Effective rps: 108 + +Percentage of requests served within a certain time + 50% 59 ms + 90% 94 ms + 95% 110 ms + 99% 137 ms + 100% 195 ms (longest request) + + +CLIENTS: *** 8 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 8 +Running on cores: 2 +Agent: none + +Completed requests: 6124 +Total errors: 0 +Total time: 60.004 s +Mean latency: 67.8 ms +Effective rps: 102 + +Percentage of requests served within a certain time + 50% 59 ms + 90% 107 ms + 95% 131 ms + 99% 173 ms + 100% 260 ms (longest request) + + +CLIENTS: *** 10 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 10 +Running on cores: 2 +Agent: none + +Completed requests: 5640 +Total errors: 0 +Total time: 60.004 s +Mean latency: 73.7 ms +Effective rps: 94 + +Percentage of requests served within a certain time + 50% 61 ms + 90% 120 ms + 95% 140 ms + 99% 240 ms + 100% 780 ms (longest request) + + +CLIENTS: *** 16 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 16 +Running on cores: 2 +Agent: none + +Completed requests: 5809 +Total errors: 0 +Total time: 60.006 s +Mean latency: 71.6 ms +Effective rps: 97 + +Percentage of requests served within a certain time + 50% 64 ms + 90% 111 ms + 95% 130 ms + 99% 162 ms + 100% 226 ms (longest request) + + +CLIENTS: *** 20 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 20 +Running on cores: 2 +Agent: none + +Completed requests: 5590 +Total errors: 0 +Total time: 60.003 s +Mean latency: 74.4 ms +Effective rps: 93 + +Percentage of requests served within a certain time + 50% 61 ms + 90% 122 ms + 95% 151 ms + 99% 247 ms + 100% 513 ms (longest request) diff --git a/lib/PsychicHttp/benchmark/results/psychic-websocket-loadtest.log b/lib/PsychicHttp/benchmark/results/psychic-websocket-loadtest.log new file mode 100644 index 0000000..812026c --- /dev/null +++ b/lib/PsychicHttp/benchmark/results/psychic-websocket-loadtest.log @@ -0,0 +1,246 @@ + + +CLIENTS: *** 1 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 1 +Agent: none + +Completed requests: 2304 +Total errors: 0 +Total time: 60.002 s +Mean latency: 25.5 ms +Effective rps: 38 + +Percentage of requests served within a certain time + 50% 22 ms + 90% 32 ms + 95% 58 ms + 99% 92 ms + 100% 105 ms (longest request) + + +CLIENTS: *** 2 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 2 +Agent: none + +Completed requests: 3647 +Total errors: 0 +Total time: 60.002 s +Mean latency: 32.3 ms +Effective rps: 61 + +Percentage of requests served within a certain time + 50% 28 ms + 90% 43 ms + 95% 67 ms + 99% 93 ms + 100% 135 ms (longest request) + + +CLIENTS: *** 3 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 3 +Agent: none + +Completed requests: 4629 +Total errors: 0 +Total time: 60.004 s +Mean latency: 38.3 ms +Effective rps: 77 + +Percentage of requests served within a certain time + 50% 34 ms + 90% 51 ms + 95% 79 ms + 99% 110 ms + 100% 152 ms (longest request) + + +CLIENTS: *** 4 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 4 +Agent: none + +Completed requests: 5290 +Total errors: 0 +Total time: 60.003 s +Mean latency: 44.7 ms +Effective rps: 88 + +Percentage of requests served within a certain time + 50% 40 ms + 90% 67 ms + 95% 92 ms + 99% 115 ms + 100% 159 ms (longest request) + + +CLIENTS: *** 5 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 5 +Agent: none + +Completed requests: 5935 +Total errors: 0 +Total time: 60.002 s +Mean latency: 50 ms +Effective rps: 99 + +Percentage of requests served within a certain time + 50% 45 ms + 90% 74 ms + 95% 97 ms + 99% 123 ms + 100% 172 ms (longest request) + + +CLIENTS: *** 6 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 6 +Agent: none + +Completed requests: 6533 +Total errors: 0 +Total time: 60.003 s +Mean latency: 54.5 ms +Effective rps: 109 + +Percentage of requests served within a certain time + 50% 49 ms + 90% 78 ms + 95% 101 ms + 99% 129 ms + 100% 170 ms (longest request) + + +CLIENTS: *** 7 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 7 +Agent: none + +Completed requests: 7086 +Total errors: 0 +Total time: 60.004 s +Mean latency: 58.6 ms +Effective rps: 118 + +Percentage of requests served within a certain time + 50% 54 ms + 90% 85 ms + 95% 107 ms + 99% 130 ms + 100% 184 ms (longest request) + + +CLIENTS: *** 8 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 8 +Running on cores: 2 +Agent: none + +Completed requests: 6994 +Total errors: 0 +Total time: 60.004 s +Mean latency: 59.3 ms +Effective rps: 117 + +Percentage of requests served within a certain time + 50% 54 ms + 90% 88 ms + 95% 109 ms + 99% 134 ms + 100% 176 ms (longest request) + + +CLIENTS: *** 10 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 10 +Running on cores: 2 +Agent: none + +Completed requests: 7197 +Total errors: 0 +Total time: 60.004 s +Mean latency: 57.7 ms +Effective rps: 120 + +Percentage of requests served within a certain time + 50% 53 ms + 90% 83 ms + 95% 98 ms + 99% 123 ms + 100% 176 ms (longest request) + + +CLIENTS: *** 16 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 16 +Running on cores: 2 +Agent: none + +Completed requests: 7173 +Total errors: 0 +Total time: 60.002 s +Mean latency: 57.9 ms +Effective rps: 120 + +Percentage of requests served within a certain time + 50% 53 ms + 90% 83 ms + 95% 100 ms + 99% 123 ms + 100% 156 ms (longest request) + + +CLIENTS: *** 20 *** + + +Target URL: ws://192.168.2.131/ws +Max time (s): 60 +Concurrent clients: 20 +Running on cores: 2 +Agent: none + +Completed requests: 6883 +Total errors: 0 +Total time: 60.002 s +Mean latency: 60.4 ms +Effective rps: 115 + +Percentage of requests served within a certain time + 50% 55 ms + 90% 92 ms + 95% 111 ms + 99% 138 ms + 100% 175 ms (longest request) diff --git a/lib/PsychicHttp/benchmark/websocket-client-test.js b/lib/PsychicHttp/benchmark/websocket-client-test.js new file mode 100644 index 0000000..3021bb0 --- /dev/null +++ b/lib/PsychicHttp/benchmark/websocket-client-test.js @@ -0,0 +1,36 @@ +#!/usr/bin/env node + +const WebSocket = require('ws'); + +const uri = 'ws://192.168.2.131/ws'; + +async function websocketClient() { + console.log(`Starting test`); + for (let i = 0; i < 1000000; i++) { + const ws = new WebSocket(uri); + + if (i % 100 == 0) + console.log(`Count: ${i}`); + + ws.on('open', () => { + //console.log(`Connected`); + }); + + ws.on('message', (message) => { + //console.log(`Message: ${message}`); + ws.close(); + }); + + ws.on('error', (error) => { + console.error(`Error: ${error.message}`); + }); + + await new Promise((resolve) => { + ws.on('close', () => { + resolve(); + }); + }); + } +} + +websocketClient(); \ No newline at end of file diff --git a/lib/PsychicHttp/component.mk b/lib/PsychicHttp/component.mk new file mode 100644 index 0000000..bb5bb16 --- /dev/null +++ b/lib/PsychicHttp/component.mk @@ -0,0 +1,3 @@ +COMPONENT_ADD_INCLUDEDIRS := src +COMPONENT_SRCDIRS := src +CXXFLAGS += -fno-rtti diff --git a/lib/PsychicHttp/examples/arduino/.gitignore b/lib/PsychicHttp/examples/arduino/.gitignore new file mode 100644 index 0000000..9e5f911 --- /dev/null +++ b/lib/PsychicHttp/examples/arduino/.gitignore @@ -0,0 +1,6 @@ +.pio +.vscode/ +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/lib/PsychicHttp/examples/arduino/arduino.ino b/lib/PsychicHttp/examples/arduino/arduino.ino new file mode 100644 index 0000000..c0fe060 --- /dev/null +++ b/lib/PsychicHttp/examples/arduino/arduino.ino @@ -0,0 +1,497 @@ +/* + PsychicHTTP Server Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/********************************************************************************************** +* Note: this demo relies on the following libraries (Install via Library Manager) +* ArduinoJson UrlEncode +**********************************************************************************************/ + +/********************************************************************************************** +* Note: this demo relies on various files to be uploaded on the LittleFS partition +* Follow instructions here: https://randomnerdtutorials.com/esp32-littlefs-arduino-ide/ +**********************************************************************************************/ + +#include +#include +#include +#include +#include +#include "_secret.h" +#include +#include //uncomment this to enable HTTPS / SSL + +#ifndef WIFI_SSID + #error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there." +#endif + +//Enter your WIFI credentials in secret.h +const char *ssid = WIFI_SSID; +const char *password = WIFI_PASS; + +// Set your SoftAP credentials +const char *softap_ssid = "PsychicHttp"; +const char *softap_password = ""; +IPAddress softap_ip(10, 0, 0, 1); + +//credentials for the /auth-basic and /auth-digest examples +const char *app_user = "admin"; +const char *app_pass = "admin"; +const char *app_name = "Your App"; + +//hostname for mdns (psychic.local) +const char *local_hostname = "psychic"; + +//#define PSY_ENABLE_SSL to enable ssl +#ifdef PSY_ENABLE_SSL + bool app_enable_ssl = true; + String server_cert; + String server_key; +#endif + +//our main server object +#ifdef PSY_ENABLE_SSL + PsychicHttpsServer server; +#else + PsychicHttpServer server; +#endif +PsychicWebSocketHandler websocketHandler; +PsychicEventSource eventSource; + +bool connectToWifi() +{ + //dual client and AP mode + WiFi.mode(WIFI_AP_STA); + + // Configure SoftAP + WiFi.softAPConfig(softap_ip, softap_ip, IPAddress(255, 255, 255, 0)); // subnet FF FF FF 00 + WiFi.softAP(softap_ssid, softap_password); + IPAddress myIP = WiFi.softAPIP(); + Serial.print("SoftAP IP Address: "); + Serial.println(myIP); + + Serial.println(); + Serial.print("[WiFi] Connecting to "); + Serial.println(ssid); + + WiFi.begin(ssid, password); + // Auto reconnect is set true as default + // To set auto connect off, use the following function + // WiFi.setAutoReconnect(false); + + // Will try for about 10 seconds (20x 500ms) + int tryDelay = 500; + int numberOfTries = 20; + + // Wait for the WiFi event + while (true) + { + switch (WiFi.status()) + { + case WL_NO_SSID_AVAIL: + Serial.println("[WiFi] SSID not found"); + break; + case WL_CONNECT_FAILED: + Serial.print("[WiFi] Failed - WiFi not connected! Reason: "); + return false; + break; + case WL_CONNECTION_LOST: + Serial.println("[WiFi] Connection was lost"); + break; + case WL_SCAN_COMPLETED: + Serial.println("[WiFi] Scan is completed"); + break; + case WL_DISCONNECTED: + Serial.println("[WiFi] WiFi is disconnected"); + break; + case WL_CONNECTED: + Serial.println("[WiFi] WiFi is connected!"); + Serial.print("[WiFi] IP address: "); + Serial.println(WiFi.localIP()); + return true; + break; + default: + Serial.print("[WiFi] WiFi Status: "); + Serial.println(WiFi.status()); + break; + } + delay(tryDelay); + + if (numberOfTries <= 0) + { + Serial.print("[WiFi] Failed to connect to WiFi!"); + // Use disconnect function to force stop trying to connect + WiFi.disconnect(); + return false; + } + else + { + numberOfTries--; + } + } + + return false; +} + +void setup() +{ + Serial.begin(115200); + delay(10); + + // We start by connecting to a WiFi network + // To debug, please enable Core Debug Level to Verbose + if (connectToWifi()) + { + //set up our esp32 to listen on the local_hostname.local domain + if (!MDNS.begin(local_hostname)) { + Serial.println("Error starting mDNS"); + return; + } + MDNS.addService("http", "tcp", 80); + + if(!LittleFS.begin()) + { + Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode"); + return; + } + + //look up our keys? + #ifdef PSY_ENABLE_SSL + if (app_enable_ssl) + { + File fp = LittleFS.open("/server.crt"); + if (fp) + { + server_cert = fp.readString(); + + // Serial.println("Server Cert:"); + // Serial.println(server_cert); + } + else + { + Serial.println("server.pem not found, SSL not available"); + app_enable_ssl = false; + } + fp.close(); + + File fp2 = LittleFS.open("/server.key"); + if (fp2) + { + server_key = fp2.readString(); + + // Serial.println("Server Key:"); + // Serial.println(server_key); + } + else + { + Serial.println("server.key not found, SSL not available"); + app_enable_ssl = false; + } + fp2.close(); + } + #endif + + //setup server config stuff here + server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls) + + #ifdef PSY_ENABLE_SSL + server.ssl_config.httpd.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls) + + //do we want secure or not? + if (app_enable_ssl) + { + server.listen(443, server_cert.c_str(), server_key.c_str()); + + //this creates a 2nd server listening on port 80 and redirects all requests HTTPS + PsychicHttpServer *redirectServer = new PsychicHttpServer(); + redirectServer->config.ctrl_port = 20424; // just a random port different from the default one + redirectServer->listen(80); + redirectServer->onNotFound([](PsychicRequest *request) { + String url = "https://" + request->host() + request->url(); + return request->redirect(url.c_str()); + }); + } + else + server.listen(80); + #else + server.listen(80); + #endif + + //serve static files from LittleFS/www on / only to clients on same wifi network + //this is where our /index.html file lives + server.serveStatic("/", LittleFS, "/www/")->setFilter(ON_STA_FILTER); + + //serve static files from LittleFS/www-ap on / only to clients on SoftAP + //this is where our /index.html file lives + server.serveStatic("/", LittleFS, "/www-ap/")->setFilter(ON_AP_FILTER); + + //serve static files from LittleFS/img on /img + //it's more efficient to serve everything from a single www directory, but this is also possible. + server.serveStatic("/img", LittleFS, "/img/"); + + //you can also serve single files + server.serveStatic("/myfile.txt", LittleFS, "/custom.txt"); + + //example callback everytime a connection is opened + server.onOpen([](PsychicClient *client) { + Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString()); + }); + + //example callback everytime a connection is closed + server.onClose([](PsychicClient *client) { + Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString()); + }); + + //api - json message passed in as post body + server.on("/api", HTTP_POST, [](PsychicRequest *request) + { + //load our JSON request + StaticJsonDocument<1024> json; + String body = request->body(); + DeserializationError err = deserializeJson(json, body); + + //create our response json + StaticJsonDocument<128> output; + output["msg"] = "status"; + output["status"] = "success"; + output["millis"] = millis(); + + //work with some params + if (json.containsKey("foo")) + { + String foo = json["foo"]; + output["foo"] = foo; + } + + //serialize and return + String jsonBuffer; + serializeJson(output, jsonBuffer); + return request->reply(200, "application/json", jsonBuffer.c_str()); + }); + + //api - parameters passed in via query eg. /api/endpoint?foo=bar + server.on("/ip", HTTP_GET, [](PsychicRequest *request) + { + String output = "Your IP is: " + request->client()->remoteIP().toString(); + return request->reply(output.c_str()); + }); + + //api - parameters passed in via query eg. /api/endpoint?foo=bar + server.on("/api", HTTP_GET, [](PsychicRequest *request) + { + //create a response object + StaticJsonDocument<128> output; + output["msg"] = "status"; + output["status"] = "success"; + output["millis"] = millis(); + + //work with some params + if (request->hasParam("foo")) + { + String foo = request->getParam("foo")->name(); + output["foo"] = foo; + } + + //serialize and return + String jsonBuffer; + serializeJson(output, jsonBuffer); + return request->reply(200, "application/json", jsonBuffer.c_str()); + }); + + //how to redirect a request + server.on("/redirect", HTTP_GET, [](PsychicRequest *request) + { + return request->redirect("/alien.png"); + }); + + //how to do basic auth + server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request) + { + if (!request->authenticate(app_user, app_pass)) + return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in."); + return request->reply("Auth Basic Success!"); + }); + + //how to do digest auth + server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request) + { + if (!request->authenticate(app_user, app_pass)) + return request->requestAuthentication(DIGEST_AUTH, app_name, "You must log in."); + return request->reply("Auth Digest Success!"); + }); + + //example of getting / setting cookies + server.on("/cookies", HTTP_GET, [](PsychicRequest *request) + { + PsychicResponse response(request); + + int counter = 0; + if (request->hasCookie("counter")) + { + counter = std::stoi(request->getCookie("counter").c_str()); + counter++; + } + + char cookie[10]; + sprintf(cookie, "%i", counter); + + response.setCookie("counter", cookie); + response.setContent(cookie); + return response.send(); + }); + + //example of getting POST variables + server.on("/post", HTTP_POST, [](PsychicRequest *request) + { + String output; + output += "Param 1: " + request->getParam("param1")->value() + "
\n"; + output += "Param 2: " + request->getParam("param2")->value() + "
\n"; + + return request->reply(output.c_str()); + }); + + //you can set up a custom 404 handler. + server.onNotFound([](PsychicRequest *request) + { + return request->reply(404, "text/html", "Custom 404 Handler"); + }); + + //handle a very basic upload as post body + PsychicUploadHandler *uploadHandler = new PsychicUploadHandler(); + uploadHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { + File file; + String path = "/www/" + filename; + + Serial.printf("Writing %d/%d bytes to: %s\n", (int)index+(int)len, request->contentLength(), path.c_str()); + + if (last) + Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len); + + //our first call? + if (!index) + file = LittleFS.open(path, FILE_WRITE); + else + file = LittleFS.open(path, FILE_APPEND); + + if(!file) { + Serial.println("Failed to open file"); + return ESP_FAIL; + } + + if(!file.write(data, len)) { + Serial.println("Write failed"); + return ESP_FAIL; + } + + return ESP_OK; + }); + + //gets called after upload has been handled + uploadHandler->onRequest([](PsychicRequest *request) + { + String url = "/" + request->getFilename(); + String output = "" + url + ""; + + return request->reply(output.c_str()); + }); + + //wildcard basic file upload - POST to /upload/filename.ext + server.on("/upload/*", HTTP_POST, uploadHandler); + + //a little bit more complicated multipart form + PsychicUploadHandler *multipartHandler = new PsychicUploadHandler(); + multipartHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { + File file; + String path = "/www/" + filename; + + //some progress over serial. + Serial.printf("Writing %d bytes to: %s\n", (int)len, path.c_str()); + if (last) + Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len); + + //our first call? + if (!index) + file = LittleFS.open(path, FILE_WRITE); + else + file = LittleFS.open(path, FILE_APPEND); + + if(!file) { + Serial.println("Failed to open file"); + return ESP_FAIL; + } + + if(!file.write(data, len)) { + Serial.println("Write failed"); + return ESP_FAIL; + } + + return ESP_OK; + }); + + //gets called after upload has been handled + multipartHandler->onRequest([](PsychicRequest *request) + { + PsychicWebParameter *file = request->getParam("file_upload"); + + String url = "/" + file->value(); + String output; + + output += "" + url + "
\n"; + output += "Bytes: " + String(file->size()) + "
\n"; + output += "Param 1: " + request->getParam("param1")->value() + "
\n"; + output += "Param 2: " + request->getParam("param2")->value() + "
\n"; + + return request->reply(output.c_str()); + }); + + //wildcard basic file upload - POST to /upload/filename.ext + server.on("/multipart", HTTP_POST, multipartHandler); + + //a websocket echo server + websocketHandler.onOpen([](PsychicWebSocketClient *client) { + Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->localIP().toString()); + client->sendMessage("Hello!"); + }); + websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) { + Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload); + return request->reply(frame); + }); + websocketHandler.onClose([](PsychicWebSocketClient *client) { + Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->localIP().toString()); + }); + server.on("/ws", &websocketHandler); + + //EventSource server + eventSource.onOpen([](PsychicEventSourceClient *client) { + Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->localIP().toString()); + client->send("Hello user!", NULL, millis(), 1000); + }); + eventSource.onClose([](PsychicEventSourceClient *client) { + Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->localIP().toString()); + }); + server.on("/events", &eventSource); + } +} + +unsigned long lastUpdate = 0; +char output[60]; + +void loop() +{ + if (millis() - lastUpdate > 2000) + { + sprintf(output, "Millis: %d\n", millis()); + websocketHandler.sendAll(output); + + sprintf(output, "%d", millis()); + eventSource.send(output, "millis", millis(), 0); + + lastUpdate = millis(); + } +} \ No newline at end of file diff --git a/lib/PsychicHttp/examples/arduino/arduino_captive_portal/README.md b/lib/PsychicHttp/examples/arduino/arduino_captive_portal/README.md new file mode 100644 index 0000000..f965eda --- /dev/null +++ b/lib/PsychicHttp/examples/arduino/arduino_captive_portal/README.md @@ -0,0 +1,60 @@ +**1) SUMMARY** + +This example implements a **captive portal** with library DNSServer, ie web page which opens automatically when user connects Wifi network (eg "PsychitHttp"). + +Captiveportal is implemented in **ESPAsyncWebServer** [https://github.com/me-no-dev/ESPAsyncWebServer/blob/master/examples/CaptivePortal/CaptivePortal.ino](url) and in **arduino-esp32 examples** [https://github.com/espressif/arduino-esp32/blob/master/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino](url) + +This feature can be implemented with Psychichttp with a **dedicated handler**, as shown in code below. + +Code highlights are added below for reference. + +**2) CODE** + +**Definitions** +``` +// captiveportal +// credits https://github.com/me-no-dev/ESPAsyncWebServer/blob/master/examples/CaptivePortal/CaptivePortal.ino +//https://github.com/espressif/arduino-esp32/blob/master/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino +#include +DNSServer dnsServer; +class CaptiveRequestHandler : public PsychicWebHandler { // handler +public: + CaptiveRequestHandler() {}; + virtual ~CaptiveRequestHandler() {}; + bool canHandle(PsychicRequest*request){ + // ... if needed some tests ... return(false); + return true; // activate captive portal + } + esp_err_t handleRequest(PsychicRequest *request) { + //PsychicFileResponse response(request, LittleFS, "/captiveportal.html"); // uncomment : for captive portal page, if any, eg "captiveportal.html" + //return response.send(); // uncomment : return captive portal page + return request->reply(200,"text/html","Welcome to captive portal !"); // simple text, comment if captive portal page + } +}; +CaptiveRequestHandler *captivehandler=NULL; // handler for captive portal +``` + +**setup()** +``` + // captive portal + dnsServer.start(53, "*", WiFi.softAPIP()); // DNS requests are executed over port 53 (standard) + captivehandler= new CaptiveRequestHandler(); // create captive portal handler, important : after server.on since handlers are triggered on a first created/first trigerred basis + server.addHandler(captivehandler); // captive portal handler (last handler) +``` + +**loop()** +``` + dnsServer.processNextRequest(); // captive portal +``` + +**3) RESULT** + +**Access Point (web page is opened automatically when connecting to PsychicHttp AP)** +![captive portal access point](images/accesspoint.png) + +**Station (web page is shown whatever url for Station IP, eg 192.168.1.50/abcdefg** +![captive portal station point](images/station.png) + + + + diff --git a/lib/PsychicHttp/examples/arduino/arduino_captive_portal/arduino_captive_portal.ino b/lib/PsychicHttp/examples/arduino/arduino_captive_portal/arduino_captive_portal.ino new file mode 100644 index 0000000..fbe83a3 --- /dev/null +++ b/lib/PsychicHttp/examples/arduino/arduino_captive_portal/arduino_captive_portal.ino @@ -0,0 +1,144 @@ +/* + PsychicHTTP Server Captive Portal Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include +#include +#include + +char* TAG = "CAPTPORT"; + +// captiveportal +// credits https://github.com/me-no-dev/ESPAsyncWebServer/blob/master/examples/CaptivePortal/CaptivePortal.ino +//https://github.com/espressif/arduino-esp32/blob/master/libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino +#include +DNSServer dnsServer; +class CaptiveRequestHandler : public PsychicWebHandler { // handler +public: + CaptiveRequestHandler() {}; + virtual ~CaptiveRequestHandler() {}; + bool canHandle(PsychicRequest*request){ + // ... if needed some tests ... return(false); + return true; // activate captive portal + } + esp_err_t handleRequest(PsychicRequest *request) { + //PsychicFileResponse response(request, LittleFS, "/captiveportal.html"); // uncomment : for captive portal page, if any, eg "captiveportal.html" + //return response.send(); // uncomment : return captive portal page + return request->reply(200,"text/html","Welcome to captive portal !"); // simple text, comment if captive portal page + } +}; +CaptiveRequestHandler *captivehandler=NULL; // handler for captive portal + +const char* ssid = "mySSID"; // replace with your SSID (mode STATION) +const char* password = "myPassword"; // replace with you password (mode STATION) + +// Set your SoftAP credentials +const char *softap_ssid = "PsychicHttp"; +const char *softap_password = ""; +IPAddress softap_ip(10, 0, 0, 1); + +//hostname for mdns (psychic.local) +const char *local_hostname = "psychic"; + +//our main server object +PsychicHttpServer server; + +bool connectToWifi() { + //dual client and AP mode + WiFi.mode(WIFI_AP_STA); + + // Configure SoftAP + WiFi.softAPConfig(softap_ip, softap_ip, IPAddress(255, 255, 255, 0)); // subnet FF FF FF 00 + WiFi.softAP(softap_ssid, softap_password); + IPAddress myIP = WiFi.softAPIP(); + ESP_LOGI(TAG,"SoftAP IP Address: %s", myIP.toString().c_str()); + ESP_LOGI(TAG,"[WiFi] Connecting to %s", ssid); + + WiFi.begin(ssid, password); + + // Will try for about 10 seconds (20x 500ms) + int tryDelay = 500; + int numberOfTries = 20; + + // Wait for the WiFi event + while (true) { + switch (WiFi.status()) { + case WL_NO_SSID_AVAIL: + ESP_LOGE(TAG,"[WiFi] SSID not found"); + break; + case WL_CONNECT_FAILED: + ESP_LOGI(TAG,"[WiFi] Failed - WiFi not connected! Reason: "); + return false; + break; + case WL_CONNECTION_LOST: + ESP_LOGI(TAG,"[WiFi] Connection was lost"); + break; + case WL_SCAN_COMPLETED: + ESP_LOGI(TAG,"[WiFi] Scan is completed"); + break; + case WL_DISCONNECTED: + ESP_LOGI(TAG,"[WiFi] WiFi is disconnected"); + break; + case WL_CONNECTED: + ESP_LOGI(TAG,"[WiFi] WiFi is connected, IP address %s",WiFi.localIP().toString().c_str()); + return true; + break; + default: + ESP_LOGI(TAG,"[WiFi] WiFi Status: %d",WiFi.status()); + break; + } + delay(tryDelay); + + if (numberOfTries <= 0) { + ESP_LOGI(TAG,"[WiFi] Failed to connect to WiFi!"); + // Use disconnect function to force stop trying to connect + WiFi.disconnect(); + return false; + } + else numberOfTries--; + } + + return false; +} // end connectToWifi + +void setup() { + Serial.begin(115200); + delay(10); + + // Wifi + if (connectToWifi()) { // set up our esp32 to listen on the local_hostname.local domain + if (!MDNS.begin(local_hostname)) { + ESP_LOGE(TAG,"Error starting mDNS"); + return; + } + MDNS.addService("http", "tcp", 80); + + if(!LittleFS.begin()) { + ESP_LOGI(TAG,"ERROR : LittleFS Mount Failed."); + return; + } + + //setup server config stuff here + server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls) + server.listen(80); + + DefaultHeaders::Instance().addHeader("Server", "PsychicHttp"); + + // captive portal + dnsServer.start(53, "*", WiFi.softAPIP()); // DNS requests are executed over port 53 (standard) + captivehandler= new CaptiveRequestHandler(); // create captive portal handler, important : after server.on since handlers are triggered on a first created/first trigerred basis + server.addHandler(captivehandler); // captive portal handler (last handler) + } // end set up our esp32 to listen on the local_hostname.local domain +} // end setup + +void loop() { + dnsServer.processNextRequest(); // captive portal +} diff --git a/lib/PsychicHttp/examples/arduino/arduino_captive_portal/images/accesspoint.png b/lib/PsychicHttp/examples/arduino/arduino_captive_portal/images/accesspoint.png new file mode 100644 index 0000000..76c3590 Binary files /dev/null and b/lib/PsychicHttp/examples/arduino/arduino_captive_portal/images/accesspoint.png differ diff --git a/lib/PsychicHttp/examples/arduino/arduino_captive_portal/images/station.png b/lib/PsychicHttp/examples/arduino/arduino_captive_portal/images/station.png new file mode 100644 index 0000000..525a530 Binary files /dev/null and b/lib/PsychicHttp/examples/arduino/arduino_captive_portal/images/station.png differ diff --git a/lib/PsychicHttp/examples/arduino/arduino_ota/README.md b/lib/PsychicHttp/examples/arduino/arduino_ota/README.md new file mode 100644 index 0000000..d5f8183 --- /dev/null +++ b/lib/PsychicHttp/examples/arduino/arduino_ota/README.md @@ -0,0 +1,138 @@ +**OTA update example for PsychicHttp** + +Example of OTA (Over The Air) update implementation for PsychicHttp, using Arduino IDE. + +**Requirements** +Requirements for project are : +- OTA update for code (firmware) +- OTA update for data (littlefs) +- manual restart of ESP32, triggered by User (no automatic restart after code or data file upload) + +**Implementation** + +OTA update relies on handler PsychicUploadHandler. + +Screenshots and Code are shown below. + +**Credits** + +https://github.com/hoeken/PsychicHttp/blob/master/src/PsychicUploadHandler.cpp + +https://github.com/hoeken/PsychicHttp/issues/30 + +**Configuration** + +Example has been implemented with following configuration :\ +Arduino IDE 1.8.19\ +arduino-32 v2.0.15\ +PsychicHttp 1.1.1\ +ESP32S3 + +**Example Files Structure** + +``` +arduino_ota + data + | update.html + arduino_ota.ino + code.bin + littlefs.bin + README +``` +"code.bin" and "littlefs.bin" are example update files which can be used to update respectily code (firmware) or data (littlefs). + +"Real" update files can be generated on Arduino IDE 1.x : +- for code, menu "Sketch -> Export bin" +- for data, using plugin arduino-esp32fs-plugin https://github.com/lorol/arduino-esp32fs-plugin/releases + +**SCREENSHOTS** + +**Update code (firmware)** +![otaupdate1](images/otaupdate1.png) + +![otaupdate2](images/otaupdate2.png)\ +```ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x1 (POWERON),boot:0x2b (SPI_FAST_FLASH_BOOT) +SPIWP:0xee +mode:DIO, clock div:1 +load:0x3fce3808,len:0x4bc +load:0x403c9700,len:0xbd8 +load:0x403cc700,len:0x2a0c +entry 0x403c98d0 +[332885][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 51 +[332895][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 867306 +[332908][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 862016 +[332919][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0 +[332929][I][arduino_ota.ino:133] operator()(): [OTA] update begin, filename code.bin +[333082][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 856272 +[333095][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0 +[snip] +[339557][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 416 +[339566][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 1 +[339718][I][arduino_ota.ino:165] operator()(): [OTA] Update Success: 867072 written +[339726][I][arduino_ota.ino:184] operator()(): [OTA] Update code or data OK Update.errorString() No Error +[339738][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 52 +[339747][I][PsychicHttpServer.cpp:262] closeCallback(): [psychic] Client disconnected 52 + +``` + + +**Update data (littlefs)** + +![otaupdate3](images/otaupdate3.png) +``` +[ 48216][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 51 +[ 48226][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 1573100 +[ 48239][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 1567810 +[ 48250][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0 +[ 48261][I][arduino_ota.ino:133] operator()(): [OTA] update begin, filename littlefs.bin +[ 48376][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 1562066 +[ 48389][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0 +[ 48408][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 1556322 +[ 48421][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 1550578 +[ 48432][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0 +[snip] +[ 54317][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 16930 +[ 54327][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0 +[ 54340][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 11186 +[ 54351][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0 +[ 54363][I][PsychicUploadHandler.cpp:164] _multipartUploadHandler(): [psychic] Remaining size : 5442 +[ 54375][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 0 +[ 54386][I][arduino_ota.ino:128] operator()(): [OTA] updateHandler->onUpload _error 0 Update.hasError() 0 last 1 +[ 54396][I][arduino_ota.ino:165] operator()(): [OTA] Update Success: 1572864 written +[ 54404][I][arduino_ota.ino:184] operator()(): [OTA] Update code or data OK Update.errorString() No Error +[ 54415][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 52 +[ 54424][I][PsychicHttpServer.cpp:262] closeCallback(): [psychic] Client disconnected 52 + +``` + +**Restart** + +![otaupdate4](images/otaupdate4.png) + +![otaupdate5](images/otaupdate5.png) + +``` +[110318][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 51 +[110327][I][arduino_ota.ino:205] operator()(): [OTA] Restarting ... +[110338][I][PsychicHttpServer.cpp:236] openCallback(): [psychic] New client connected 52 +[111317][W][WiFiGeneric.cpp:1062] _eventCallback(): Reason: 8 - ASSOC_LEAVE +[111319][I][PsychicHttpServer.cpp:262] closeCallback(): [psychic] Client disconnected 51 +[111332][I][PsychicHttpServer.cpp:262] closeCallback(): [psychic] Client disconnected 52 +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0xc (RTC_SW_CPU_RST),boot:0x8 (SPI_FAST_FLASH_BOOT) +Saved PC:0x420984ae +SPIWP:0xee +mode:DIO, clock div:1 +load:0x3fce3808,len:0x4bc +load:0x403c9700,len:0xbd8 +load:0x403cc700,len:0x2a0c +entry 0x403c98d0 +[ 283][I][arduino_ota.ino:57] connectToWifi(): [OTA] [WiFi] WiFi is disconnected +[ 791][I][arduino_ota.ino:60] connectToWifi(): [OTA] [WiFi] WiFi is connected, IP address 192.168.1.50 + +``` + + diff --git a/lib/PsychicHttp/examples/arduino/arduino_ota/arduino_ota.ino b/lib/PsychicHttp/examples/arduino/arduino_ota/arduino_ota.ino new file mode 100644 index 0000000..47f611f --- /dev/null +++ b/lib/PsychicHttp/examples/arduino/arduino_ota/arduino_ota.ino @@ -0,0 +1,219 @@ +/* + Over The Air (OTA) update example for PsychicHttp web server + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. + +*/ +char *TAG = "OTA"; // ESP_LOG tag + +// PsychicHttp +#include +#include +#include +#include +#include +PsychicHttpServer server; // main server object +const char *local_hostname = "psychichttp"; // hostname for mdns + +// OTA +#include +bool esprestart=false; // true if/when ESP should be restarted, after OTA update + +// Wifi +const char *ssid = "SSID"; // your SSID +const char *password = "PASSWORD"; // your PASSWORD + +bool connectToWifi() { // Wifi + //client in STA mode + WiFi.mode(WIFI_AP_STA); + WiFi.begin(ssid,password); + + WiFi.begin(ssid, password); + // Will try for about 10 seconds (20x 500ms) + int tryDelay = 500; + int numberOfTries = 20; + + // Wait for the WiFi event + while (true) { + switch (WiFi.status()) { + case WL_NO_SSID_AVAIL: + ESP_LOGE(TAG,"[WiFi] SSID not found"); + break; + case WL_CONNECT_FAILED: + ESP_LOGI(TAG,"[WiFi] Failed - WiFi not connected! Reason: "); + return false; + break; + case WL_CONNECTION_LOST: + ESP_LOGI(TAG,"[WiFi] Connection was lost"); + break; + case WL_SCAN_COMPLETED: + ESP_LOGI(TAG,"[WiFi] Scan is completed"); + break; + case WL_DISCONNECTED: + ESP_LOGI(TAG,"[WiFi] WiFi is disconnected"); + break; + case WL_CONNECTED: + ESP_LOGI(TAG,"[WiFi] WiFi is connected, IP address %s",WiFi.localIP().toString().c_str()); + return true; + break; + default: + ESP_LOGI(TAG,"[WiFi] WiFi Status: %d",WiFi.status()); + break; + } + delay(tryDelay); + + if (numberOfTries <= 0) { + ESP_LOGI(TAG,"[WiFi] Failed to connect to WiFi!"); + // Use disconnect function to force stop trying to connect + WiFi.disconnect(); + return false; + } + else numberOfTries--; + } + return false; +} + + +// ======================================================================= +// setup +// ======================================================================= +void setup() +{ Serial.begin(115200); + delay(10); + + // Wifi + if (connectToWifi()) { //set up our esp32 to listen on the local_hostname.local domain + if (!MDNS.begin(local_hostname)) { + ESP_LOGE(TAG,"Error starting mDNS"); + return; + } + MDNS.addService("http", "tcp", 80); + + if(!LittleFS.begin()) { + ESP_LOGI(TAG,"ERROR : LittleFS Mount Failed."); + return; + } + + //setup server config stuff here + server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls) + server.listen(80); + + DefaultHeaders::Instance().addHeader("Server", "PsychicHttp"); + + //server.maxRequestBodySize=2*1024*1024; // 2Mb, change default value if needed + //server.maxUploadSize=64*1024*1024; // 64Mb, change default value if needed + + //you can set up a custom 404 handler. + // curl -i http://psychic.local/404 + server.onNotFound([](PsychicRequest *request) { + return request->reply(404, "text/html", "Custom 404 Handler"); + }); + + // OTA + PsychicUploadHandler *updateHandler = new PsychicUploadHandler(); // create handler for OTA update + updateHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { // onUpload + /* callback to upload code (firmware) or data (littlefs) + * callback is triggered for each file chunk, from first chunk (index is 0) to last chunk (last is true) + * callback is triggered by handler in handleRequest(), after _multipartUploadHandler() * + * filename : name of file to upload, with naming convention below + * "*code.bin" for code (firmware) update, eg "v1_code.bin" + * "*littlefs.bin" for data (little fs) update, eg "v1_littlefs.bin" * + */ + + int command; //command : firmware and filesystem update type, ie code (U_FLASH 0) or data (U_SPIFFS 100) + ESP_LOGI(TAG,"updateHandler->onUpload _error %d Update.hasError() %d last %d", Update.getError(), Update.hasError(), last); + + // Update.abort() replaces 1st error (eg "UPDATE_ERROR_ERASE") with abort error ("UPDATE_ERROR_ABORT") so root cause is lost + if (!Update.hasError()) { // no error encountered so far during update, process current chunk + if (!index){ // index is 0, begin update (first chunk) + ESP_LOGI(TAG,"update begin, filename %s", filename.c_str()); + Update.clearError(); // first chunk, clear Update error if any + // check if update file is code, data or sd card one + if (!filename.endsWith("code.bin") && !filename.endsWith("littlefs.bin")) { // incorrect file name + ESP_LOGE(TAG,"ERROR : filename %s format is incorrect", filename.c_str()); + if (!Update.hasError()) Update.abort(); + return(ESP_FAIL); + } // end incorrect file name + else { // file name is correct + // check update type : code or data + if (filename.endsWith("code.bin")) command=U_FLASH; // update code + else command=U_SPIFFS; // update data + if (!Update.begin(UPDATE_SIZE_UNKNOWN, command)) { // start update with max available size + // error, begin is KO + if (!Update.hasError()) Update.abort(); // abort + ESP_LOGE(TAG,"ERROR : update.begin error Update.errorString() %s",Update.errorString()); + return(ESP_FAIL); + } + } // end file name is correct + } // end begin update + + if ((len) && (!Update.hasError())) { // ongoing update if no error encountered + if (Update.write(data, len) != len) { + // error, write is KO + if (!Update.hasError()) Update.abort(); + ESP_LOGE(TAG,"ERROR : update.write len %d Update.errorString() %s",len, Update.errorString()) ; + return(ESP_FAIL); + } + } // end ongoing update + + if ((last) && (!Update.hasError())) { // last update if no error encountered + if (Update.end(true)) { // update end is OKTEST + ESP_LOGI(TAG, "Update Success: %u written", index+len); + } + else { // update end is KO + if (!Update.hasError()) Update.abort(); // abort + ESP_LOGE(TAG,"ERROR : update end error Update.errorString() %s", Update.errorString()); + return(ESP_FAIL); + } + } // last update if no error encountered + return(ESP_OK); + } // end no error encountered so far during update, process current chunk + else { // error encountered so far during update + return(ESP_FAIL); + } + }); // end onUpload + + updateHandler->onRequest([](PsychicRequest *request) { // triggered when update is completed (either OK or KO) and returns request's response (important) + String result; // request result + // code below is executed when update is finished + if (!Update.hasError()) { // update is OK + ESP_LOGI(TAG,"Update code or data OK Update.errorString() %s", Update.errorString()); + result = "Update done for file."; + return request->reply(200,"text/html",result.c_str()); + // ESP.restart(); // restart ESP if needed + } // end update is OK + else { // update is KO, send request with pretty print error + result = " Update.errorString() " + String(Update.errorString()); + ESP_LOGE(TAG,"ERROR : error %s",result.c_str()); + return request->reply(500, "text/html", result.c_str()); + } // end update is KO + }); + + server.on("/update", HTTP_GET, [](PsychicRequest*request){ + PsychicFileResponse response(request, LittleFS, "/update.html"); + return response.send(); + }); + + server.on("/update", HTTP_POST, updateHandler); + + server.on("/restart", HTTP_POST, [](PsychicRequest *request) { + String output = "Restarting ..."; + ESP_LOGI(TAG,"%s",output.c_str()); + esprestart=true; + return request->reply(output.c_str()); + }); + } // end onRequest + +} // end setup + +// ======================================================================= +// loop +// ======================================================================= +void loop() { + delay(2000); + if (esprestart) ESP.restart(); // restart ESP +} // end loop diff --git a/lib/PsychicHttp/examples/arduino/arduino_ota/code.bin b/lib/PsychicHttp/examples/arduino/arduino_ota/code.bin new file mode 100644 index 0000000..f9842f4 Binary files /dev/null and b/lib/PsychicHttp/examples/arduino/arduino_ota/code.bin differ diff --git a/lib/PsychicHttp/examples/arduino/arduino_ota/data/update.html b/lib/PsychicHttp/examples/arduino/arduino_ota/data/update.html new file mode 100644 index 0000000..aedd817 --- /dev/null +++ b/lib/PsychicHttp/examples/arduino/arduino_ota/data/update.html @@ -0,0 +1,166 @@ + + + PSYCHICHTTP + + + + + + + + + +
+

PsychicHttp OTA Update

+
+ +

This page allows to test OTA update with PsychicHttp, and file naming convention below : +

    +
  • "*code.bin" for code (firmware) update, eg "v1_code.bin"
  • +
  • "*littlefs.bin" for data (littlefs) update, eg "v1_littlefs.bin"
  • +
+
+ +
+
+ +
+
+ +
+

Update must be done for each of the files provided (code, littlefs). Once updates are made, the ESP32 can be restarted. + +

+
+ + + + \ No newline at end of file diff --git a/lib/PsychicHttp/examples/arduino/arduino_ota/images/otaupdate1.png b/lib/PsychicHttp/examples/arduino/arduino_ota/images/otaupdate1.png new file mode 100644 index 0000000..c47e4e9 Binary files /dev/null and b/lib/PsychicHttp/examples/arduino/arduino_ota/images/otaupdate1.png differ diff --git a/lib/PsychicHttp/examples/arduino/arduino_ota/images/otaupdate2.png b/lib/PsychicHttp/examples/arduino/arduino_ota/images/otaupdate2.png new file mode 100644 index 0000000..c6be666 Binary files /dev/null and b/lib/PsychicHttp/examples/arduino/arduino_ota/images/otaupdate2.png differ diff --git a/lib/PsychicHttp/examples/arduino/arduino_ota/images/otaupdate3.png b/lib/PsychicHttp/examples/arduino/arduino_ota/images/otaupdate3.png new file mode 100644 index 0000000..ecefade Binary files /dev/null and b/lib/PsychicHttp/examples/arduino/arduino_ota/images/otaupdate3.png differ diff --git a/lib/PsychicHttp/examples/arduino/arduino_ota/images/otaupdate4.png b/lib/PsychicHttp/examples/arduino/arduino_ota/images/otaupdate4.png new file mode 100644 index 0000000..3c86f1c Binary files /dev/null and b/lib/PsychicHttp/examples/arduino/arduino_ota/images/otaupdate4.png differ diff --git a/lib/PsychicHttp/examples/arduino/arduino_ota/images/otaupdate5.png b/lib/PsychicHttp/examples/arduino/arduino_ota/images/otaupdate5.png new file mode 100644 index 0000000..dc87798 Binary files /dev/null and b/lib/PsychicHttp/examples/arduino/arduino_ota/images/otaupdate5.png differ diff --git a/lib/PsychicHttp/examples/arduino/arduino_ota/littlefs.bin b/lib/PsychicHttp/examples/arduino/arduino_ota/littlefs.bin new file mode 100644 index 0000000..403ad59 Binary files /dev/null and b/lib/PsychicHttp/examples/arduino/arduino_ota/littlefs.bin differ diff --git a/lib/PsychicHttp/examples/arduino/data/custom.txt b/lib/PsychicHttp/examples/arduino/data/custom.txt new file mode 100644 index 0000000..d3db23d --- /dev/null +++ b/lib/PsychicHttp/examples/arduino/data/custom.txt @@ -0,0 +1 @@ +Custom text file. \ No newline at end of file diff --git a/lib/PsychicHttp/examples/arduino/data/img/request_flow.png b/lib/PsychicHttp/examples/arduino/data/img/request_flow.png new file mode 100644 index 0000000..1005a38 Binary files /dev/null and b/lib/PsychicHttp/examples/arduino/data/img/request_flow.png differ diff --git a/lib/PsychicHttp/examples/arduino/data/server.crt b/lib/PsychicHttp/examples/arduino/data/server.crt new file mode 100644 index 0000000..34a1e01 --- /dev/null +++ b/lib/PsychicHttp/examples/arduino/data/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL +BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMTgx +MDE3MTEzMjU3WhcNMjgxMDE0MTEzMjU3WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ +UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T +sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k +qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd +GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4 +sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb +jAn4PCuR2akdF4G8WLUeDWECAwEAAaNTMFEwHQYDVR0OBBYEFMnmdJKOEepXrHI/ +ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud +EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADiXIGEkSsN0SLSfCF1VNWO3 +emBurfOcDq4EGEaxRKAU0814VEmU87btIDx80+z5Dbf+GGHCPrY7odIkxGNn0DJY +W1WcF+DOcbiWoUN6DTkAML0SMnp8aGj9ffx3x+qoggT+vGdWVVA4pgwqZT7Ybntx +bkzcNFW0sqmCv4IN1t4w6L0A87ZwsNwVpre/j6uyBw7s8YoJHDLRFT6g7qgn0tcN +ZufhNISvgWCVJQy/SZjNBHSpnIdCUSJAeTY2mkM4sGxY0Widk8LnjydxZUSxC3Nl +hb6pnMh3jRq4h0+5CZielA4/a+TdrNPv/qok67ot/XJdY3qHCCd8O2b14OVq9jo= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/lib/PsychicHttp/examples/arduino/data/server.key b/lib/PsychicHttp/examples/arduino/data/server.key new file mode 100644 index 0000000..a591325 --- /dev/null +++ b/lib/PsychicHttp/examples/arduino/data/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH +JioMD7U7BitLgpcYPi8Cid1l7snt6Kp546iQsDBJ3l8xnRtPU7ANEsjT8KxIHmyw +h/NGp94FlOKRw3ahh3yUGtowS9vdHv+S+TAfuj07NjSnKIyv5KnGZJ+fDFl4Q1tT +aQJybY1Z4itirL6/2CGEm8g/iYhLNDBsRMfpDpfXe4URyWiM3Rhf7ztqZdveb9al +3pAJZIDTLWCFQI1MvQjKamkAQkES/gZj0iUZFwbGJPBj54nkuLFLKedw7DbwgrVg +0+n3fQ9b/gQepw5PxQjyobY2DsDgGZV+MFjUmaUTa+XX68SrG4wJ+DwrkdmpHReB +vFi1Hg1hAgMBAAECggEAaTCnZkl/7qBjLexIryC/CBBJyaJ70W1kQ7NMYfniWwui +f0aRxJgOdD81rjTvkINsPp+xPRQO6oOadjzdjImYEuQTqrJTEUnntbu924eh+2D9 +Mf2CAanj0mglRnscS9mmljZ0KzoGMX6Z/EhnuS40WiJTlWlH6MlQU/FDnwC6U34y +JKy6/jGryfsx+kGU/NRvKSru6JYJWt5v7sOrymHWD62IT59h3blOiP8GMtYKeQlX +49om9Mo1VTIFASY3lrxmexbY+6FG8YO+tfIe0tTAiGrkb9Pz6tYbaj9FjEWOv4Vc ++3VMBUVdGJjgqvE8fx+/+mHo4Rg69BUPfPSrpEg7sQKBgQDlL85G04VZgrNZgOx6 +pTlCCl/NkfNb1OYa0BELqWINoWaWQHnm6lX8YjrUjwRpBF5s7mFhguFjUjp/NW6D +0EEg5BmO0ePJ3dLKSeOA7gMo7y7kAcD/YGToqAaGljkBI+IAWK5Su5yldrECTQKG +YnMKyQ1MWUfCYEwHtPvFvE5aPwKBgQDFBWXekpxHIvt/B41Cl/TftAzE7/f58JjV +MFo/JCh9TDcH6N5TMTRS1/iQrv5M6kJSSrHnq8pqDXOwfHLwxetpk9tr937VRzoL +CuG1Ar7c1AO6ujNnAEmUVC2DppL/ck5mRPWK/kgLwZSaNcZf8sydRgphsW1ogJin +7g0nGbFwXwKBgQCPoZY07Pr1TeP4g8OwWTu5F6dSvdU2CAbtZthH5q98u1n/cAj1 +noak1Srpa3foGMTUn9CHu+5kwHPIpUPNeAZZBpq91uxa5pnkDMp3UrLIRJ2uZyr8 +4PxcknEEh8DR5hsM/IbDcrCJQglM19ZtQeW3LKkY4BsIxjDf45ymH407IQKBgE/g +Ul6cPfOxQRlNLH4VMVgInSyyxWx1mODFy7DRrgCuh5kTVh+QUVBM8x9lcwAn8V9/ +nQT55wR8E603pznqY/jX0xvAqZE6YVPcw4kpZcwNwL1RhEl8GliikBlRzUL3SsW3 +q30AfqEViHPE3XpE66PPo6Hb1ymJCVr77iUuC3wtAoGBAIBrOGunv1qZMfqmwAY2 +lxlzRgxgSiaev0lTNxDzZkmU/u3dgdTwJ5DDANqPwJc6b8SGYTp9rQ0mbgVHnhIB +jcJQBQkTfq6Z0H6OoTVi7dPs3ibQJFrtkoyvYAbyk36quBmNRjVh6rc8468bhXYr +v/t+MeGJP/0Zw8v/X2CFll96 +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/lib/PsychicHttp/examples/arduino/data/www-ap/index.html b/lib/PsychicHttp/examples/arduino/data/www-ap/index.html new file mode 100644 index 0000000..73df596 --- /dev/null +++ b/lib/PsychicHttp/examples/arduino/data/www-ap/index.html @@ -0,0 +1,15 @@ + + + + + + PsychicHTTP SoftAP Demo + + + +
+

SoftAP Demo

+

You are connected to the ESP in SoftAP mode.

+
+ + \ No newline at end of file diff --git a/lib/PsychicHttp/examples/arduino/data/www/alien.png b/lib/PsychicHttp/examples/arduino/data/www/alien.png new file mode 100644 index 0000000..a030da0 Binary files /dev/null and b/lib/PsychicHttp/examples/arduino/data/www/alien.png differ diff --git a/lib/PsychicHttp/examples/arduino/data/www/favicon.ico b/lib/PsychicHttp/examples/arduino/data/www/favicon.ico new file mode 100644 index 0000000..bdf785c Binary files /dev/null and b/lib/PsychicHttp/examples/arduino/data/www/favicon.ico differ diff --git a/lib/PsychicHttp/examples/arduino/data/www/index.html b/lib/PsychicHttp/examples/arduino/data/www/index.html new file mode 100644 index 0000000..4ee2491 --- /dev/null +++ b/lib/PsychicHttp/examples/arduino/data/www/index.html @@ -0,0 +1,236 @@ + + + + + + PsychicHTTP Demo + + + +
+

Basic Request Examples

+ + +

Static Serving

+

+ + +

+

Text File

+ +

Simple POST Form

+
+ + +
+ + +
+ +
+ +

Basic File Upload

+ + + + + + + + + + + + +
+ + + +
+ + + +
+ +
+ + +

Multipart POST Form

+
+ + +
+ + + +
+ + + +
+ + +
+ +

Websocket Demo

+ + + +
+ +
+ + + +

EventSource Demo

+ +
+ +
+ + +
+ + \ No newline at end of file diff --git a/lib/PsychicHttp/examples/arduino/data/www/text.txt b/lib/PsychicHttp/examples/arduino/data/www/text.txt new file mode 100644 index 0000000..5375816 --- /dev/null +++ b/lib/PsychicHttp/examples/arduino/data/www/text.txt @@ -0,0 +1 @@ +Test File. diff --git a/lib/PsychicHttp/examples/arduino/secret.h b/lib/PsychicHttp/examples/arduino/secret.h new file mode 100644 index 0000000..6d4bb15 --- /dev/null +++ b/lib/PsychicHttp/examples/arduino/secret.h @@ -0,0 +1,2 @@ +#define WIFI_SSID "Your_SSID" +#define WIFI_PASS "Your_PASS" \ No newline at end of file diff --git a/lib/PsychicHttp/examples/esp-idf/.gitignore b/lib/PsychicHttp/examples/esp-idf/.gitignore new file mode 100644 index 0000000..3413246 --- /dev/null +++ b/lib/PsychicHttp/examples/esp-idf/.gitignore @@ -0,0 +1,4 @@ +build/ +sdkconfig +sdkconfig.old +components/ diff --git a/lib/PsychicHttp/examples/esp-idf/CMakeLists.txt b/lib/PsychicHttp/examples/esp-idf/CMakeLists.txt new file mode 100644 index 0000000..634d58e --- /dev/null +++ b/lib/PsychicHttp/examples/esp-idf/CMakeLists.txt @@ -0,0 +1,19 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +if(DEFINED ENV{HTTP_PATH}) + set(HTTP_PATH $ENV{HTTP_PATH}) +else() + #these both work + set(HTTP_PATH "../../") + #set(HTTP_PATH ${CMAKE_CURRENT_LIST_DIR}/../../../) + + #this does not work for me... + #set(HTTP_PATH ${CMAKE_CURRENT_LIST_DIR}/../../../PsychicHttp) +endif(DEFINED ENV{HTTP_PATH}) + +set(EXTRA_COMPONENT_DIRS ${HTTP_PATH}) + +project(PsychicHttp_IDF) \ No newline at end of file diff --git a/lib/PsychicHttp/examples/esp-idf/README.md b/lib/PsychicHttp/examples/esp-idf/README.md new file mode 100644 index 0000000..ef91f3c --- /dev/null +++ b/lib/PsychicHttp/examples/esp-idf/README.md @@ -0,0 +1,7 @@ +# PsychicHttp - ESP IDF Example +* Download and install [ESP IDF 4.4.7](https://github.com/espressif/esp-idf/releases/tag/v4.4.7) (or later version) +* Clone the project: ```git clone --recursive git@github.com:hoeken/PsychicHttp.git``` +* Run build command: ```cd PsychicHttp/examples/esp-idf``` and then ```idf.py build``` +* Flash the LittleFS filesystem: ```esptool.py write_flash --flash_mode dio --flash_freq 40m --flash_size 4MB 0x317000 build/littlefs.bin``` +* Flash the app firmware: ```idf.py flash monitor``` and visit the IP address shown in the console with a web browser. +* Learn more about [Arduino as ESP-IDF Component](https://docs.espressif.com/projects/arduino-esp32/en/latest/esp-idf_component.html) diff --git a/lib/PsychicHttp/examples/esp-idf/data/custom.txt b/lib/PsychicHttp/examples/esp-idf/data/custom.txt new file mode 100644 index 0000000..d3db23d --- /dev/null +++ b/lib/PsychicHttp/examples/esp-idf/data/custom.txt @@ -0,0 +1 @@ +Custom text file. \ No newline at end of file diff --git a/lib/PsychicHttp/examples/esp-idf/data/img/request_flow.png b/lib/PsychicHttp/examples/esp-idf/data/img/request_flow.png new file mode 100644 index 0000000..1005a38 Binary files /dev/null and b/lib/PsychicHttp/examples/esp-idf/data/img/request_flow.png differ diff --git a/lib/PsychicHttp/examples/esp-idf/data/server.crt b/lib/PsychicHttp/examples/esp-idf/data/server.crt new file mode 100644 index 0000000..34a1e01 --- /dev/null +++ b/lib/PsychicHttp/examples/esp-idf/data/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL +BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMTgx +MDE3MTEzMjU3WhcNMjgxMDE0MTEzMjU3WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ +UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T +sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k +qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd +GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4 +sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb +jAn4PCuR2akdF4G8WLUeDWECAwEAAaNTMFEwHQYDVR0OBBYEFMnmdJKOEepXrHI/ +ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud +EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADiXIGEkSsN0SLSfCF1VNWO3 +emBurfOcDq4EGEaxRKAU0814VEmU87btIDx80+z5Dbf+GGHCPrY7odIkxGNn0DJY +W1WcF+DOcbiWoUN6DTkAML0SMnp8aGj9ffx3x+qoggT+vGdWVVA4pgwqZT7Ybntx +bkzcNFW0sqmCv4IN1t4w6L0A87ZwsNwVpre/j6uyBw7s8YoJHDLRFT6g7qgn0tcN +ZufhNISvgWCVJQy/SZjNBHSpnIdCUSJAeTY2mkM4sGxY0Widk8LnjydxZUSxC3Nl +hb6pnMh3jRq4h0+5CZielA4/a+TdrNPv/qok67ot/XJdY3qHCCd8O2b14OVq9jo= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/lib/PsychicHttp/examples/esp-idf/data/server.key b/lib/PsychicHttp/examples/esp-idf/data/server.key new file mode 100644 index 0000000..a591325 --- /dev/null +++ b/lib/PsychicHttp/examples/esp-idf/data/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH +JioMD7U7BitLgpcYPi8Cid1l7snt6Kp546iQsDBJ3l8xnRtPU7ANEsjT8KxIHmyw +h/NGp94FlOKRw3ahh3yUGtowS9vdHv+S+TAfuj07NjSnKIyv5KnGZJ+fDFl4Q1tT +aQJybY1Z4itirL6/2CGEm8g/iYhLNDBsRMfpDpfXe4URyWiM3Rhf7ztqZdveb9al +3pAJZIDTLWCFQI1MvQjKamkAQkES/gZj0iUZFwbGJPBj54nkuLFLKedw7DbwgrVg +0+n3fQ9b/gQepw5PxQjyobY2DsDgGZV+MFjUmaUTa+XX68SrG4wJ+DwrkdmpHReB +vFi1Hg1hAgMBAAECggEAaTCnZkl/7qBjLexIryC/CBBJyaJ70W1kQ7NMYfniWwui +f0aRxJgOdD81rjTvkINsPp+xPRQO6oOadjzdjImYEuQTqrJTEUnntbu924eh+2D9 +Mf2CAanj0mglRnscS9mmljZ0KzoGMX6Z/EhnuS40WiJTlWlH6MlQU/FDnwC6U34y +JKy6/jGryfsx+kGU/NRvKSru6JYJWt5v7sOrymHWD62IT59h3blOiP8GMtYKeQlX +49om9Mo1VTIFASY3lrxmexbY+6FG8YO+tfIe0tTAiGrkb9Pz6tYbaj9FjEWOv4Vc ++3VMBUVdGJjgqvE8fx+/+mHo4Rg69BUPfPSrpEg7sQKBgQDlL85G04VZgrNZgOx6 +pTlCCl/NkfNb1OYa0BELqWINoWaWQHnm6lX8YjrUjwRpBF5s7mFhguFjUjp/NW6D +0EEg5BmO0ePJ3dLKSeOA7gMo7y7kAcD/YGToqAaGljkBI+IAWK5Su5yldrECTQKG +YnMKyQ1MWUfCYEwHtPvFvE5aPwKBgQDFBWXekpxHIvt/B41Cl/TftAzE7/f58JjV +MFo/JCh9TDcH6N5TMTRS1/iQrv5M6kJSSrHnq8pqDXOwfHLwxetpk9tr937VRzoL +CuG1Ar7c1AO6ujNnAEmUVC2DppL/ck5mRPWK/kgLwZSaNcZf8sydRgphsW1ogJin +7g0nGbFwXwKBgQCPoZY07Pr1TeP4g8OwWTu5F6dSvdU2CAbtZthH5q98u1n/cAj1 +noak1Srpa3foGMTUn9CHu+5kwHPIpUPNeAZZBpq91uxa5pnkDMp3UrLIRJ2uZyr8 +4PxcknEEh8DR5hsM/IbDcrCJQglM19ZtQeW3LKkY4BsIxjDf45ymH407IQKBgE/g +Ul6cPfOxQRlNLH4VMVgInSyyxWx1mODFy7DRrgCuh5kTVh+QUVBM8x9lcwAn8V9/ +nQT55wR8E603pznqY/jX0xvAqZE6YVPcw4kpZcwNwL1RhEl8GliikBlRzUL3SsW3 +q30AfqEViHPE3XpE66PPo6Hb1ymJCVr77iUuC3wtAoGBAIBrOGunv1qZMfqmwAY2 +lxlzRgxgSiaev0lTNxDzZkmU/u3dgdTwJ5DDANqPwJc6b8SGYTp9rQ0mbgVHnhIB +jcJQBQkTfq6Z0H6OoTVi7dPs3ibQJFrtkoyvYAbyk36quBmNRjVh6rc8468bhXYr +v/t+MeGJP/0Zw8v/X2CFll96 +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/lib/PsychicHttp/examples/esp-idf/data/www-ap/index.html b/lib/PsychicHttp/examples/esp-idf/data/www-ap/index.html new file mode 100644 index 0000000..73df596 --- /dev/null +++ b/lib/PsychicHttp/examples/esp-idf/data/www-ap/index.html @@ -0,0 +1,15 @@ + + + + + + PsychicHTTP SoftAP Demo + + + +
+

SoftAP Demo

+

You are connected to the ESP in SoftAP mode.

+
+ + \ No newline at end of file diff --git a/lib/PsychicHttp/examples/esp-idf/data/www/alien.png b/lib/PsychicHttp/examples/esp-idf/data/www/alien.png new file mode 100644 index 0000000..a030da0 Binary files /dev/null and b/lib/PsychicHttp/examples/esp-idf/data/www/alien.png differ diff --git a/lib/PsychicHttp/examples/esp-idf/data/www/favicon.ico b/lib/PsychicHttp/examples/esp-idf/data/www/favicon.ico new file mode 100644 index 0000000..bdf785c Binary files /dev/null and b/lib/PsychicHttp/examples/esp-idf/data/www/favicon.ico differ diff --git a/lib/PsychicHttp/examples/esp-idf/data/www/index.html b/lib/PsychicHttp/examples/esp-idf/data/www/index.html new file mode 100644 index 0000000..4ee2491 --- /dev/null +++ b/lib/PsychicHttp/examples/esp-idf/data/www/index.html @@ -0,0 +1,236 @@ + + + + + + PsychicHTTP Demo + + + +
+

Basic Request Examples

+ + +

Static Serving

+

+ + +

+

Text File

+ +

Simple POST Form

+
+ + +
+ + +
+ +
+ +

Basic File Upload

+ + + + + + + + + + + + +
+ + + +
+ + + +
+ +
+ + +

Multipart POST Form

+
+ + +
+ + + +
+ + + +
+ + +
+ +

Websocket Demo

+ + + +
+ +
+ + + +

EventSource Demo

+ +
+ +
+ + +
+ + \ No newline at end of file diff --git a/lib/PsychicHttp/examples/esp-idf/data/www/text.txt b/lib/PsychicHttp/examples/esp-idf/data/www/text.txt new file mode 100644 index 0000000..5375816 --- /dev/null +++ b/lib/PsychicHttp/examples/esp-idf/data/www/text.txt @@ -0,0 +1 @@ +Test File. diff --git a/lib/PsychicHttp/examples/esp-idf/include/README b/lib/PsychicHttp/examples/esp-idf/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/lib/PsychicHttp/examples/esp-idf/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/PsychicHttp/examples/esp-idf/lib/README b/lib/PsychicHttp/examples/esp-idf/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/PsychicHttp/examples/esp-idf/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/PsychicHttp/examples/esp-idf/main/CMakeLists.txt b/lib/PsychicHttp/examples/esp-idf/main/CMakeLists.txt new file mode 100644 index 0000000..ad22981 --- /dev/null +++ b/lib/PsychicHttp/examples/esp-idf/main/CMakeLists.txt @@ -0,0 +1,8 @@ +# This file was automatically generated for projects +# without default 'CMakeLists.txt' file. + +idf_component_register( + SRCS "main.cpp" + INCLUDE_DIRS ".") + +littlefs_create_partition_image(littlefs ${project_dir}/data) \ No newline at end of file diff --git a/lib/PsychicHttp/examples/esp-idf/main/main.cpp b/lib/PsychicHttp/examples/esp-idf/main/main.cpp new file mode 100644 index 0000000..21c18e4 --- /dev/null +++ b/lib/PsychicHttp/examples/esp-idf/main/main.cpp @@ -0,0 +1,510 @@ +/* + PsychicHTTP Server Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/********************************************************************************************** +* Note: this demo relies on the following libraries (Install via Library Manager) +* ArduinoJson UrlEncode +**********************************************************************************************/ + +/********************************************************************************************** +* Note: this demo relies on various files to be uploaded on the LittleFS partition +* Follow instructions here: https://randomnerdtutorials.com/esp32-littlefs-arduino-ide/ +**********************************************************************************************/ + +#include +#include +#include +#include +#include +#include "secret.h" +#include +#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE //set this to y in menuconfig to enable SSL +#include +#endif + +#ifndef WIFI_SSID + #error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there." +#endif + +//Enter your WIFI credentials in secret.h +const char *ssid = WIFI_SSID; +const char *password = WIFI_PASS; + +// Set your SoftAP credentials +const char *softap_ssid = "PsychicHttp"; +const char *softap_password = ""; +IPAddress softap_ip(10, 0, 0, 1); + +//credentials for the /auth-basic and /auth-digest examples +const char *app_user = "admin"; +const char *app_pass = "admin"; +const char *app_name = "Your App"; + +//hostname for mdns (psychic.local) +const char *local_hostname = "psychic"; + +//#define CONFIG_ESP_HTTPS_SERVER_ENABLE to enable ssl +#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE + bool app_enable_ssl = true; + String server_cert; + String server_key; +#endif + +//our main server object +#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE + PsychicHttpsServer server; +#else + PsychicHttpServer server; +#endif +PsychicWebSocketHandler websocketHandler; +PsychicEventSource eventSource; + +bool connectToWifi() +{ + //dual client and AP mode + WiFi.mode(WIFI_AP_STA); + + // Configure SoftAP + WiFi.softAPConfig(softap_ip, softap_ip, IPAddress(255, 255, 255, 0)); // subnet FF FF FF 00 + WiFi.softAP(softap_ssid, softap_password); + IPAddress myIP = WiFi.softAPIP(); + Serial.print("SoftAP IP Address: "); + Serial.println(myIP); + + Serial.println(); + Serial.print("[WiFi] Connecting to "); + Serial.println(ssid); + + WiFi.begin(ssid, password); + // Auto reconnect is set true as default + // To set auto connect off, use the following function + // WiFi.setAutoReconnect(false); + + // Will try for about 10 seconds (20x 500ms) + int tryDelay = 500; + int numberOfTries = 20; + + // Wait for the WiFi event + while (true) + { + switch (WiFi.status()) + { + case WL_NO_SSID_AVAIL: + Serial.println("[WiFi] SSID not found"); + break; + case WL_CONNECT_FAILED: + Serial.print("[WiFi] Failed - WiFi not connected! Reason: "); + return false; + break; + case WL_CONNECTION_LOST: + Serial.println("[WiFi] Connection was lost"); + break; + case WL_SCAN_COMPLETED: + Serial.println("[WiFi] Scan is completed"); + break; + case WL_DISCONNECTED: + Serial.println("[WiFi] WiFi is disconnected"); + break; + case WL_CONNECTED: + Serial.println("[WiFi] WiFi is connected!"); + Serial.print("[WiFi] IP address: "); + Serial.println(WiFi.localIP()); + return true; + break; + default: + Serial.print("[WiFi] WiFi Status: "); + Serial.println(WiFi.status()); + break; + } + delay(tryDelay); + + if (numberOfTries <= 0) + { + Serial.print("[WiFi] Failed to connect to WiFi!"); + // Use disconnect function to force stop trying to connect + WiFi.disconnect(); + return false; + } + else + { + numberOfTries--; + } + } + + return false; +} + +void setup() +{ + Serial.begin(115200); + delay(10); + + // We start by connecting to a WiFi network + // To debug, please enable Core Debug Level to Verbose + if (connectToWifi()) + { + //set up our esp32 to listen on the local_hostname.local domain + if (!MDNS.begin(local_hostname)) { + Serial.println("Error starting mDNS"); + return; + } + MDNS.addService("http", "tcp", 80); + + if(!LittleFS.begin()) + { + Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode"); + return; + } + + //look up our keys? + #ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE + if (app_enable_ssl) + { + File fp = LittleFS.open("/server.crt"); + if (fp) + { + server_cert = fp.readString(); + + // Serial.println("Server Cert:"); + // Serial.println(server_cert); + } + else + { + Serial.println("server.pem not found, SSL not available"); + app_enable_ssl = false; + } + fp.close(); + + File fp2 = LittleFS.open("/server.key"); + if (fp2) + { + server_key = fp2.readString(); + + // Serial.println("Server Key:"); + // Serial.println(server_key); + } + else + { + Serial.println("server.key not found, SSL not available"); + app_enable_ssl = false; + } + fp2.close(); + } + #endif + + //setup server config stuff here + server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls) + + #ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE + server.ssl_config.httpd.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls) + + //do we want secure or not? + if (app_enable_ssl) + { + server.listen(443, server_cert.c_str(), server_key.c_str()); + + //this creates a 2nd server listening on port 80 and redirects all requests HTTPS + PsychicHttpServer *redirectServer = new PsychicHttpServer(); + redirectServer->config.ctrl_port = 20424; // just a random port different from the default one + redirectServer->listen(80); + redirectServer->onNotFound([](PsychicRequest *request) { + String url = "https://" + request->host() + request->url(); + return request->redirect(url.c_str()); + }); + } + else + server.listen(80); + #else + server.listen(80); + #endif + + //serve static files from LittleFS/www on / only to clients on same wifi network + //this is where our /index.html file lives + server.serveStatic("/", LittleFS, "/www/")->setFilter(ON_STA_FILTER); + + //serve static files from LittleFS/www-ap on / only to clients on SoftAP + //this is where our /index.html file lives + server.serveStatic("/", LittleFS, "/www-ap/")->setFilter(ON_AP_FILTER); + + //serve static files from LittleFS/img on /img + //it's more efficient to serve everything from a single www directory, but this is also possible. + server.serveStatic("/img", LittleFS, "/img/"); + + //you can also serve single files + server.serveStatic("/myfile.txt", LittleFS, "/custom.txt"); + + //example callback everytime a connection is opened + server.onOpen([](PsychicClient *client) { + Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str()); + }); + + //example callback everytime a connection is closed + server.onClose([](PsychicClient *client) { + Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); + }); + + //api - json message passed in as post body + server.on("/api", HTTP_POST, [](PsychicRequest *request) + { + //load our JSON request + JsonDocument json; + String body = request->body(); + DeserializationError err = deserializeJson(json, body); + + //create our response json + JsonDocument output; + output["msg"] = "status"; + + //did it parse? + if (err) + { + output["status"] = "failure"; + output["error"] = err.c_str(); + } + else + { + output["status"] = "success"; + output["millis"] = millis(); + + //work with some params + if (json.containsKey("foo")) + { + String foo = json["foo"]; + output["foo"] = foo; + } + } + + //serialize and return + String jsonBuffer; + serializeJson(output, jsonBuffer); + return request->reply(200, "application/json", jsonBuffer.c_str()); + }); + + //api - parameters passed in via query eg. /api/endpoint?foo=bar + server.on("/ip", HTTP_GET, [](PsychicRequest *request) + { + String output = "Your IP is: " + request->client()->remoteIP().toString(); + return request->reply(output.c_str()); + }); + + //api - parameters passed in via query eg. /api/endpoint?foo=bar + server.on("/api", HTTP_GET, [](PsychicRequest *request) + { + //create a response object + JsonDocument output; + output["msg"] = "status"; + output["status"] = "success"; + output["millis"] = millis(); + + //work with some params + if (request->hasParam("foo")) + { + String foo = request->getParam("foo")->name(); + output["foo"] = foo; + } + + //serialize and return + String jsonBuffer; + serializeJson(output, jsonBuffer); + return request->reply(200, "application/json", jsonBuffer.c_str()); + }); + + //how to redirect a request + server.on("/redirect", HTTP_GET, [](PsychicRequest *request) + { + return request->redirect("/alien.png"); + }); + + //how to do basic auth + server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request) + { + if (!request->authenticate(app_user, app_pass)) + return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in."); + return request->reply("Auth Basic Success!"); + }); + + //how to do digest auth + server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request) + { + if (!request->authenticate(app_user, app_pass)) + return request->requestAuthentication(DIGEST_AUTH, app_name, "You must log in."); + return request->reply("Auth Digest Success!"); + }); + + //example of getting / setting cookies + server.on("/cookies", HTTP_GET, [](PsychicRequest *request) + { + PsychicResponse response(request); + + int counter = 0; + if (request->hasCookie("counter")) + { + counter = std::stoi(request->getCookie("counter").c_str()); + counter++; + } + + char cookie[12]; + sprintf(cookie, "%i", counter); + + response.setCookie("counter", cookie); + response.setContent(cookie); + return response.send(); + }); + + //example of getting POST variables + server.on("/post", HTTP_POST, [](PsychicRequest *request) + { + String output; + output += "Param 1: " + request->getParam("param1")->value() + "
\n"; + output += "Param 2: " + request->getParam("param2")->value() + "
\n"; + + return request->reply(output.c_str()); + }); + + //you can set up a custom 404 handler. + server.onNotFound([](PsychicRequest *request) + { + return request->reply(404, "text/html", "Custom 404 Handler"); + }); + + //handle a very basic upload as post body + PsychicUploadHandler *uploadHandler = new PsychicUploadHandler(); + uploadHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { + File file; + String path = "/www/" + filename; + + Serial.printf("Writing %d/%d bytes to: %s\n", (int)index+(int)len, request->contentLength(), path.c_str()); + + if (last) + Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len); + + //our first call? + if (!index) + file = LittleFS.open(path, FILE_WRITE); + else + file = LittleFS.open(path, FILE_APPEND); + + if(!file) { + Serial.println("Failed to open file"); + return ESP_FAIL; + } + + if(!file.write(data, len)) { + Serial.println("Write failed"); + return ESP_FAIL; + } + + return ESP_OK; + }); + + //gets called after upload has been handled + uploadHandler->onRequest([](PsychicRequest *request) + { + String url = "/" + request->getFilename(); + String output = "" + url + ""; + + return request->reply(output.c_str()); + }); + + //wildcard basic file upload - POST to /upload/filename.ext + server.on("/upload/*", HTTP_POST, uploadHandler); + + //a little bit more complicated multipart form + PsychicUploadHandler *multipartHandler = new PsychicUploadHandler(); + multipartHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { + File file; + String path = "/www/" + filename; + + //some progress over serial. + Serial.printf("Writing %d bytes to: %s\n", (int)len, path.c_str()); + if (last) + Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len); + + //our first call? + if (!index) + file = LittleFS.open(path, FILE_WRITE); + else + file = LittleFS.open(path, FILE_APPEND); + + if(!file) { + Serial.println("Failed to open file"); + return ESP_FAIL; + } + + if(!file.write(data, len)) { + Serial.println("Write failed"); + return ESP_FAIL; + } + + return ESP_OK; + }); + + //gets called after upload has been handled + multipartHandler->onRequest([](PsychicRequest *request) + { + PsychicWebParameter *file = request->getParam("file_upload"); + + String url = "/" + file->value(); + String output; + + output += "" + url + "
\n"; + output += "Bytes: " + String(file->size()) + "
\n"; + output += "Param 1: " + request->getParam("param1")->value() + "
\n"; + output += "Param 2: " + request->getParam("param2")->value() + "
\n"; + + return request->reply(output.c_str()); + }); + + //wildcard basic file upload - POST to /upload/filename.ext + server.on("/multipart", HTTP_POST, multipartHandler); + + //a websocket echo server + websocketHandler.onOpen([](PsychicWebSocketClient *client) { + Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str()); + client->sendMessage("Hello!"); + }); + websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) { + Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload); + return request->reply(frame); + }); + websocketHandler.onClose([](PsychicWebSocketClient *client) { + Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); + }); + server.on("/ws", &websocketHandler); + + //EventSource server + eventSource.onOpen([](PsychicEventSourceClient *client) { + Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str()); + client->send("Hello user!", NULL, millis(), 1000); + }); + eventSource.onClose([](PsychicEventSourceClient *client) { + Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); + }); + server.on("/events", &eventSource); + } +} + +unsigned long lastUpdate = 0; +char output[60]; + +void loop() +{ + if (millis() - lastUpdate > 2000) + { + sprintf(output, "Millis: %lu\n", millis()); + websocketHandler.sendAll(output); + + sprintf(output, "%lu", millis()); + eventSource.send(output, "millis", millis(), 0); + + lastUpdate = millis(); + } + vTaskDelay(1 / portTICK_PERIOD_MS); // Feed WDT +} \ No newline at end of file diff --git a/lib/PsychicHttp/examples/esp-idf/main/secret.h b/lib/PsychicHttp/examples/esp-idf/main/secret.h new file mode 100644 index 0000000..97048cf --- /dev/null +++ b/lib/PsychicHttp/examples/esp-idf/main/secret.h @@ -0,0 +1,2 @@ +#define WIFI_SSID "WIFI_SSID" +#define WIFI_PASS "WIFI_PASS" \ No newline at end of file diff --git a/lib/PsychicHttp/examples/esp-idf/partitions_custom.csv b/lib/PsychicHttp/examples/esp-idf/partitions_custom.csv new file mode 100644 index 0000000..57f4354 --- /dev/null +++ b/lib/PsychicHttp/examples/esp-idf/partitions_custom.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x11000, 0xC000 +otadata, data, ota, 0x1D000, 0x2000 +phy_init, data, phy, 0x1F000, 0x1000 +app0, app, ota_0, 0x20000, 0x177000 +app1, app, ota_1, 0x1A0000, 0x177000 +littlefs, data, spiffs, 0x317000, 0xE1000 \ No newline at end of file diff --git a/lib/PsychicHttp/examples/esp-idf/sdkconfig.defaults b/lib/PsychicHttp/examples/esp-idf/sdkconfig.defaults new file mode 100644 index 0000000..6b0dc83 --- /dev/null +++ b/lib/PsychicHttp/examples/esp-idf/sdkconfig.defaults @@ -0,0 +1,64 @@ +CONFIG_AUTOSTART_ARDUINO=y +# CONFIG_WS2812_LED_ENABLE is not set +CONFIG_FREERTOS_HZ=1000 + +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y + +# +# Serial flasher config +# +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_ESPTOOLPY_FLASHSIZE="4MB" + +# +# Partition Table +# +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_custom.csv" +#CONFIG_PARTITION_TABLE_FILENAME="partitions_custom.csv" +#CONFIG_PARTITION_TABLE_OFFSET=0xE000 +CONFIG_PARTITION_TABLE_MD5=y + +# +# ESP HTTPS OTA +# +CONFIG_ESP_HTTPS_OTA_DECRYPT_CB=y +CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP=y +# end of ESP HTTPS OTA + + +# +# ESP HTTP client +# +CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y +CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH=y +CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH=y +# end of ESP HTTP client + +# +# HTTP Server +# +CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 +CONFIG_HTTPD_MAX_URI_LEN=512 +CONFIG_HTTPD_ERR_RESP_NO_DELAY=y +CONFIG_HTTPD_PURGE_BUF_LEN=32 +# CONFIG_HTTPD_LOG_PURGE_DATA is not set +CONFIG_HTTPD_WS_SUPPORT=y +# end of HTTP Server + +# +# ESP HTTPS server +# +CONFIG_ESP_HTTPS_SERVER_ENABLE=n +# end of ESP HTTPS server + + +# +# TLS Key Exchange Methods +# +# 2 option require for arduino Arduino +CONFIG_MBEDTLS_PSK_MODES=y +CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y + diff --git a/lib/PsychicHttp/examples/old/esp_ota_http_server/.gitignore b/lib/PsychicHttp/examples/old/esp_ota_http_server/.gitignore new file mode 100644 index 0000000..be2b7e8 --- /dev/null +++ b/lib/PsychicHttp/examples/old/esp_ota_http_server/.gitignore @@ -0,0 +1,39 @@ +.pioenvs +.clang_complete +.gcc-flags.json +# Compiled Object files +*.slo +*.lo +*.o +*.obj +# Precompiled Headers +*.gch +*.pch +# Compiled Dynamic libraries +*.so +*.dylib +*.dll +# Fortran module files +*.mod +# Compiled Static libraries +*.lai +*.la +*.a +*.lib +# Executables +*.exe +*.out +*.app +# Visual Studio/VisualMicro stuff +Visual\ Micro +*.sdf +*.opensdf +*.suo +.pioenvs +.piolibdeps +.pio +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/settings.json +.vscode/.browse.c_cpp.db* +.vscode/ipch \ No newline at end of file diff --git a/lib/PsychicHttp/examples/old/esp_ota_http_server/include/README b/lib/PsychicHttp/examples/old/esp_ota_http_server/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/lib/PsychicHttp/examples/old/esp_ota_http_server/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/PsychicHttp/examples/old/esp_ota_http_server/lib/README b/lib/PsychicHttp/examples/old/esp_ota_http_server/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/PsychicHttp/examples/old/esp_ota_http_server/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/PsychicHttp/examples/old/esp_ota_http_server/platformio.ini b/lib/PsychicHttp/examples/old/esp_ota_http_server/platformio.ini new file mode 100644 index 0000000..8a0c120 --- /dev/null +++ b/lib/PsychicHttp/examples/old/esp_ota_http_server/platformio.ini @@ -0,0 +1,74 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[common] +lib_deps = ArduinoMongoose +monitor_speed = 115200 +monitor_port = /dev/ttyUSB1 +build_flags = + -DENABLE_DEBUG +# -DCS_ENABLE_STDIO + -DMG_ENABLE_HTTP_STREAMING_MULTIPART=1 + +build_flags_secure = + -DSIMPLE_SERVER_SECURE + -DMG_ENABLE_SSL=1 + +# -DMG_SSL_IF=MG_SSL_IF_OPENSSL +# -DKR_VERSION + + -DMG_SSL_MBED_DUMMY_RANDOM=1 + -DMG_SSL_IF=MG_SSL_IF_MBEDTLS + -DMG_SSL_IF_MBEDTLS_FREE_CERTS=1 + -DMG_SSL_IF_MBEDTLS_MAX_FRAG_LEN=2048 + +build_flags_auth = + -DADMIN_USER='"admin"' + -DADMIN_PASS='"admin"' + -DADMIN_REALM='"esp_ota_http_server"' + +#[env:huzzah] +#platform = espressif8266 +#board = huzzah +#framework = arduino +#monitor_speed = ${common.monitor_speed} +#monitor_port = ${common.monitor_port} +#lib_deps = ${common.lib_deps} +#build_flags = ${common.build_flags} + +[env:esp-wrover-kit] +platform = espressif32 +framework = arduino +board = esp-wrover-kit +monitor_speed = ${common.monitor_speed} +monitor_port = ${common.monitor_port} +lib_deps = ${common.lib_deps} +build_flags = ${common.build_flags} + +[env:esp-wrover-kit-secure] +platform = espressif32 +framework = arduino +board = esp-wrover-kit +monitor_speed = ${common.monitor_speed} +monitor_port = ${common.monitor_port} +lib_deps = ${common.lib_deps} +build_flags = ${common.build_flags} ${common.build_flags_secure} + +[env:esp-wrover-kit-auth] +extends = env:esp-wrover-kit +build_flags = ${common.build_flags} ${common.build_flags_auth} -ggdb + +#[env:linux_x86_64] +#platform = linux_x86_64 +#framework = arduino +#board = generic +#lib_deps = ${common.lib_deps} +#build_flags = ${common.build_flags} +#build_flags = -DSERIAL_TO_CONSOLE \ No newline at end of file diff --git a/lib/PsychicHttp/examples/old/esp_ota_http_server/src/esp_ota_http_server.cpp b/lib/PsychicHttp/examples/old/esp_ota_http_server/src/esp_ota_http_server.cpp new file mode 100644 index 0000000..9d9d35b --- /dev/null +++ b/lib/PsychicHttp/examples/old/esp_ota_http_server/src/esp_ota_http_server.cpp @@ -0,0 +1,229 @@ +// +// A simple server implementation showing how to: +// * serve static messages +// * read GET and POST parameters +// * handle missing pages / 404s +// + +#include +#include +#include +#include + +#ifdef ESP32 +#include +#define START_ESP_WIFI +#elif defined(ESP8266) +#include +#define START_ESP_WIFI +#else +#error Platform not supported +#endif + +MongooseHttpServer server; + +const char *ssid = "wifi"; +const char *password = "password"; + +const char *server_pem = +"-----BEGIN CERTIFICATE-----\r\n" +"MIIDDjCCAfagAwIBAgIBBDANBgkqhkiG9w0BAQsFADA/MRkwFwYDVQQDDBB0ZXN0\r\n" +"LmNlc2FudGEuY29tMRAwDgYDVQQKDAdDZXNhbnRhMRAwDgYDVQQLDAd0ZXN0aW5n\r\n" +"MB4XDTE2MTExMzEzMTgwMVoXDTI2MDgxMzEzMTgwMVowFDESMBAGA1UEAwwJbG9j\r\n" +"YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAro8CW1X0xaGm\r\n" +"GkDaMxKbXWA5Lw+seA61tioGrSIQzuqLYeJoFnwVgF0jB5PTj+3EiGMBcA/mh73V\r\n" +"AthTFmJBxj+agIp7/cvUBpgfLClmSYL2fZi6Fodz+f9mcry3XRw7O6vlamtWfTX8\r\n" +"TAmMSR6PXVBHLgjs5pDOFFmrNAsM5sLYU1/1MFvE2Z9InTI5G437IE1WchRSbpYd\r\n" +"HchC39XzpDGoInZB1a3OhcHm+xUtLpMJ0G0oE5VFEynZreZoEIY4JxspQ7LPsay9\r\n" +"fx3Tlk09gEMQgVCeCNiQwUxZdtLau2x61LNcdZCKN7FbFLJszv1U2uguELsTmi7E\r\n" +"6pHrTziosQIDAQABo0AwPjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIDqDATBgNVHSUE\r\n" +"DDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB\r\n" +"AQBUw0hbTcT6crzODO4QAXU7z4Xxn0LkxbXEsoThG1QCVgMc4Bhpx8gyz5CLyHYz\r\n" +"AiJOBFEeV0XEqoGTNMMFelR3Q5Tg9y1TYO3qwwAWxe6/brVzpts6NiG1uEMBnBFg\r\n" +"oN1x3I9x4NpOxU5MU1dlIxvKs5HQCoNJ8D0SqOX9BV/pZqwEgiCbuWDWQAlxkFpn\r\n" +"iLonlkVI5hTuybCSBsa9FEI9M6JJn9LZmlH90FYHeS4t6P8eOJCeekHL0jUG4Iae\r\n" +"DMP12h8Sd0yxIKmmZ+Q/p/D/BkuHf5Idv3hgyLkZ4mNznjK49wHaYM+BgBoL3Zeg\r\n" +"gJ2sWjUlokrbHswSBLLbUJIF\r\n" +"-----END CERTIFICATE-----\r\n"; + +const char *server_key = +"-----BEGIN PRIVATE KEY-----\r\n" +"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCujwJbVfTFoaYa\r\n" +"QNozEptdYDkvD6x4DrW2KgatIhDO6oth4mgWfBWAXSMHk9OP7cSIYwFwD+aHvdUC\r\n" +"2FMWYkHGP5qAinv9y9QGmB8sKWZJgvZ9mLoWh3P5/2ZyvLddHDs7q+Vqa1Z9NfxM\r\n" +"CYxJHo9dUEcuCOzmkM4UWas0CwzmwthTX/UwW8TZn0idMjkbjfsgTVZyFFJulh0d\r\n" +"yELf1fOkMagidkHVrc6Fweb7FS0ukwnQbSgTlUUTKdmt5mgQhjgnGylDss+xrL1/\r\n" +"HdOWTT2AQxCBUJ4I2JDBTFl20tq7bHrUs1x1kIo3sVsUsmzO/VTa6C4QuxOaLsTq\r\n" +"ketPOKixAgMBAAECggEAI+uNwpnHirue4Jwjyoqzqd1ZJxQEm5f7UIcJZKsz5kBh\r\n" +"ej0KykWybv27bZ2/1UhKPv6QlyzOdXRc1v8I6fxCKLeB5Z2Zsjo1YT4AfCfwwoPO\r\n" +"kT3SXTx2YyVpQYcP/HsIvVi8FtALtixbxJHaall9iugwHYr8pN17arihAE6d0wZC\r\n" +"JXtXRjUWwjKzXP8FoH4KhyadhHbDwIbbJe3cyLfdvp54Gr0YHha0JcOxYgDYNya4\r\n" +"OKxlCluI+hPF31iNzOmFLQVrdYynyPcR6vY5XOiANKE2iNbqCzRb54CvW9WMqObX\r\n" +"RD9t3DMOxGsbVNIwyzZndWy13HoQMGnrHfnGak9ueQKBgQDiVtOqYfLnUnTxvJ/b\r\n" +"qlQZr2ZmsYPZztxlP+DSqZGPD+WtGSo9+rozWfzjTv3KGIDLvf+GFVmjVHwlLQfd\r\n" +"u7eTemWHFc4HK68wruzPO/FdyVpQ4w9v3Usg+ll4a/PDEId0fDMjAr6kk4LC6t8y\r\n" +"9fJR0HjOz57jVnlrDt3v50G8BwKBgQDFbw+jRiUxXnBbDyXZLi+I4iGBGdC+CbaJ\r\n" +"CmsM6/TsOFc+GRsPwQF1gCGqdaURw76noIVKZJOSc8I+yiwU6izyh/xaju5JiWQd\r\n" +"kwbU1j4DE6GnxmT3ARmB7VvCxjaEZEAtICWs1QTKRz7PcTV8yr7Ng1A3VIy+NSpo\r\n" +"LFMMmk83hwKBgQDVCEwpLg/mUeHoNVVw95w4oLKNLb+gHeerFLiTDy8FrDzM88ai\r\n" +"l37yHly7xflxYia3nZkHpsi7xiUjCINC3BApKyasQoWskh1OgRY653yCfaYYQ96f\r\n" +"t3WjEH9trI2+p6wWo1+uMEMnu/9zXoW9/WeaQdGzNg+igh29+jxCNTPVuQKBgGV4\r\n" +"CN9vI5pV4QTLqjYOSJvfLDz/mYqxz0BrPE1tz3jAFAZ0PLZCCY/sBGFpCScyJQBd\r\n" +"vWNYgYeZOtGuci1llSgov4eDQfBFTlDsyWwFl+VY55IkoqtXw1ZFOQ3HdSlhpKIM\r\n" +"jZBgApA7QYq3sjeqs5lHzahCKftvs5XKgfxOKjxtAoGBALdnYe6xkDvGLvI51Yr+\r\n" +"Dy0TNcB5W84SxUKvM7DVEomy1QPB57ZpyQaoBq7adOz0pWJXfp7qo4950ZOhBGH1\r\n" +"hKbZ6c4ggwVJy2j49EgMok5NGCKvPAtabbR6H8Mz8DW9aXURxhWJvij+Qw1fWK4b\r\n" +"7G/qUI9iE5iUU7MkIcLIbTf/\r\n" +"-----END PRIVATE KEY-----\r\n"; + +const char* server_index = +"" +"
" + "" + "" +"
" +"
progress: 0%
" +""; + +#include + +bool updateCompleted = false; + +static void updateError(MongooseHttpServerRequest *request) +{ + MongooseHttpServerResponseStream *resp = request->beginResponseStream(); + resp->setCode(500); + resp->setContentType("text/plain"); + resp->printf("Error: %d", Update.getError()); + request->send(resp); + + // Anoyingly this uses Stream rather than Print... + Update.printError(Serial); +} + +void setup() +{ + Serial.begin(115200); + +#ifdef START_ESP_WIFI + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) + { + Serial.printf("WiFi Failed!\n"); + return; + } + + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + Serial.print("Hostname: "); +#ifdef ESP32 + Serial.println(WiFi.getHostname()); +#elif defined(ESP8266) + Serial.println(WiFi.hostname()); +#endif +#endif + + Mongoose.begin(); + +#ifdef SIMPLE_SERVER_SECURE + if(false == server.begin(443, server_pem, server_key)) { + Serial.print("Failed to start server"); + return; + } +#else + server.begin(80); +#endif + + server.on("/$", HTTP_GET, [](MongooseHttpServerRequest *request) { +#if defined(ADMIN_USER) && defined(ADMIN_PASS) && defined(ADMIN_REALM) + if(false == request->authenticate(ADMIN_USER, ADMIN_PASS)) { + request->requestAuthentication(ADMIN_REALM); + return; + } +#endif + + request->send(200, "text/html", server_index); + }); + + server.on("/update$", HTTP_POST)-> + onRequest([](MongooseHttpServerRequest *request) { +#if defined(ADMIN_USER) && defined(ADMIN_PASS) && defined(ADMIN_REALM) + if(false == request->authenticate(ADMIN_USER, ADMIN_PASS)) { + request->requestAuthentication(ADMIN_REALM); + return; + } +#endif + updateCompleted = false; + })-> + onUpload([](MongooseHttpServerRequest *request, int ev, MongooseString filename, uint64_t index, uint8_t *data, size_t len) + { + if(MG_EV_HTTP_PART_BEGIN == ev) { + Serial.printf("Update Start: %s\n", filename.c_str()); + + if (!Update.begin()) { //start with max available size + updateError(request); + } + } + + if(!Update.hasError()) + { + Serial.printf("Update Writing %llu\n", index); + if(Update.write(data, len) != len) { + updateError(request); + } + } + + if(MG_EV_HTTP_PART_END == ev) { + Serial.println("Data finished"); + if(Update.end(true)) { + Serial.printf("Update Success: %lluB\n", index+len); + request->send(200, "text/plain", "OK"); + updateCompleted = true; + } else { + updateError(request); + } + } + + return len; + })-> + onClose([](MongooseHttpServerRequest *request) + { + if(updateCompleted) { + ESP.restart(); + } + }); +} + +void loop() +{ + Mongoose.poll(1000); +} diff --git a/lib/PsychicHttp/examples/old/esp_ota_http_server/test/README b/lib/PsychicHttp/examples/old/esp_ota_http_server/test/README new file mode 100644 index 0000000..df5066e --- /dev/null +++ b/lib/PsychicHttp/examples/old/esp_ota_http_server/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/lib/PsychicHttp/examples/old/simple_http_server/.gitignore b/lib/PsychicHttp/examples/old/simple_http_server/.gitignore new file mode 100644 index 0000000..be2b7e8 --- /dev/null +++ b/lib/PsychicHttp/examples/old/simple_http_server/.gitignore @@ -0,0 +1,39 @@ +.pioenvs +.clang_complete +.gcc-flags.json +# Compiled Object files +*.slo +*.lo +*.o +*.obj +# Precompiled Headers +*.gch +*.pch +# Compiled Dynamic libraries +*.so +*.dylib +*.dll +# Fortran module files +*.mod +# Compiled Static libraries +*.lai +*.la +*.a +*.lib +# Executables +*.exe +*.out +*.app +# Visual Studio/VisualMicro stuff +Visual\ Micro +*.sdf +*.opensdf +*.suo +.pioenvs +.piolibdeps +.pio +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/settings.json +.vscode/.browse.c_cpp.db* +.vscode/ipch \ No newline at end of file diff --git a/lib/PsychicHttp/examples/old/simple_http_server/include/README b/lib/PsychicHttp/examples/old/simple_http_server/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/lib/PsychicHttp/examples/old/simple_http_server/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/PsychicHttp/examples/old/simple_http_server/lib/README b/lib/PsychicHttp/examples/old/simple_http_server/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/PsychicHttp/examples/old/simple_http_server/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/PsychicHttp/examples/old/simple_http_server/platformio.ini b/lib/PsychicHttp/examples/old/simple_http_server/platformio.ini new file mode 100644 index 0000000..5da8a5a --- /dev/null +++ b/lib/PsychicHttp/examples/old/simple_http_server/platformio.ini @@ -0,0 +1,64 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[common] +lib_deps = ArduinoMongoose +monitor_speed = 115200 +monitor_port = /dev/ttyUSB1 +build_flags = + -DENABLE_DEBUG +# -DCS_ENABLE_STDIO + +build_flags_secure = + -DSIMPLE_SERVER_SECURE + -DMG_ENABLE_SSL=1 + +# -DMG_SSL_IF=MG_SSL_IF_OPENSSL +# -DKR_VERSION + + -DMG_SSL_MBED_DUMMY_RANDOM=1 + -DMG_SSL_IF=MG_SSL_IF_MBEDTLS + -DMG_SSL_IF_MBEDTLS_FREE_CERTS=1 + -DMG_SSL_IF_MBEDTLS_MAX_FRAG_LEN=2048 + +[env:huzzah] +platform = espressif8266 +board = huzzah +framework = arduino +monitor_speed = ${common.monitor_speed} +monitor_port = ${common.monitor_port} +lib_deps = ${common.lib_deps} +build_flags = ${common.build_flags} + +[env:esp-wrover-kit] +platform = espressif32 +framework = arduino +board = esp-wrover-kit +monitor_speed = ${common.monitor_speed} +monitor_port = ${common.monitor_port} +lib_deps = ${common.lib_deps} +build_flags = ${common.build_flags} + +[env:esp-wrover-kit-secure] +platform = espressif32 +framework = arduino +board = esp-wrover-kit +monitor_speed = ${common.monitor_speed} +monitor_port = ${common.monitor_port} +lib_deps = ${common.lib_deps} +build_flags = ${common.build_flags} ${common.build_flags_secure} + +#[env:linux_x86_64] +#platform = linux_x86_64 +#framework = arduino +#board = generic +#lib_deps = ${common.lib_deps} +#build_flags = ${common.build_flags} +#build_flags = -DSERIAL_TO_CONSOLE \ No newline at end of file diff --git a/lib/PsychicHttp/examples/old/simple_http_server/src/simple_http_server.cpp b/lib/PsychicHttp/examples/old/simple_http_server/src/simple_http_server.cpp new file mode 100644 index 0000000..ffa1e4d --- /dev/null +++ b/lib/PsychicHttp/examples/old/simple_http_server/src/simple_http_server.cpp @@ -0,0 +1,211 @@ +// +// A simple server implementation showing how to: +// * serve static messages +// * read GET and POST parameters +// * handle missing pages / 404s +// + +#include +#include +#include + +#ifdef ESP32 +#include +#define START_ESP_WIFI +#elif defined(ESP8266) +#include +#define START_ESP_WIFI +#else +#error Platform not supported +#endif + +MongooseHttpServer server; + +const char *ssid = "wifi"; +const char *password = "password"; + +const char *PARAM_MESSAGE = "message"; + +const char *server_pem = +"-----BEGIN CERTIFICATE-----\r\n" +"MIIDDjCCAfagAwIBAgIBBDANBgkqhkiG9w0BAQsFADA/MRkwFwYDVQQDDBB0ZXN0\r\n" +"LmNlc2FudGEuY29tMRAwDgYDVQQKDAdDZXNhbnRhMRAwDgYDVQQLDAd0ZXN0aW5n\r\n" +"MB4XDTE2MTExMzEzMTgwMVoXDTI2MDgxMzEzMTgwMVowFDESMBAGA1UEAwwJbG9j\r\n" +"YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAro8CW1X0xaGm\r\n" +"GkDaMxKbXWA5Lw+seA61tioGrSIQzuqLYeJoFnwVgF0jB5PTj+3EiGMBcA/mh73V\r\n" +"AthTFmJBxj+agIp7/cvUBpgfLClmSYL2fZi6Fodz+f9mcry3XRw7O6vlamtWfTX8\r\n" +"TAmMSR6PXVBHLgjs5pDOFFmrNAsM5sLYU1/1MFvE2Z9InTI5G437IE1WchRSbpYd\r\n" +"HchC39XzpDGoInZB1a3OhcHm+xUtLpMJ0G0oE5VFEynZreZoEIY4JxspQ7LPsay9\r\n" +"fx3Tlk09gEMQgVCeCNiQwUxZdtLau2x61LNcdZCKN7FbFLJszv1U2uguELsTmi7E\r\n" +"6pHrTziosQIDAQABo0AwPjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIDqDATBgNVHSUE\r\n" +"DDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB\r\n" +"AQBUw0hbTcT6crzODO4QAXU7z4Xxn0LkxbXEsoThG1QCVgMc4Bhpx8gyz5CLyHYz\r\n" +"AiJOBFEeV0XEqoGTNMMFelR3Q5Tg9y1TYO3qwwAWxe6/brVzpts6NiG1uEMBnBFg\r\n" +"oN1x3I9x4NpOxU5MU1dlIxvKs5HQCoNJ8D0SqOX9BV/pZqwEgiCbuWDWQAlxkFpn\r\n" +"iLonlkVI5hTuybCSBsa9FEI9M6JJn9LZmlH90FYHeS4t6P8eOJCeekHL0jUG4Iae\r\n" +"DMP12h8Sd0yxIKmmZ+Q/p/D/BkuHf5Idv3hgyLkZ4mNznjK49wHaYM+BgBoL3Zeg\r\n" +"gJ2sWjUlokrbHswSBLLbUJIF\r\n" +"-----END CERTIFICATE-----\r\n"; + +const char *server_key = +"-----BEGIN PRIVATE KEY-----\r\n" +"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCujwJbVfTFoaYa\r\n" +"QNozEptdYDkvD6x4DrW2KgatIhDO6oth4mgWfBWAXSMHk9OP7cSIYwFwD+aHvdUC\r\n" +"2FMWYkHGP5qAinv9y9QGmB8sKWZJgvZ9mLoWh3P5/2ZyvLddHDs7q+Vqa1Z9NfxM\r\n" +"CYxJHo9dUEcuCOzmkM4UWas0CwzmwthTX/UwW8TZn0idMjkbjfsgTVZyFFJulh0d\r\n" +"yELf1fOkMagidkHVrc6Fweb7FS0ukwnQbSgTlUUTKdmt5mgQhjgnGylDss+xrL1/\r\n" +"HdOWTT2AQxCBUJ4I2JDBTFl20tq7bHrUs1x1kIo3sVsUsmzO/VTa6C4QuxOaLsTq\r\n" +"ketPOKixAgMBAAECggEAI+uNwpnHirue4Jwjyoqzqd1ZJxQEm5f7UIcJZKsz5kBh\r\n" +"ej0KykWybv27bZ2/1UhKPv6QlyzOdXRc1v8I6fxCKLeB5Z2Zsjo1YT4AfCfwwoPO\r\n" +"kT3SXTx2YyVpQYcP/HsIvVi8FtALtixbxJHaall9iugwHYr8pN17arihAE6d0wZC\r\n" +"JXtXRjUWwjKzXP8FoH4KhyadhHbDwIbbJe3cyLfdvp54Gr0YHha0JcOxYgDYNya4\r\n" +"OKxlCluI+hPF31iNzOmFLQVrdYynyPcR6vY5XOiANKE2iNbqCzRb54CvW9WMqObX\r\n" +"RD9t3DMOxGsbVNIwyzZndWy13HoQMGnrHfnGak9ueQKBgQDiVtOqYfLnUnTxvJ/b\r\n" +"qlQZr2ZmsYPZztxlP+DSqZGPD+WtGSo9+rozWfzjTv3KGIDLvf+GFVmjVHwlLQfd\r\n" +"u7eTemWHFc4HK68wruzPO/FdyVpQ4w9v3Usg+ll4a/PDEId0fDMjAr6kk4LC6t8y\r\n" +"9fJR0HjOz57jVnlrDt3v50G8BwKBgQDFbw+jRiUxXnBbDyXZLi+I4iGBGdC+CbaJ\r\n" +"CmsM6/TsOFc+GRsPwQF1gCGqdaURw76noIVKZJOSc8I+yiwU6izyh/xaju5JiWQd\r\n" +"kwbU1j4DE6GnxmT3ARmB7VvCxjaEZEAtICWs1QTKRz7PcTV8yr7Ng1A3VIy+NSpo\r\n" +"LFMMmk83hwKBgQDVCEwpLg/mUeHoNVVw95w4oLKNLb+gHeerFLiTDy8FrDzM88ai\r\n" +"l37yHly7xflxYia3nZkHpsi7xiUjCINC3BApKyasQoWskh1OgRY653yCfaYYQ96f\r\n" +"t3WjEH9trI2+p6wWo1+uMEMnu/9zXoW9/WeaQdGzNg+igh29+jxCNTPVuQKBgGV4\r\n" +"CN9vI5pV4QTLqjYOSJvfLDz/mYqxz0BrPE1tz3jAFAZ0PLZCCY/sBGFpCScyJQBd\r\n" +"vWNYgYeZOtGuci1llSgov4eDQfBFTlDsyWwFl+VY55IkoqtXw1ZFOQ3HdSlhpKIM\r\n" +"jZBgApA7QYq3sjeqs5lHzahCKftvs5XKgfxOKjxtAoGBALdnYe6xkDvGLvI51Yr+\r\n" +"Dy0TNcB5W84SxUKvM7DVEomy1QPB57ZpyQaoBq7adOz0pWJXfp7qo4950ZOhBGH1\r\n" +"hKbZ6c4ggwVJy2j49EgMok5NGCKvPAtabbR6H8Mz8DW9aXURxhWJvij+Qw1fWK4b\r\n" +"7G/qUI9iE5iUU7MkIcLIbTf/\r\n" +"-----END PRIVATE KEY-----\r\n"; + +static void notFound(MongooseHttpServerRequest *request); + +#include + +void setup() +{ + Serial.begin(115200); + +#ifdef START_ESP_WIFI + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) + { + Serial.printf("WiFi Failed!\n"); + return; + } + + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + Serial.print("Hostname: "); +#ifdef ESP32 + Serial.println(WiFi.getHostname()); +#elif defined(ESP8266) + Serial.println(WiFi.hostname()); +#endif +#endif + + Mongoose.begin(); + +#ifdef SIMPLE_SERVER_SECURE + if(false == server.begin(443, server_pem, server_key)) { + Serial.print("Failed to start server"); + return; + } +#else + server.begin(80); +#endif + + server.on("/$", HTTP_GET, [](MongooseHttpServerRequest *request) { + request->send(200, "text/plain", "Hello world"); + }); + + // Send a GET request to /get?message= + server.on("/get$", HTTP_GET, [](MongooseHttpServerRequest *request) { + String message; + if (request->hasParam(PARAM_MESSAGE)) + { + message = request->getParam(PARAM_MESSAGE); + } + else + { + message = "No message sent"; + } + request->send(200, "text/plain", "Hello, GET: " + message); + }); + + // Send a POST request to /post with a form field message set to + server.on("/post$", HTTP_POST, [](MongooseHttpServerRequest *request) { + String message; + if (request->hasParam(PARAM_MESSAGE)) + { + message = request->getParam(PARAM_MESSAGE); + } + else + { + message = "No message sent"; + } + request->send(200, "text/plain", "Hello, POST: " + message); + }); + + // Test the basic response class + server.on("/basic$", HTTP_GET, [](MongooseHttpServerRequest *request) { + MongooseHttpServerResponseBasic *resp = request->beginResponse(); + resp->setCode(200); + resp->setContentType("text/html"); + resp->addHeader("Cache-Control", "max-age=300"); + resp->addHeader("X-hello", "world"); + resp->setContent( + "\n" + "\n" + "Basic Page\n" + "\n" + "\n" + "

Basic Page

\n" + "

\n" + "This page has been sent using the MongooseHttpServerResponseBasic class\n" + "

\n" + "\n" + "\n"); + request->send(resp); + }); + + // Test the stream response class + server.on("/stream$", HTTP_GET, [](MongooseHttpServerRequest *request) { + MongooseHttpServerResponseStream *resp = request->beginResponseStream(); + resp->setCode(200); + resp->setContentType("text/html"); + resp->addHeader("Cache-Control", "max-age=300"); + resp->addHeader("X-hello", "world"); + + resp->println(""); + resp->println(""); + resp->println("Stream Page"); + resp->println(""); + resp->println(""); + resp->println("

Stream Page

"); + resp->println("

"); + resp->println("This page has been sent using the MongooseHttpServerResponseStream class"); + resp->println("

"); + resp->println("

"); + resp->printf("micros = %lu
", micros()); + resp->printf("free = %u
", ESP.getFreeHeap()); + resp->println("

"); + resp->println(""); + resp->println(""); + + request->send(resp); + }); + + server.onNotFound(notFound); +} + +void loop() +{ + Mongoose.poll(1000); + Serial.printf("Free memory %u\n", ESP.getFreeHeap()); +} + +static void notFound(MongooseHttpServerRequest *request) +{ + request->send(404, "text/plain", "Not found"); +} diff --git a/lib/PsychicHttp/examples/old/simple_http_server/test/README b/lib/PsychicHttp/examples/old/simple_http_server/test/README new file mode 100644 index 0000000..df5066e --- /dev/null +++ b/lib/PsychicHttp/examples/old/simple_http_server/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/lib/PsychicHttp/examples/old/simple_http_server/test/tests.rest b/lib/PsychicHttp/examples/old/simple_http_server/test/tests.rest new file mode 100644 index 0000000..4072428 --- /dev/null +++ b/lib/PsychicHttp/examples/old/simple_http_server/test/tests.rest @@ -0,0 +1,35 @@ +# Name: REST Client +# Id: humao.rest-client +# Description: REST Client for Visual Studio Code +# Version: 0.21.3 +# Publisher: Huachao Mao +# VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=humao.rest-client + +@baseUrl = http://172.16.0.87 + +### + +GET {{baseUrl}}/ HTTP/1.1 + +### + +GET {{baseUrl}}/get?message=Hello+World HTTP/1.1 + +### + +POST {{baseUrl}}/post HTTP/1.1 +Content-Type: application/x-www-form-urlencoded;charset=UTF-8 + +message=Hello+World + +### + +GET {{baseUrl}}/someRandomFile HTTP/1.1 + +### + +GET {{baseUrl}}/basic HTTP/1.1 + +### + +GET {{baseUrl}}/stream HTTP/1.1 diff --git a/lib/PsychicHttp/examples/old/simplest_web_server_esp/.gitignore b/lib/PsychicHttp/examples/old/simplest_web_server_esp/.gitignore new file mode 100644 index 0000000..be2b7e8 --- /dev/null +++ b/lib/PsychicHttp/examples/old/simplest_web_server_esp/.gitignore @@ -0,0 +1,39 @@ +.pioenvs +.clang_complete +.gcc-flags.json +# Compiled Object files +*.slo +*.lo +*.o +*.obj +# Precompiled Headers +*.gch +*.pch +# Compiled Dynamic libraries +*.so +*.dylib +*.dll +# Fortran module files +*.mod +# Compiled Static libraries +*.lai +*.la +*.a +*.lib +# Executables +*.exe +*.out +*.app +# Visual Studio/VisualMicro stuff +Visual\ Micro +*.sdf +*.opensdf +*.suo +.pioenvs +.piolibdeps +.pio +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/settings.json +.vscode/.browse.c_cpp.db* +.vscode/ipch \ No newline at end of file diff --git a/lib/PsychicHttp/examples/old/simplest_web_server_esp/.travis.yml b/lib/PsychicHttp/examples/old/simplest_web_server_esp/.travis.yml new file mode 100644 index 0000000..7c486f1 --- /dev/null +++ b/lib/PsychicHttp/examples/old/simplest_web_server_esp/.travis.yml @@ -0,0 +1,67 @@ +# Continuous Integration (CI) is the practice, in software +# engineering, of merging all developer working copies with a shared mainline +# several times a day < https://docs.platformio.org/page/ci/index.html > +# +# Documentation: +# +# * Travis CI Embedded Builds with PlatformIO +# < https://docs.travis-ci.com/user/integration/platformio/ > +# +# * PlatformIO integration with Travis CI +# < https://docs.platformio.org/page/ci/travis.html > +# +# * User Guide for `platformio ci` command +# < https://docs.platformio.org/page/userguide/cmd_ci.html > +# +# +# Please choose one of the following templates (proposed below) and uncomment +# it (remove "# " before each line) or use own configuration according to the +# Travis CI documentation (see above). +# + + +# +# Template #1: General project. Test it using existing `platformio.ini`. +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# install: +# - pip install -U platformio +# - platformio update +# +# script: +# - platformio run + + +# +# Template #2: The project is intended to be used as a library with examples. +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# env: +# - PLATFORMIO_CI_SRC=path/to/test/file.c +# - PLATFORMIO_CI_SRC=examples/file.ino +# - PLATFORMIO_CI_SRC=path/to/test/directory +# +# install: +# - pip install -U platformio +# - platformio update +# +# script: +# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N diff --git a/lib/PsychicHttp/examples/old/simplest_web_server_esp/include/README b/lib/PsychicHttp/examples/old/simplest_web_server_esp/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/lib/PsychicHttp/examples/old/simplest_web_server_esp/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/PsychicHttp/examples/old/simplest_web_server_esp/lib/README b/lib/PsychicHttp/examples/old/simplest_web_server_esp/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/PsychicHttp/examples/old/simplest_web_server_esp/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/PsychicHttp/examples/old/simplest_web_server_esp/platformio.ini b/lib/PsychicHttp/examples/old/simplest_web_server_esp/platformio.ini new file mode 100644 index 0000000..d02c3bc --- /dev/null +++ b/lib/PsychicHttp/examples/old/simplest_web_server_esp/platformio.ini @@ -0,0 +1,40 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[common] +lib_deps = ArduinoMongoose +monitor_speed = 115200 +build_flags = + +[espressif8266] +build_flags = -DMG_ESP8266 + +[espressif32] +build_flags = + +[env:huzzah] +platform = espressif8266 +framework = arduino +board = huzzah +monitor_speed = ${common.monitor_speed} +lib_deps = ${common.lib_deps} +build_flags = + ${espressif8266.build_flags} + ${common.build_flags} + +[env:espwroverkit] +platform = espressif32 +framework = arduino +board = esp-wrover-kit +monitor_speed = ${common.monitor_speed} +lib_deps = ${common.lib_deps} +build_flags = + ${espressif32.build_flags} + ${common.build_flags} diff --git a/lib/PsychicHttp/examples/old/simplest_web_server_esp/src/simplest_web_server.cpp b/lib/PsychicHttp/examples/old/simplest_web_server_esp/src/simplest_web_server.cpp new file mode 100644 index 0000000..cedb9c2 --- /dev/null +++ b/lib/PsychicHttp/examples/old/simplest_web_server_esp/src/simplest_web_server.cpp @@ -0,0 +1,97 @@ +// Copyright (c) 2015 Cesanta Software Limited +// All rights reserved + +#include + +#ifdef ESP32 +#include +#elif defined(ESP8266) +#include +#else +#error Platform not supported +#endif + +#include "mongoose.h" + +const char* ssid = "my-ssid"; +const char* password = "my-password"; + +static const char *s_http_port = "80"; +//static struct mg_serve_http_opts s_http_server_opts; + +static void ev_handler(struct mg_connection *nc, int ev, void *p, void *d) { + static const char *reply_fmt = + "HTTP/1.0 200 OK\r\n" + "Connection: close\r\n" + "Content-Type: text/plain\r\n" + "\r\n" + "Hello %s\n"; + + switch (ev) { + case MG_EV_ACCEPT: { + char addr[32]; + mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr), + MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT); + Serial.printf("Connection %p from %s\n", nc, addr); + break; + } + case MG_EV_HTTP_REQUEST: { + char addr[32]; + struct http_message *hm = (struct http_message *) p; + mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr), + MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT); + Serial.printf("HTTP request from %s: %.*s %.*s\n", addr, (int) hm->method.len, + hm->method.p, (int) hm->uri.len, hm->uri.p); + mg_printf(nc, reply_fmt, addr); + nc->flags |= MG_F_SEND_AND_CLOSE; + break; + } + case MG_EV_CLOSE: { + Serial.printf("Connection %p closed\n", nc); + break; + } + } +} + +struct mg_mgr mgr; +struct mg_connection *nc; + +void setup() +{ + Serial.begin(115200); + + Serial.print("Connecting to "); + Serial.println(ssid); + + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + mg_mgr_init(&mgr, NULL); + Serial.printf("Starting web server on port %s\n", s_http_port); + nc = mg_bind(&mgr, s_http_port, ev_handler, NULL); + if (nc == NULL) { + Serial.printf("Failed to create listener\n"); + return; + } + + // Set up HTTP server parameters + mg_set_protocol_http_websocket(nc); +// s_http_server_opts.document_root = "."; // Serve current directory +// s_http_server_opts.enable_directory_listing = "yes"; +} + +static uint32_t count = 0; +void loop() +{ + mg_mgr_poll(&mgr, 1000); + //Serial.println(count++); +} diff --git a/lib/PsychicHttp/examples/old/simplest_web_server_esp/test/README b/lib/PsychicHttp/examples/old/simplest_web_server_esp/test/README new file mode 100644 index 0000000..df5066e --- /dev/null +++ b/lib/PsychicHttp/examples/old/simplest_web_server_esp/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/lib/PsychicHttp/examples/old/websocket_chat/.gitignore b/lib/PsychicHttp/examples/old/websocket_chat/.gitignore new file mode 100644 index 0000000..be2b7e8 --- /dev/null +++ b/lib/PsychicHttp/examples/old/websocket_chat/.gitignore @@ -0,0 +1,39 @@ +.pioenvs +.clang_complete +.gcc-flags.json +# Compiled Object files +*.slo +*.lo +*.o +*.obj +# Precompiled Headers +*.gch +*.pch +# Compiled Dynamic libraries +*.so +*.dylib +*.dll +# Fortran module files +*.mod +# Compiled Static libraries +*.lai +*.la +*.a +*.lib +# Executables +*.exe +*.out +*.app +# Visual Studio/VisualMicro stuff +Visual\ Micro +*.sdf +*.opensdf +*.suo +.pioenvs +.piolibdeps +.pio +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/settings.json +.vscode/.browse.c_cpp.db* +.vscode/ipch \ No newline at end of file diff --git a/lib/PsychicHttp/examples/old/websocket_chat/include/README b/lib/PsychicHttp/examples/old/websocket_chat/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/lib/PsychicHttp/examples/old/websocket_chat/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/PsychicHttp/examples/old/websocket_chat/lib/README b/lib/PsychicHttp/examples/old/websocket_chat/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/PsychicHttp/examples/old/websocket_chat/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/PsychicHttp/examples/old/websocket_chat/platformio.ini b/lib/PsychicHttp/examples/old/websocket_chat/platformio.ini new file mode 100644 index 0000000..165b7ac --- /dev/null +++ b/lib/PsychicHttp/examples/old/websocket_chat/platformio.ini @@ -0,0 +1,64 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[common] +lib_deps = ArduinoMongoose +monitor_speed = 115200 +monitor_port = /dev/ttyUSB2 +build_flags = + -DENABLE_DEBUG +# -DCS_ENABLE_STDIO + +build_flags_secure = + -DSIMPLE_SERVER_SECURE + -DMG_ENABLE_SSL=1 + +# -DMG_SSL_IF=MG_SSL_IF_OPENSSL +# -DKR_VERSION + + -DMG_SSL_MBED_DUMMY_RANDOM=1 + -DMG_SSL_IF=MG_SSL_IF_MBEDTLS + -DMG_SSL_IF_MBEDTLS_FREE_CERTS=1 + -DMG_SSL_IF_MBEDTLS_MAX_FRAG_LEN=2048 + +[env:huzzah] +platform = espressif8266 +board = huzzah +framework = arduino +monitor_speed = ${common.monitor_speed} +monitor_port = ${common.monitor_port} +lib_deps = ${common.lib_deps} +build_flags = ${common.build_flags} + +[env:esp-wrover-kit] +platform = espressif32 +framework = arduino +board = esp-wrover-kit +monitor_speed = ${common.monitor_speed} +monitor_port = ${common.monitor_port} +lib_deps = ${common.lib_deps} +build_flags = ${common.build_flags} + +[env:esp-wrover-kit-secure] +platform = espressif32 +framework = arduino +board = esp-wrover-kit +monitor_speed = ${common.monitor_speed} +monitor_port = ${common.monitor_port} +lib_deps = ${common.lib_deps} +build_flags = ${common.build_flags} ${common.build_flags_secure} + +#[env:linux_x86_64] +#platform = linux_x86_64 +#framework = arduino +#board = generic +#lib_deps = ${common.lib_deps} +#build_flags = ${common.build_flags} +#build_flags = -DSERIAL_TO_CONSOLE \ No newline at end of file diff --git a/lib/PsychicHttp/examples/old/websocket_chat/src/websocket_chat.cpp b/lib/PsychicHttp/examples/old/websocket_chat/src/websocket_chat.cpp new file mode 100644 index 0000000..47a04cf --- /dev/null +++ b/lib/PsychicHttp/examples/old/websocket_chat/src/websocket_chat.cpp @@ -0,0 +1,235 @@ +// +// A simple server implementation showing how to: +// * serve static messages +// * read GET and POST parameters +// * handle missing pages / 404s +// + +#include +#include +#include + +#ifdef ESP32 +#include +#define START_ESP_WIFI +#elif defined(ESP8266) +#include +#define START_ESP_WIFI +#else +#error Platform not supported +#endif + +MongooseHttpServer server; + +const char *ssid = "wifi"; +const char *password = "password"; + +const char *server_pem = +"-----BEGIN CERTIFICATE-----\r\n" +"MIIDDjCCAfagAwIBAgIBBDANBgkqhkiG9w0BAQsFADA/MRkwFwYDVQQDDBB0ZXN0\r\n" +"LmNlc2FudGEuY29tMRAwDgYDVQQKDAdDZXNhbnRhMRAwDgYDVQQLDAd0ZXN0aW5n\r\n" +"MB4XDTE2MTExMzEzMTgwMVoXDTI2MDgxMzEzMTgwMVowFDESMBAGA1UEAwwJbG9j\r\n" +"YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAro8CW1X0xaGm\r\n" +"GkDaMxKbXWA5Lw+seA61tioGrSIQzuqLYeJoFnwVgF0jB5PTj+3EiGMBcA/mh73V\r\n" +"AthTFmJBxj+agIp7/cvUBpgfLClmSYL2fZi6Fodz+f9mcry3XRw7O6vlamtWfTX8\r\n" +"TAmMSR6PXVBHLgjs5pDOFFmrNAsM5sLYU1/1MFvE2Z9InTI5G437IE1WchRSbpYd\r\n" +"HchC39XzpDGoInZB1a3OhcHm+xUtLpMJ0G0oE5VFEynZreZoEIY4JxspQ7LPsay9\r\n" +"fx3Tlk09gEMQgVCeCNiQwUxZdtLau2x61LNcdZCKN7FbFLJszv1U2uguELsTmi7E\r\n" +"6pHrTziosQIDAQABo0AwPjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIDqDATBgNVHSUE\r\n" +"DDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB\r\n" +"AQBUw0hbTcT6crzODO4QAXU7z4Xxn0LkxbXEsoThG1QCVgMc4Bhpx8gyz5CLyHYz\r\n" +"AiJOBFEeV0XEqoGTNMMFelR3Q5Tg9y1TYO3qwwAWxe6/brVzpts6NiG1uEMBnBFg\r\n" +"oN1x3I9x4NpOxU5MU1dlIxvKs5HQCoNJ8D0SqOX9BV/pZqwEgiCbuWDWQAlxkFpn\r\n" +"iLonlkVI5hTuybCSBsa9FEI9M6JJn9LZmlH90FYHeS4t6P8eOJCeekHL0jUG4Iae\r\n" +"DMP12h8Sd0yxIKmmZ+Q/p/D/BkuHf5Idv3hgyLkZ4mNznjK49wHaYM+BgBoL3Zeg\r\n" +"gJ2sWjUlokrbHswSBLLbUJIF\r\n" +"-----END CERTIFICATE-----\r\n"; + +const char *server_key = +"-----BEGIN PRIVATE KEY-----\r\n" +"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCujwJbVfTFoaYa\r\n" +"QNozEptdYDkvD6x4DrW2KgatIhDO6oth4mgWfBWAXSMHk9OP7cSIYwFwD+aHvdUC\r\n" +"2FMWYkHGP5qAinv9y9QGmB8sKWZJgvZ9mLoWh3P5/2ZyvLddHDs7q+Vqa1Z9NfxM\r\n" +"CYxJHo9dUEcuCOzmkM4UWas0CwzmwthTX/UwW8TZn0idMjkbjfsgTVZyFFJulh0d\r\n" +"yELf1fOkMagidkHVrc6Fweb7FS0ukwnQbSgTlUUTKdmt5mgQhjgnGylDss+xrL1/\r\n" +"HdOWTT2AQxCBUJ4I2JDBTFl20tq7bHrUs1x1kIo3sVsUsmzO/VTa6C4QuxOaLsTq\r\n" +"ketPOKixAgMBAAECggEAI+uNwpnHirue4Jwjyoqzqd1ZJxQEm5f7UIcJZKsz5kBh\r\n" +"ej0KykWybv27bZ2/1UhKPv6QlyzOdXRc1v8I6fxCKLeB5Z2Zsjo1YT4AfCfwwoPO\r\n" +"kT3SXTx2YyVpQYcP/HsIvVi8FtALtixbxJHaall9iugwHYr8pN17arihAE6d0wZC\r\n" +"JXtXRjUWwjKzXP8FoH4KhyadhHbDwIbbJe3cyLfdvp54Gr0YHha0JcOxYgDYNya4\r\n" +"OKxlCluI+hPF31iNzOmFLQVrdYynyPcR6vY5XOiANKE2iNbqCzRb54CvW9WMqObX\r\n" +"RD9t3DMOxGsbVNIwyzZndWy13HoQMGnrHfnGak9ueQKBgQDiVtOqYfLnUnTxvJ/b\r\n" +"qlQZr2ZmsYPZztxlP+DSqZGPD+WtGSo9+rozWfzjTv3KGIDLvf+GFVmjVHwlLQfd\r\n" +"u7eTemWHFc4HK68wruzPO/FdyVpQ4w9v3Usg+ll4a/PDEId0fDMjAr6kk4LC6t8y\r\n" +"9fJR0HjOz57jVnlrDt3v50G8BwKBgQDFbw+jRiUxXnBbDyXZLi+I4iGBGdC+CbaJ\r\n" +"CmsM6/TsOFc+GRsPwQF1gCGqdaURw76noIVKZJOSc8I+yiwU6izyh/xaju5JiWQd\r\n" +"kwbU1j4DE6GnxmT3ARmB7VvCxjaEZEAtICWs1QTKRz7PcTV8yr7Ng1A3VIy+NSpo\r\n" +"LFMMmk83hwKBgQDVCEwpLg/mUeHoNVVw95w4oLKNLb+gHeerFLiTDy8FrDzM88ai\r\n" +"l37yHly7xflxYia3nZkHpsi7xiUjCINC3BApKyasQoWskh1OgRY653yCfaYYQ96f\r\n" +"t3WjEH9trI2+p6wWo1+uMEMnu/9zXoW9/WeaQdGzNg+igh29+jxCNTPVuQKBgGV4\r\n" +"CN9vI5pV4QTLqjYOSJvfLDz/mYqxz0BrPE1tz3jAFAZ0PLZCCY/sBGFpCScyJQBd\r\n" +"vWNYgYeZOtGuci1llSgov4eDQfBFTlDsyWwFl+VY55IkoqtXw1ZFOQ3HdSlhpKIM\r\n" +"jZBgApA7QYq3sjeqs5lHzahCKftvs5XKgfxOKjxtAoGBALdnYe6xkDvGLvI51Yr+\r\n" +"Dy0TNcB5W84SxUKvM7DVEomy1QPB57ZpyQaoBq7adOz0pWJXfp7qo4950ZOhBGH1\r\n" +"hKbZ6c4ggwVJy2j49EgMok5NGCKvPAtabbR6H8Mz8DW9aXURxhWJvij+Qw1fWK4b\r\n" +"7G/qUI9iE5iUU7MkIcLIbTf/\r\n" +"-----END PRIVATE KEY-----\r\n"; + +const char *index_page = +"\n" +"\n" +"\n" +" \n" +" WebSocket Test\n" +" \n" +" \n" +"\n" +"\n" +"\n" +"\n" +"
\n" +"

Websocket PubSub Demonstration

\n" +"\n" +"

\n" +" This page demonstrates how Mongoose could be used to implement\n" +" \n" +" publish–subscribe pattern. Open this page in several browser\n" +" windows. Each window initiates persistent\n" +" WebSocket\n" +" connection with the server, making each browser window a websocket client.\n" +" Send messages, and see messages sent by other clients.\n" +"

\n" +"\n" +"
\n" +"
\n" +"\n" +"

\n" +" \n" +" \n" +"

\n" +"
\n" +"\n" +"\n"; + +#include + +void broadcast(MongooseHttpWebSocketConnection *from, MongooseString msg) +{ + char buf[500]; + char addr[32]; + mg_sock_addr_to_str(from->getRemoteAddress(), addr, sizeof(addr), + MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT); + + snprintf(buf, sizeof(buf), "%s %.*s", addr, (int) msg.length(), msg.c_str()); + printf("%s\n", buf); + server.sendAll(from, buf); +} + +void setup() +{ + Serial.begin(115200); + +#ifdef START_ESP_WIFI + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) + { + Serial.printf("WiFi Failed!\n"); + return; + } + + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + Serial.print("Hostname: "); +#ifdef ESP32 + Serial.println(WiFi.getHostname()); +#elif defined(ESP8266) + Serial.println(WiFi.hostname()); +#endif +#endif + + Mongoose.begin(); + +#ifdef SIMPLE_SERVER_SECURE + if(false == server.begin(443, server_pem, server_key)) { + Serial.print("Failed to start server"); + return; + } +#else + server.begin(80); +#endif + + server.on("/$", HTTP_GET, [](MongooseHttpServerRequest *request) { + request->send(200, "text/html", index_page); + }); + + // Test the stream response class + server.on("/ws$")-> + onConnect([](MongooseHttpWebSocketConnection *connection) { + broadcast(connection, MongooseString("++ joined")); + })-> + onClose([](MongooseHttpServerRequest *c) { + MongooseHttpWebSocketConnection *connection = static_cast(c); + broadcast(connection, MongooseString("++ left")); + })-> + onFrame([](MongooseHttpWebSocketConnection *connection, int flags, uint8_t *data, size_t len) { + broadcast(connection, MongooseString((const char *)data, len)); + }); +} + +void loop() +{ + Mongoose.poll(1000); + Serial.printf("Free memory %u\n", ESP.getFreeHeap()); +} diff --git a/lib/PsychicHttp/examples/old/websocket_chat/test/README b/lib/PsychicHttp/examples/old/websocket_chat/test/README new file mode 100644 index 0000000..df5066e --- /dev/null +++ b/lib/PsychicHttp/examples/old/websocket_chat/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/lib/PsychicHttp/examples/old/websocket_chat/test/tests.rest b/lib/PsychicHttp/examples/old/websocket_chat/test/tests.rest new file mode 100644 index 0000000..4072428 --- /dev/null +++ b/lib/PsychicHttp/examples/old/websocket_chat/test/tests.rest @@ -0,0 +1,35 @@ +# Name: REST Client +# Id: humao.rest-client +# Description: REST Client for Visual Studio Code +# Version: 0.21.3 +# Publisher: Huachao Mao +# VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=humao.rest-client + +@baseUrl = http://172.16.0.87 + +### + +GET {{baseUrl}}/ HTTP/1.1 + +### + +GET {{baseUrl}}/get?message=Hello+World HTTP/1.1 + +### + +POST {{baseUrl}}/post HTTP/1.1 +Content-Type: application/x-www-form-urlencoded;charset=UTF-8 + +message=Hello+World + +### + +GET {{baseUrl}}/someRandomFile HTTP/1.1 + +### + +GET {{baseUrl}}/basic HTTP/1.1 + +### + +GET {{baseUrl}}/stream HTTP/1.1 diff --git a/lib/PsychicHttp/examples/platformio/.gitignore b/lib/PsychicHttp/examples/platformio/.gitignore new file mode 100644 index 0000000..9e5f911 --- /dev/null +++ b/lib/PsychicHttp/examples/platformio/.gitignore @@ -0,0 +1,6 @@ +.pio +.vscode/ +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/lib/PsychicHttp/examples/platformio/data/custom.txt b/lib/PsychicHttp/examples/platformio/data/custom.txt new file mode 100644 index 0000000..d3db23d --- /dev/null +++ b/lib/PsychicHttp/examples/platformio/data/custom.txt @@ -0,0 +1 @@ +Custom text file. \ No newline at end of file diff --git a/lib/PsychicHttp/examples/platformio/data/img/request_flow.png b/lib/PsychicHttp/examples/platformio/data/img/request_flow.png new file mode 100644 index 0000000..1005a38 Binary files /dev/null and b/lib/PsychicHttp/examples/platformio/data/img/request_flow.png differ diff --git a/lib/PsychicHttp/examples/platformio/data/server.crt b/lib/PsychicHttp/examples/platformio/data/server.crt new file mode 100644 index 0000000..34a1e01 --- /dev/null +++ b/lib/PsychicHttp/examples/platformio/data/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL +BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMTgx +MDE3MTEzMjU3WhcNMjgxMDE0MTEzMjU3WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ +UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T +sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k +qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd +GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4 +sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb +jAn4PCuR2akdF4G8WLUeDWECAwEAAaNTMFEwHQYDVR0OBBYEFMnmdJKOEepXrHI/ +ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud +EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADiXIGEkSsN0SLSfCF1VNWO3 +emBurfOcDq4EGEaxRKAU0814VEmU87btIDx80+z5Dbf+GGHCPrY7odIkxGNn0DJY +W1WcF+DOcbiWoUN6DTkAML0SMnp8aGj9ffx3x+qoggT+vGdWVVA4pgwqZT7Ybntx +bkzcNFW0sqmCv4IN1t4w6L0A87ZwsNwVpre/j6uyBw7s8YoJHDLRFT6g7qgn0tcN +ZufhNISvgWCVJQy/SZjNBHSpnIdCUSJAeTY2mkM4sGxY0Widk8LnjydxZUSxC3Nl +hb6pnMh3jRq4h0+5CZielA4/a+TdrNPv/qok67ot/XJdY3qHCCd8O2b14OVq9jo= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/lib/PsychicHttp/examples/platformio/data/server.key b/lib/PsychicHttp/examples/platformio/data/server.key new file mode 100644 index 0000000..a591325 --- /dev/null +++ b/lib/PsychicHttp/examples/platformio/data/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH +JioMD7U7BitLgpcYPi8Cid1l7snt6Kp546iQsDBJ3l8xnRtPU7ANEsjT8KxIHmyw +h/NGp94FlOKRw3ahh3yUGtowS9vdHv+S+TAfuj07NjSnKIyv5KnGZJ+fDFl4Q1tT +aQJybY1Z4itirL6/2CGEm8g/iYhLNDBsRMfpDpfXe4URyWiM3Rhf7ztqZdveb9al +3pAJZIDTLWCFQI1MvQjKamkAQkES/gZj0iUZFwbGJPBj54nkuLFLKedw7DbwgrVg +0+n3fQ9b/gQepw5PxQjyobY2DsDgGZV+MFjUmaUTa+XX68SrG4wJ+DwrkdmpHReB +vFi1Hg1hAgMBAAECggEAaTCnZkl/7qBjLexIryC/CBBJyaJ70W1kQ7NMYfniWwui +f0aRxJgOdD81rjTvkINsPp+xPRQO6oOadjzdjImYEuQTqrJTEUnntbu924eh+2D9 +Mf2CAanj0mglRnscS9mmljZ0KzoGMX6Z/EhnuS40WiJTlWlH6MlQU/FDnwC6U34y +JKy6/jGryfsx+kGU/NRvKSru6JYJWt5v7sOrymHWD62IT59h3blOiP8GMtYKeQlX +49om9Mo1VTIFASY3lrxmexbY+6FG8YO+tfIe0tTAiGrkb9Pz6tYbaj9FjEWOv4Vc ++3VMBUVdGJjgqvE8fx+/+mHo4Rg69BUPfPSrpEg7sQKBgQDlL85G04VZgrNZgOx6 +pTlCCl/NkfNb1OYa0BELqWINoWaWQHnm6lX8YjrUjwRpBF5s7mFhguFjUjp/NW6D +0EEg5BmO0ePJ3dLKSeOA7gMo7y7kAcD/YGToqAaGljkBI+IAWK5Su5yldrECTQKG +YnMKyQ1MWUfCYEwHtPvFvE5aPwKBgQDFBWXekpxHIvt/B41Cl/TftAzE7/f58JjV +MFo/JCh9TDcH6N5TMTRS1/iQrv5M6kJSSrHnq8pqDXOwfHLwxetpk9tr937VRzoL +CuG1Ar7c1AO6ujNnAEmUVC2DppL/ck5mRPWK/kgLwZSaNcZf8sydRgphsW1ogJin +7g0nGbFwXwKBgQCPoZY07Pr1TeP4g8OwWTu5F6dSvdU2CAbtZthH5q98u1n/cAj1 +noak1Srpa3foGMTUn9CHu+5kwHPIpUPNeAZZBpq91uxa5pnkDMp3UrLIRJ2uZyr8 +4PxcknEEh8DR5hsM/IbDcrCJQglM19ZtQeW3LKkY4BsIxjDf45ymH407IQKBgE/g +Ul6cPfOxQRlNLH4VMVgInSyyxWx1mODFy7DRrgCuh5kTVh+QUVBM8x9lcwAn8V9/ +nQT55wR8E603pznqY/jX0xvAqZE6YVPcw4kpZcwNwL1RhEl8GliikBlRzUL3SsW3 +q30AfqEViHPE3XpE66PPo6Hb1ymJCVr77iUuC3wtAoGBAIBrOGunv1qZMfqmwAY2 +lxlzRgxgSiaev0lTNxDzZkmU/u3dgdTwJ5DDANqPwJc6b8SGYTp9rQ0mbgVHnhIB +jcJQBQkTfq6Z0H6OoTVi7dPs3ibQJFrtkoyvYAbyk36quBmNRjVh6rc8468bhXYr +v/t+MeGJP/0Zw8v/X2CFll96 +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/lib/PsychicHttp/examples/platformio/data/www-ap/index.html b/lib/PsychicHttp/examples/platformio/data/www-ap/index.html new file mode 100644 index 0000000..73df596 --- /dev/null +++ b/lib/PsychicHttp/examples/platformio/data/www-ap/index.html @@ -0,0 +1,15 @@ + + + + + + PsychicHTTP SoftAP Demo + + + +
+

SoftAP Demo

+

You are connected to the ESP in SoftAP mode.

+
+ + \ No newline at end of file diff --git a/lib/PsychicHttp/examples/platformio/data/www/alien.png b/lib/PsychicHttp/examples/platformio/data/www/alien.png new file mode 100644 index 0000000..a030da0 Binary files /dev/null and b/lib/PsychicHttp/examples/platformio/data/www/alien.png differ diff --git a/lib/PsychicHttp/examples/platformio/data/www/favicon.ico b/lib/PsychicHttp/examples/platformio/data/www/favicon.ico new file mode 100644 index 0000000..bdf785c Binary files /dev/null and b/lib/PsychicHttp/examples/platformio/data/www/favicon.ico differ diff --git a/lib/PsychicHttp/examples/platformio/data/www/index.html b/lib/PsychicHttp/examples/platformio/data/www/index.html new file mode 100644 index 0000000..76f14a9 --- /dev/null +++ b/lib/PsychicHttp/examples/platformio/data/www/index.html @@ -0,0 +1,236 @@ + + + + + + PsychicHTTP Demo + + + +
+

Basic Request Examples

+ + +

Static Serving

+

+ + +

+

Text File

+ +

Simple POST Form

+
+ + +
+ + +
+ +
+ +

Basic File Upload

+ + + + + + + + + + + + +
+ + + +
+ + + +
+ +
+ + +

Multipart POST Form

+
+ + +
+ + + +
+ + + +
+ + +
+ +

Websocket Demo

+ + + +
+ +
+ + + +

EventSource Demo

+ +
+ +
+ + +
+ + \ No newline at end of file diff --git a/lib/PsychicHttp/examples/platformio/data/www/text.txt b/lib/PsychicHttp/examples/platformio/data/www/text.txt new file mode 100644 index 0000000..5375816 --- /dev/null +++ b/lib/PsychicHttp/examples/platformio/data/www/text.txt @@ -0,0 +1 @@ +Test File. diff --git a/lib/PsychicHttp/examples/platformio/include/README b/lib/PsychicHttp/examples/platformio/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/lib/PsychicHttp/examples/platformio/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/PsychicHttp/examples/platformio/lib/README b/lib/PsychicHttp/examples/platformio/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/PsychicHttp/examples/platformio/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/PsychicHttp/examples/platformio/platformio.ini b/lib/PsychicHttp/examples/platformio/platformio.ini new file mode 100644 index 0000000..77a33bc --- /dev/null +++ b/lib/PsychicHttp/examples/platformio/platformio.ini @@ -0,0 +1,30 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env] +platform = espressif32 +framework = arduino +board = esp32-s3-devkitc-1 +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder +lib_deps = + ; devmode: with this disabled make a symlink from platformio/lib to the PsychicHttp directory + ;hoeken/PsychicHttp + bblanchon/ArduinoJson +board_build.filesystem = littlefs + +[env:default] +build_flags = + -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_WARN + ;-D ENABLE_ASYNC + +; [env:arduino3] +; platform = https://github.com/platformio/platform-espressif32.git +; platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32#master \ No newline at end of file diff --git a/lib/PsychicHttp/examples/platformio/src/main.cpp b/lib/PsychicHttp/examples/platformio/src/main.cpp new file mode 100644 index 0000000..eb52479 --- /dev/null +++ b/lib/PsychicHttp/examples/platformio/src/main.cpp @@ -0,0 +1,583 @@ +/* + PsychicHTTP Server Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/********************************************************************************************** +* Note: this demo relies on various files to be uploaded on the LittleFS partition +* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image +**********************************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "_secret.h" +#include +//#include //uncomment this to enable HTTPS / SSL + +#ifndef WIFI_SSID + #error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there." +#endif + +//Enter your WIFI credentials in secret.h +const char *ssid = WIFI_SSID; +const char *password = WIFI_PASS; + +// Set your SoftAP credentials +const char *softap_ssid = "PsychicHttp"; +const char *softap_password = ""; +IPAddress softap_ip(10, 0, 0, 1); + +//credentials for the /auth-basic and /auth-digest examples +const char *app_user = "admin"; +const char *app_pass = "admin"; +const char *app_name = "Your App"; + +//hostname for mdns (psychic.local) +const char *local_hostname = "psychic"; + +//#define PSY_ENABLE_SSL to enable ssl +#ifdef PSY_ENABLE_SSL + bool app_enable_ssl = true; + String server_cert; + String server_key; +#endif + +//our main server object +#ifdef PSY_ENABLE_SSL + PsychicHttpsServer server; +#else + PsychicHttpServer server; +#endif +PsychicWebSocketHandler websocketHandler; +PsychicEventSource eventSource; + +//NTP server stuff +const char *ntpServer1 = "pool.ntp.org"; +const char *ntpServer2 = "time.nist.gov"; +const long gmtOffset_sec = 0; +const int daylightOffset_sec = 0; +struct tm timeinfo; + +// Callback function (gets called when time adjusts via NTP) +void timeAvailable(struct timeval *t) +{ + if (!getLocalTime(&timeinfo)) { + Serial.println("Failed to obtain time"); + return; + } + + Serial.print("NTP update: "); + char buffer[40]; + strftime(buffer, 40, "%FT%T%z", &timeinfo); + Serial.println(buffer); +} + +bool connectToWifi() +{ + //dual client and AP mode + WiFi.mode(WIFI_AP_STA); + + // Configure SoftAP + WiFi.softAPConfig(softap_ip, softap_ip, IPAddress(255, 255, 255, 0)); // subnet FF FF FF 00 + WiFi.softAP(softap_ssid, softap_password); + IPAddress myIP = WiFi.softAPIP(); + Serial.print("SoftAP IP Address: "); + Serial.println(myIP); + + Serial.println(); + Serial.print("[WiFi] Connecting to "); + Serial.println(ssid); + + WiFi.begin(ssid, password); + // Auto reconnect is set true as default + // To set auto connect off, use the following function + // WiFi.setAutoReconnect(false); + + // Will try for about 10 seconds (20x 500ms) + int tryDelay = 500; + int numberOfTries = 20; + + // Wait for the WiFi event + while (true) + { + switch (WiFi.status()) + { + case WL_NO_SSID_AVAIL: + Serial.println("[WiFi] SSID not found"); + break; + case WL_CONNECT_FAILED: + Serial.print("[WiFi] Failed - WiFi not connected! Reason: "); + return false; + break; + case WL_CONNECTION_LOST: + Serial.println("[WiFi] Connection was lost"); + break; + case WL_SCAN_COMPLETED: + Serial.println("[WiFi] Scan is completed"); + break; + case WL_DISCONNECTED: + Serial.println("[WiFi] WiFi is disconnected"); + break; + case WL_CONNECTED: + Serial.println("[WiFi] WiFi is connected!"); + Serial.print("[WiFi] IP address: "); + Serial.println(WiFi.localIP()); + return true; + break; + default: + Serial.print("[WiFi] WiFi Status: "); + Serial.println(WiFi.status()); + break; + } + delay(tryDelay); + + if (numberOfTries <= 0) + { + Serial.print("[WiFi] Failed to connect to WiFi!"); + // Use disconnect function to force stop trying to connect + WiFi.disconnect(); + return false; + } + else + { + numberOfTries--; + } + } + + return false; +} + +void setup() +{ + Serial.begin(115200); + delay(10); + + // We start by connecting to a WiFi network + // To debug, please enable Core Debug Level to Verbose + if (connectToWifi()) + { + //Setup our NTP to get the current time. + sntp_set_time_sync_notification_cb(timeAvailable); + sntp_servermode_dhcp(1); // (optional) + configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1, ntpServer2); + + //set up our esp32 to listen on the local_hostname.local domain + if (!MDNS.begin(local_hostname)) { + Serial.println("Error starting mDNS"); + return; + } + MDNS.addService("http", "tcp", 80); + + if(!LittleFS.begin()) + { + Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode"); + return; + } + + //look up our keys? + #ifdef PSY_ENABLE_SSL + if (app_enable_ssl) + { + File fp = LittleFS.open("/server.crt"); + if (fp) + { + server_cert = fp.readString(); + + // Serial.println("Server Cert:"); + // Serial.println(server_cert); + } + else + { + Serial.println("server.pem not found, SSL not available"); + app_enable_ssl = false; + } + fp.close(); + + File fp2 = LittleFS.open("/server.key"); + if (fp2) + { + server_key = fp2.readString(); + + // Serial.println("Server Key:"); + // Serial.println(server_key); + } + else + { + Serial.println("server.key not found, SSL not available"); + app_enable_ssl = false; + } + fp2.close(); + } + #endif + + //setup server config stuff here + server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls) + + #ifdef PSY_ENABLE_SSL + server.ssl_config.httpd.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls) + + //do we want secure or not? + if (app_enable_ssl) + { + server.listen(443, server_cert.c_str(), server_key.c_str()); + + //this creates a 2nd server listening on port 80 and redirects all requests HTTPS + PsychicHttpServer *redirectServer = new PsychicHttpServer(); + redirectServer->config.ctrl_port = 20424; // just a random port different from the default one + redirectServer->listen(80); + redirectServer->onNotFound([](PsychicRequest *request) { + String url = "https://" + request->host() + request->url(); + return request->redirect(url.c_str()); + }); + } + else + server.listen(80); + #else + server.listen(80); + #endif + + DefaultHeaders::Instance().addHeader("Server", "PsychicHttp"); + + //serve static files from LittleFS/www on / only to clients on same wifi network + //this is where our /index.html file lives + // curl -i http://psychic.local/ + PsychicStaticFileHandler* handler = server.serveStatic("/", LittleFS, "/www/"); + handler->setFilter(ON_STA_FILTER); + handler->setCacheControl("max-age=60"); + + //serve static files from LittleFS/www-ap on / only to clients on SoftAP + //this is where our /index.html file lives + server.serveStatic("/", LittleFS, "/www-ap/")->setFilter(ON_AP_FILTER); + + //serve static files from LittleFS/img on /img + //it's more efficient to serve everything from a single www directory, but this is also possible. + // curl -i http://psychic.local/img/request_flow.png + server.serveStatic("/img", LittleFS, "/img/"); + + //you can also serve single files + // curl -i http://psychic.local/myfile.txt + server.serveStatic("/myfile.txt", LittleFS, "/custom.txt"); + + //example callback everytime a connection is opened + server.onOpen([](PsychicClient *client) { + Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); + }); + + //example callback everytime a connection is closed + server.onClose([](PsychicClient *client) { + Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); + }); + + //api - json message passed in as post body + // curl -i -X POST -H "Content-Type: application/json" -d '{"foo":"bar"}' http://psychic.local/api + server.on("/api", HTTP_POST, [](PsychicRequest *request, JsonVariant &json) + { + JsonObject input = json.as(); + + //create our response json + PsychicJsonResponse response = PsychicJsonResponse(request); + JsonObject output = response.getRoot(); + + output["msg"] = "status"; + output["status"] = "success"; + output["millis"] = millis(); + + //work with some params + if (input.containsKey("foo")) + { + String foo = input["foo"]; + output["foo"] = foo; + } + + return response.send(); + }); + + //ip - get info about the client + // curl -i http://psychic.local/ip + server.on("/ip", HTTP_GET, [](PsychicRequest *request) + { + String output = "Your IP is: " + request->client()->remoteIP().toString(); + return request->reply(output.c_str()); + }); + + //client connect/disconnect to a url + // curl -i http://psychic.local/handler + PsychicWebHandler *connectionHandler = new PsychicWebHandler(); + connectionHandler->onRequest([](PsychicRequest *request) + { + return request->reply("OK"); + }); + connectionHandler->onOpen([](PsychicClient *client) { + Serial.printf("[handler] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); + }); + connectionHandler->onClose([](PsychicClient *client) { + Serial.printf("[handler] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); + }); + + //add it to our server + server.on("/handler", connectionHandler); + + //api - parameters passed in via query eg. /api?foo=bar + // curl -i 'http://psychic.local/api?foo=bar' + server.on("/api", HTTP_GET, [](PsychicRequest *request) + { + //showcase some of the variables + Serial.println(request->host()); + Serial.println(request->uri()); + Serial.println(request->path()); + Serial.println(request->queryString()); + + //create a response object + //create our response json + PsychicJsonResponse response = PsychicJsonResponse(request); + JsonObject output = response.getRoot(); + + output["msg"] = "status"; + output["status"] = "success"; + output["millis"] = millis(); + + //work with some params + if (request->hasParam("foo")) + { + String foo = request->getParam("foo")->value(); + output["foo"] = foo; + } + + return response.send(); + }); + + //JsonResponse example + // curl -i http://psychic.local/json + server.on("/json", HTTP_GET, [](PsychicRequest *request) + { + PsychicJsonResponse response = PsychicJsonResponse(request); + + char key[16]; + char value[32]; + JsonObject root = response.getRoot(); + for (int i=0; i<100; i++) + { + sprintf(key, "key%d", i); + sprintf(value, "value is %d", i); + root[key] = value; + } + + return response.send(); + }); + + //how to redirect a request + // curl -i http://psychic.local/redirect + server.on("/redirect", HTTP_GET, [](PsychicRequest *request) + { + return request->redirect("/alien.png"); + }); + + //how to do basic auth + // curl -i --user admin:admin http://psychic.local/auth-basic + server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request) + { + if (!request->authenticate(app_user, app_pass)) + return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in."); + return request->reply("Auth Basic Success!"); + }); + + //how to do digest auth + // curl -i --user admin:admin http://psychic.local/auth-digest + server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request) + { + if (!request->authenticate(app_user, app_pass)) + return request->requestAuthentication(DIGEST_AUTH, app_name, "You must log in."); + return request->reply("Auth Digest Success!"); + }); + + //example of getting / setting cookies + // curl -i -b cookie.txt -c cookie.txt http://psychic.local/cookies + server.on("/cookies", HTTP_GET, [](PsychicRequest *request) + { + PsychicResponse response(request); + + int counter = 0; + if (request->hasCookie("counter")) + { + counter = std::stoi(request->getCookie("counter").c_str()); + counter++; + } + + char cookie[10]; + sprintf(cookie, "%i", counter); + + response.setCookie("counter", cookie); + response.setContent(cookie); + return response.send(); + }); + + //example of getting POST variables + // curl -i -d "param1=value1¶m2=value2" -X POST http://psychic.local/post + server.on("/post", HTTP_POST, [](PsychicRequest *request) + { + String output; + output += "Param 1: " + request->getParam("param1")->value() + "
\n"; + output += "Param 2: " + request->getParam("param2")->value() + "
\n"; + + return request->reply(output.c_str()); + }); + + //you can set up a custom 404 handler. + // curl -i http://psychic.local/404 + server.onNotFound([](PsychicRequest *request) + { + return request->reply(404, "text/html", "Custom 404 Handler"); + }); + + //handle a very basic upload as post body + PsychicUploadHandler *uploadHandler = new PsychicUploadHandler(); + uploadHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { + File file; + String path = "/www/" + filename; + + Serial.printf("Writing %d/%d bytes to: %s\n", (int)index+(int)len, request->contentLength(), path.c_str()); + + if (last) + Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len); + + //our first call? + if (!index) + file = LittleFS.open(path, FILE_WRITE); + else + file = LittleFS.open(path, FILE_APPEND); + + if(!file) { + Serial.println("Failed to open file"); + return ESP_FAIL; + } + + if(!file.write(data, len)) { + Serial.println("Write failed"); + return ESP_FAIL; + } + + return ESP_OK; + }); + + //gets called after upload has been handled + uploadHandler->onRequest([](PsychicRequest *request) + { + String url = "/" + request->getFilename(); + String output = "" + url + ""; + + return request->reply(output.c_str()); + }); + + //wildcard basic file upload - POST to /upload/filename.ext + // use http://psychic.local/ to test + server.on("/upload/*", HTTP_POST, uploadHandler); + + //a little bit more complicated multipart form + PsychicUploadHandler *multipartHandler = new PsychicUploadHandler(); + multipartHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { + File file; + String path = "/www/" + filename; + + //some progress over serial. + Serial.printf("Writing %d bytes to: %s\n", (int)len, path.c_str()); + if (last) + Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len); + + //our first call? + if (!index) + file = LittleFS.open(path, FILE_WRITE); + else + file = LittleFS.open(path, FILE_APPEND); + + if(!file) { + Serial.println("Failed to open file"); + return ESP_FAIL; + } + + if(!file.write(data, len)) { + Serial.println("Write failed"); + return ESP_FAIL; + } + + return ESP_OK; + }); + + //gets called after upload has been handled + multipartHandler->onRequest([](PsychicRequest *request) + { + if (request->hasParam("file_upload")) + { + PsychicWebParameter *file = request->getParam("file_upload"); + + String url = "/" + file->value(); + String output; + + output += "" + url + "
\n"; + output += "Bytes: " + String(file->size()) + "
\n"; + output += "Param 1: " + request->getParam("param1")->value() + "
\n"; + output += "Param 2: " + request->getParam("param2")->value() + "
\n"; + + return request->reply(output.c_str()); + } + else + return request->reply("No upload."); + }); + + //wildcard basic file upload - POST to /upload/filename.ext + // use http://psychic.local/ to test + server.on("/multipart", HTTP_POST, multipartHandler); + + //a websocket echo server + // npm install -g wscat + // wscat -c ws://psychic.local/ws + websocketHandler.onOpen([](PsychicWebSocketClient *client) { + Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); + client->sendMessage("Hello!"); + }); + websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) { + Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload); + return request->reply(frame); + }); + websocketHandler.onClose([](PsychicWebSocketClient *client) { + Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); + }); + server.on("/ws", &websocketHandler); + + //EventSource server + // curl -i -N http://psychic.local/events + eventSource.onOpen([](PsychicEventSourceClient *client) { + Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); + client->send("Hello user!", NULL, millis(), 1000); + }); + eventSource.onClose([](PsychicEventSourceClient *client) { + Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); + }); + server.on("/events", &eventSource); + } +} + +unsigned long lastUpdate = 0; +char output[60]; + +void loop() +{ + if (millis() - lastUpdate > 2000) + { + sprintf(output, "Millis: %d\n", millis()); + websocketHandler.sendAll(output); + + sprintf(output, "%d", millis()); + eventSource.send(output, "millis", millis(), 0); + + lastUpdate = millis(); + } +} \ No newline at end of file diff --git a/lib/PsychicHttp/examples/platformio/src/secret.h b/lib/PsychicHttp/examples/platformio/src/secret.h new file mode 100644 index 0000000..6d4bb15 --- /dev/null +++ b/lib/PsychicHttp/examples/platformio/src/secret.h @@ -0,0 +1,2 @@ +#define WIFI_SSID "Your_SSID" +#define WIFI_PASS "Your_PASS" \ No newline at end of file diff --git a/lib/PsychicHttp/examples/platformio/test/README b/lib/PsychicHttp/examples/platformio/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/lib/PsychicHttp/examples/platformio/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/lib/PsychicHttp/examples/websockets/.gitignore b/lib/PsychicHttp/examples/websockets/.gitignore new file mode 100644 index 0000000..e37ccaa --- /dev/null +++ b/lib/PsychicHttp/examples/websockets/.gitignore @@ -0,0 +1,8 @@ +.pio +.vscode/ +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch +src/_secret.h +lib/PsychicHttp/ \ No newline at end of file diff --git a/lib/PsychicHttp/examples/websockets/data/www/favicon.ico b/lib/PsychicHttp/examples/websockets/data/www/favicon.ico new file mode 100644 index 0000000..bdf785c Binary files /dev/null and b/lib/PsychicHttp/examples/websockets/data/www/favicon.ico differ diff --git a/lib/PsychicHttp/examples/websockets/data/www/index.html b/lib/PsychicHttp/examples/websockets/data/www/index.html new file mode 100644 index 0000000..c4ba4d9 --- /dev/null +++ b/lib/PsychicHttp/examples/websockets/data/www/index.html @@ -0,0 +1,76 @@ + + + + + + PsychicHTTP Websocket Demo + + + +
+

Websocket Demo

+ + + +
+ +
+ + +
+ + \ No newline at end of file diff --git a/lib/PsychicHttp/examples/websockets/include/README b/lib/PsychicHttp/examples/websockets/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/lib/PsychicHttp/examples/websockets/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/PsychicHttp/examples/websockets/lib/README b/lib/PsychicHttp/examples/websockets/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/PsychicHttp/examples/websockets/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/PsychicHttp/examples/websockets/platformio.ini b/lib/PsychicHttp/examples/websockets/platformio.ini new file mode 100644 index 0000000..4849f9e --- /dev/null +++ b/lib/PsychicHttp/examples/websockets/platformio.ini @@ -0,0 +1,25 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env] +platform = espressif32 +framework = arduino +board = esp32dev +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder +lib_deps = + ; devmode: with this disabled make a symlink from platformio/lib to the PsychicHttp directory + ;hoeken/PsychicHttp + bblanchon/ArduinoJson +board_build.filesystem = littlefs + +[env:default] +build_flags = + -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_WARN \ No newline at end of file diff --git a/lib/PsychicHttp/examples/websockets/src/main.cpp b/lib/PsychicHttp/examples/websockets/src/main.cpp new file mode 100644 index 0000000..a7c79b4 --- /dev/null +++ b/lib/PsychicHttp/examples/websockets/src/main.cpp @@ -0,0 +1,225 @@ +/* + PsychicHTTP Server Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/********************************************************************************************** +* Note: this demo relies on various files to be uploaded on the LittleFS partition +* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image +**********************************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "_secret.h" +#include +#include + +#ifndef WIFI_SSID + #error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there." +#endif + +//Enter your WIFI credentials in secret.h +const char *ssid = WIFI_SSID; +const char *password = WIFI_PASS; + +//hostname for mdns (psychic.local) +const char *local_hostname = "psychic"; + +PsychicHttpServer server; +PsychicWebSocketHandler websocketHandler; + +typedef struct { + int socket; + char *buffer; + size_t len; +} WebsocketMessage; + +QueueHandle_t wsMessages; + +bool connectToWifi() +{ + Serial.print("[WiFi] Connecting to "); + Serial.println(ssid); + + //setup our wifi + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + + // Will try for about 10 seconds (20x 500ms) + int tryDelay = 500; + int numberOfTries = 20; + + // Wait for the WiFi event + while (true) + { + switch (WiFi.status()) + { + case WL_NO_SSID_AVAIL: + Serial.println("[WiFi] SSID not found"); + break; + case WL_CONNECT_FAILED: + Serial.print("[WiFi] Failed - WiFi not connected! Reason: "); + return false; + break; + case WL_CONNECTION_LOST: + Serial.println("[WiFi] Connection was lost"); + break; + case WL_SCAN_COMPLETED: + Serial.println("[WiFi] Scan is completed"); + break; + case WL_DISCONNECTED: + Serial.println("[WiFi] WiFi is disconnected"); + break; + case WL_CONNECTED: + Serial.println("[WiFi] WiFi is connected!"); + Serial.print("[WiFi] IP address: "); + Serial.println(WiFi.localIP()); + return true; + break; + default: + Serial.print("[WiFi] WiFi Status: "); + Serial.println(WiFi.status()); + break; + } + delay(tryDelay); + + if (numberOfTries <= 0) + { + Serial.print("[WiFi] Failed to connect to WiFi!"); + // Use disconnect function to force stop trying to connect + WiFi.disconnect(); + return false; + } + else + { + numberOfTries--; + } + } + + return false; +} + +void setup() +{ + Serial.begin(115200); + delay(10); + + //prepare our message queue of 10 messages + wsMessages = xQueueCreate(10, sizeof(WebsocketMessage)); + if (wsMessages == 0) + Serial.printf("Failed to create queue= %p\n", wsMessages); + + // We start by connecting to a WiFi network + // To debug, please enable Core Debug Level to Verbose + if (connectToWifi()) + { + //set up our esp32 to listen on the local_hostname.local domain + if (!MDNS.begin(local_hostname)) { + Serial.println("Error starting mDNS"); + return; + } + MDNS.addService("http", "tcp", 80); + + if(!LittleFS.begin()) + { + Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode"); + return; + } + + server.listen(80); + + //this is where our /index.html file lives + // curl -i http://psychic.local/ + PsychicStaticFileHandler* handler = server.serveStatic("/", LittleFS, "/www/"); + + //a websocket echo server + // npm install -g wscat + // wscat -c ws://psychic.local/ws + websocketHandler.onOpen([](PsychicWebSocketClient *client) { + Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); + client->sendMessage("Hello!"); + }); + websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) + { + Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload); + + //we are allocating memory here, and the worker will free it + WebsocketMessage wm; + wm.socket = request->client()->socket(); + wm.len = frame->len; + wm.buffer = (char *)malloc(frame->len); + + //did we flame out? + if (wm.buffer == NULL) + { + Serial.printf("Queue message: unable to allocate %d bytes\n", frame->len); + return ESP_FAIL; + } + + //okay, copy it over + memcpy(wm.buffer, frame->payload, frame->len); + + //try to throw it in our queue + if (xQueueSend(wsMessages, &wm, 1) != pdTRUE) + { + Serial.printf("[socket] queue full #%d\n", wm.socket); + + //free the memory... no worker to do it for us. + free(wm.buffer); + } + + //send a throttle message if we're full + if (!uxQueueSpacesAvailable(wsMessages)) + return request->reply("Queue Full"); + + return ESP_OK; + }); + websocketHandler.onClose([](PsychicWebSocketClient *client) { + Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); + }); + server.on("/ws", &websocketHandler); + } +} + +unsigned long lastUpdate = 0; +char output[60]; + +void loop() +{ + //process our websockets outside the callback. + WebsocketMessage message; + while (xQueueReceive(wsMessages, &message, 0) == pdTRUE) + { + //make sure our client is still good. + PsychicWebSocketClient *client = websocketHandler.getClient(message.socket); + if (client == NULL) { + Serial.printf("[socket] client #%d bad, bailing\n", message.socket); + return; + } + + //echo it back to the client. + //alternatively, this is where you would deserialize a json message, parse it, and generate a response if needed + client->sendMessage(HTTPD_WS_TYPE_TEXT, message.buffer, message.len); + + //make sure to release our memory! + free(message.buffer); + } + + //send a periodic update to all clients + if (millis() - lastUpdate > 2000) + { + sprintf(output, "Millis: %d\n", millis()); + websocketHandler.sendAll(output); + + lastUpdate = millis(); + } +} \ No newline at end of file diff --git a/lib/PsychicHttp/examples/websockets/src/secret.h b/lib/PsychicHttp/examples/websockets/src/secret.h new file mode 100644 index 0000000..6d4bb15 --- /dev/null +++ b/lib/PsychicHttp/examples/websockets/src/secret.h @@ -0,0 +1,2 @@ +#define WIFI_SSID "Your_SSID" +#define WIFI_PASS "Your_PASS" \ No newline at end of file diff --git a/lib/PsychicHttp/examples/websockets/test/README b/lib/PsychicHttp/examples/websockets/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/lib/PsychicHttp/examples/websockets/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html diff --git a/lib/PsychicHttp/library.json b/lib/PsychicHttp/library.json new file mode 100644 index 0000000..e732d31 --- /dev/null +++ b/lib/PsychicHttp/library.json @@ -0,0 +1,53 @@ +{ + "name": "PsychicHttp", + "version": "1.2.1", + "description": "Arduino style wrapper around ESP-IDF HTTP library. HTTP server with SSL + websockets. Works on esp32 and probably esp8266", + "keywords": "network,http,https,tcp,ssl,tls,websocket,espasyncwebserver", + "repository": + { + "type": "git", + "url": "https://github.com/hoeken/PsychicHttp" + }, + "authors": + [ + { + "name": "Zach Hoeken", + "email": "hoeken@gmail.com", + "maintainer": true + } + ], + "license" : "MIT", + "examples": [ + { + "name": "platformio", + "base": "examples/platformio", + "files": [ + "src/main.cpp" + ] + } + ], + "frameworks": "arduino", + "platforms": "espressif32", + "dependencies": [ + { + "owner": "bblanchon", + "name": "ArduinoJson", + "version": "^7.0.4" + }, + { + "owner": "plageoj", + "name" : "UrlEncode", + "version" : "^1.0.1" + } + ], + "export": { + "include": [ + "examples/platformio", + "src", + "library.json", + "library.properties", + "LICENSE", + "README.md" + ] + } +} diff --git a/lib/PsychicHttp/library.properties b/lib/PsychicHttp/library.properties new file mode 100644 index 0000000..885afb5 --- /dev/null +++ b/lib/PsychicHttp/library.properties @@ -0,0 +1,11 @@ +name=PsychicHttp +version=1.2.1 +author=Zach Hoeken +maintainer=Zach Hoeken +sentence=PsychicHttp is a robust webserver that supports http/https + websockets. +paragraph=This library is based on the ESP-IDF HTTP Server library which is asynchronous, does http / https+ssl and supports websockets. +category=Communication +architectures=esp32 +url=https://github.com/hoeken/PsychicHttp +includes=PsychicHttp.h +depends=ArduinoJson,UrlEncode \ No newline at end of file diff --git a/lib/PsychicHttp/request flow.drawio b/lib/PsychicHttp/request flow.drawio new file mode 100644 index 0000000..8915583 --- /dev/null +++ b/lib/PsychicHttp/request flow.drawio @@ -0,0 +1 @@ +7Vxbd5s4EP41Pmf3wTkgAcaPza3Zbq/bbNM+yiAbNhi5ICd2f/0KIxmQFIypsZ1jpw+1hBCS5pv5ZgaJHryaLt4maBZ8ID6OesDwFz143QPANIDD/stqlnnNwIJ5xSQJfd6oqPga/sLiTl47D32cVhpSQiIazqqVHolj7NFKHUoS8lxtNiZR9akzNMFKxVcPRWrtQ+jTIK91waCov8PhJBBPNp1hfmWKRGM+kzRAPnkuVcGbHrxKCKH5r+niCkfZ4ol1efhr+RC9f3TevvuS/kT/Xv59//FbP+/sdptb1lNIcExbd+0/B97S/uJ9MrxvX76P0/f9+Gff5MvwhKI5XzA+WboUK5iQeezjrBezBy+fg5DirzPkZVefGWZYXUCnEb+8XiSDFcYkphwRbL3ZRXZbGE9Y0c6uhlF0RSKSrB4Dx3b2j99Vqs//srtpQh5x6Yqz+mNXGq4PX8cnnFC8KKGDr9dbTKaYJkvWRFwVIObYN21efi6QZA95XVBCkevwSsTRO1n3XUiI/eBC0gvsevkufbie3t/Yd9blDfz0bvHQ70NzG4EZVeH8hyldcoGgOSWsiiQ0IBMSo+g9ITPeriQ3Myvj2H+TqSIrjyLiPeZVt2E29NUzWIm3d9eCEtqmoCJCIxxdIu9xshqoEGhMYpx15TP15XMpBndT1LKHsYX8nnV2YYvij/VAWOF6USktRWkR0tJtrPSDDy/7XdyUFcQ9KUUJFXPnI9wObSmZJx6uUUEuP/agCaZ1kueQyhaoFrwJjhANn6r2TwdEfutnErKJrEEPbFgBPRyCahf5jPhdZYOzoSPbkDrKp6x0tNKL9XwaqYpWAODkNaU9UrXtoIpUbTtn10BtajHrRl2Cwd39/WdW8w/+Occp/T2+KwOAzfFyEqE0XRuPWulK/Ocj7I49Lc95Lh6Nu+Q5t6qpQGhqiedMoOE5awc0p52CpQjtB3MiT0x92xLdjtXeaaj2ptlQ7znq+sYFgIZbQV7f5N53WxITTch4nOJ6foKGdVB+shWMfyQKxDej5FUrQTdQ3YhA4c8b+wObVTWxljHYK9gcBWw3sT/jY/yAqBdkELpVTWxApqN52iLwu0XTMMpme4ejJ0xDD2noEkXhJGYFj8kfJ3o0rYNGpyjdr9DLaEINI8dj4Glp1HdGjt1puDiUPGdnoNKooQsXu6JRU40WP6dLLwi9OxT7EVvxs/MjOz+2uUfnRxvpaaR2it5Pi3B9/x6TXoCamH5XLpMNoC25TPtjMcuQWAxILNY2N2ANm9EhwydalpqtGCzVTErQrmSSLRfUj0um6Wp79iMfQVturoVLSd9XjJyxHnAiBqDLEbPUzoSuoOqgaWaH41Ga/ScM+Zm7f4sFZP9sr9ytR4WauLjGYzSPMrgWztvJ87cle12udWE3ZHC7K9mp+YvTi+0KMq5QccHMOyHjjXlzoUdHGBRam4LCpnQKZTo1O4su9Wus5jLOHuur8lib5vZbeKyOpST5Duix2m1VTNJVW3Z9d+Sx2rIqH6XHqqaTPuJnVnGV7+IISax1SA/jnBwyJSS/WXEbJhdAZ26luuMjxQmb0QWJP81wnAkRRdGImcFjkZ9vY9e3dPJzwQh2ugMEwqrpAoODJ4eGivxUx7KgRS9b9dCriqbKw4K2zBJpFRS2iba69O0205bm3VQt6jfSVnlnj0as9m8y1xpWktEWsNqalVw5ISN11LHjB9QgtUQBzJREJH3hxcLJc4Fra1IMe+UCoIapQZ5OYmSwEp2af1qTxQvXBXnUef8nwx6Sb+ZCt2lqQnZTdydzNVQ7Xf4AmrCnVlOOlD9Mw2ob1khGadgwEb8rAhG7GktwvA3jMA2OxXyMXQ/rCWPk2pa9PSjbE8YQNCSMzpxPCGoIg0cPPaC8o0hn2AvHzIoAwzvTw4v0MBCm+HDyVd25kyAH7cZstyE3CKU4Fm4wq6hybPNiWP5rSRTWVt3ujja0smmA0y6Tzes3HK9ub2ibHHV7vapNIZcVq+5kROcHHuTMrt0yGJc7GkCwV6VQXSnGdRQnf/yZ0fI5Bte4VPY+87H6E3iK0DwU55s6znJ7UW4OPLTcVE+YcUnAph16iJ5FVyO6g6ucmvVK8qNAbwoJhiTOZHgc0sMmi1IGOukNnQFE+4xRtNKzzD1K70TzV03PjtYh/kgjlIHV0uFSAmi5o44dLleBYp4N4ScLzwZEY/4HB2du9QVqgtMZiVN8kTLLcZaaRmpu00MR3fnJanRzULu/r+1adfZ8o90fHrfdbxtoy9sdlY52tRdLvPYRzg//FMJL45LbD7bcuzXY8d4tvSKpkcvpKpKhKlLtEY1j1ST56HJTTbLEkQaxed6W7PeONMmxqs8RX815aVxyewjr28vnfKT2HWnSkb0tOagmNd6Of9ya5LTXJAmBg272B8uaIcqvW5PUlIw4p0zprFf6VotxGzFlkrWMebW0qldVl5m/Vin717xKOcSW+cihh6I3/MI09P3VGxed21/VXuVzZ8q30PiIre10r7nbDiTh6T7kAsUOj7I2we3ddlYsvn2XA6H4giC8+R8=7Vtbc5s4FP41ntk+tGMQAvyYOLdNp9NOvTttn3ZkkDGNQA7It/z6FUayBcK3xNhMA34I5+iCdL5P5xwJ0gH9aHGfoMn4C/Ux6Zhdf9EBNx3TNLqmzf9kmmWucSyQK4Ik9EWljWIQvmDZUminoY/TQkVGKWHhpKj0aBxjjxV0KEnovFhtREnxqRMUYE0x8BDRtT9Cn41zrWs6G/0DDoOxfLJh9/KSCMnKYibpGPl0rqjAbQf0E0pZfhct+phkxpN2ue69/J493t0Yzy/92Xf/3v3nKviYd3Z3TJP1FBIcs9N2beZdzxCZCnuJubKlNGBCp7GPs06MDriej0OGBxPkZaVzThmuG7OIiOK1jbpcGNGYCUJwc/NC3iyMAy7CrDQkpE8JTVaPASOY/UQrRZ9fWWuW0CeslNiri5dEdIaGq+FmQ0hwGr6oMmWIKTInOFZl7IeqKHioaA60vkBphhOGFwr3BBr3mEaYJUteRZTyueVNxMoCUBBtvuEpsIVurHDUcoQSibURrPve4M9vBAWOoENPo8MPPHxAsU9w8jZiKFwwuE2uA4LSVPAkR1auTp1GRaL4CLsjr5IQnouHozoRc7sFxCwANcQMswqxugCTA1AQE3Clq2HxUZndPiJkiLynVIOQz5uVFnDBqDGNcQkBoUIkDGIuetzEnBvgOrNiyN3ulSiIQt/PHlNJjA11yn4CVjoRMWKrNmANWFyKbsVS7Dk6sOAEwBqfH8jEnz7+9yv4m4HH5+gruhe+XMWVxt/x8xSn7K8PjVmKELu+VbUUXXMIVr65LsQsGcDlUrQOXIpmXYgZGmIaTDj2r7KUJls3mdFDr4hMcVlwiyXLn6rwKxM+QSneLNTCm6WUFiH7KYMbv1dacWnTKBNkm+NgSuk08fD+OMJQEmC2w2YCC+wXcjgddAVUWIGp1CWYIBbOiplfFdDiCd9oyKercKrk3iEsdpHPW7RSU61yR3axI2Bbn3rqVew2N5PW7YqFayO8nph6jseD+oB6T5i1oX1baLfNM4b2SthARQT4OsFx6/4r3L/dvbT7tyrguktQhFu8qvA6NHOuDS89caZxn9C0xasSL/vSeNnvM73aFdH3pldWs9IroxhiofHa9Ao4uzuqOaFymkLF42i1ly6gWXTpljIy60R0sctp/Ra6cADRUqk2ySqkh/NbDnjruMr1jUJ9fpOP4KTcdf9Q7sJ3wl2zodx1zsBd/XT63wmhyG93sdt2sevIeLFdrORJe5B5YKYNL3+Q+U5PMnf5nL3xR/K8KQGodJQJwYmOMi3rNUeZbw1HcviHhiMIzxCODLPCteURqfVsVZ7t4md0hn5IdyHPdpzN93sfs1nep3sq71Pe6dvnSX+P9jfn2LoZ+pHl7YzzZ5DHsKY4nOZkv845P8+ohkw/tWxf4mwNEM6h79zqCxD68Uj7VmAHYBd/K2Dq28s/JKLbjY7ozqs/jQBbKFRzRHfgcRHdAWeI6GZjdtqnZq/7TthbTmybwt63HcdycfNZe159888B4PZ/ \ No newline at end of file diff --git a/lib/PsychicHttp/src/ChunkPrinter.cpp b/lib/PsychicHttp/src/ChunkPrinter.cpp new file mode 100644 index 0000000..b3f8713 --- /dev/null +++ b/lib/PsychicHttp/src/ChunkPrinter.cpp @@ -0,0 +1,85 @@ + +#include "ChunkPrinter.h" + +ChunkPrinter::ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len) : + _response(response), + _buffer(buffer), + _length(len), + _pos(0) +{} + +ChunkPrinter::~ChunkPrinter() +{ + flush(); +} + +size_t ChunkPrinter::write(uint8_t c) +{ + esp_err_t err; + + //if we're full, send a chunk + if (_pos == _length) + { + _pos = 0; + err = _response->sendChunk(_buffer, _length); + + if (err != ESP_OK) + return 0; + } + + _buffer[_pos] = c; + _pos++; + return 1; +} + +size_t ChunkPrinter::write(const uint8_t *buffer, size_t size) +{ + size_t written = 0; + + while (written < size) + { + size_t space = _length - _pos; + size_t blockSize = std::min(space, size - written); + + memcpy(_buffer + _pos, buffer + written, blockSize); + _pos += blockSize; + + if (_pos == _length) + { + _pos = 0; + + if (_response->sendChunk(_buffer, _length) != ESP_OK) + return written; + } + written += blockSize; //Update if sent correctly. + } + return written; +} + +void ChunkPrinter::flush() +{ + if (_pos) + { + _response->sendChunk(_buffer, _pos); + _pos = 0; + } +} + +size_t ChunkPrinter::copyFrom(Stream &stream) +{ + size_t count = 0; + + while (stream.available()){ + + if (_pos == _length) + { + _response->sendChunk(_buffer, _length); + _pos = 0; + } + + size_t readBytes = stream.readBytes(_buffer + _pos, _length - _pos); + _pos += readBytes; + count += readBytes; + } + return count; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/ChunkPrinter.h b/lib/PsychicHttp/src/ChunkPrinter.h new file mode 100644 index 0000000..f63fac0 --- /dev/null +++ b/lib/PsychicHttp/src/ChunkPrinter.h @@ -0,0 +1,27 @@ +#ifndef ChunkPrinter_h +#define ChunkPrinter_h + +#include "PsychicResponse.h" +#include + +class ChunkPrinter : public Print +{ + private: + PsychicResponse *_response; + uint8_t *_buffer; + size_t _length; + size_t _pos; + + public: + ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len); + ~ChunkPrinter(); + + size_t write(uint8_t c) override; + size_t write(const uint8_t *buffer, size_t size) override; + + size_t copyFrom(Stream &stream); + + void flush() override; +}; + +#endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicClient.cpp b/lib/PsychicHttp/src/PsychicClient.cpp new file mode 100644 index 0000000..fd7820d --- /dev/null +++ b/lib/PsychicHttp/src/PsychicClient.cpp @@ -0,0 +1,72 @@ +#include "PsychicClient.h" +#include "PsychicHttpServer.h" +#include + +PsychicClient::PsychicClient(httpd_handle_t server, int socket) : + _server(server), + _socket(socket), + _friend(NULL), + isNew(false) +{} + +PsychicClient::~PsychicClient() { +} + +httpd_handle_t PsychicClient::server() { + return _server; +} + +int PsychicClient::socket() { + return _socket; +} + +// I'm not sure this is entirely safe to call. I was having issues with race conditions when highly loaded using this. +esp_err_t PsychicClient::close() +{ + esp_err_t err = httpd_sess_trigger_close(_server, _socket); + //PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list. + + return err; +} + +IPAddress PsychicClient::localIP() +{ + IPAddress address(0,0,0,0); + + char ipstr[INET6_ADDRSTRLEN]; + struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing + socklen_t addr_size = sizeof(addr); + + if (getsockname(_socket, (struct sockaddr *)&addr, &addr_size) < 0) { + ESP_LOGE(PH_TAG, "Error getting client IP"); + return address; + } + + // Convert to IPv4 string + inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr)); + //ESP_LOGD(PH_TAG, "Client Local IP => %s", ipstr); + address.fromString(ipstr); + + return address; +} + +IPAddress PsychicClient::remoteIP() +{ + IPAddress address(0,0,0,0); + + char ipstr[INET6_ADDRSTRLEN]; + struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing + socklen_t addr_size = sizeof(addr); + + if (getpeername(_socket, (struct sockaddr *)&addr, &addr_size) < 0) { + ESP_LOGE(PH_TAG, "Error getting client IP"); + return address; + } + + // Convert to IPv4 string + inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr)); + //ESP_LOGD(PH_TAG, "Client Remote IP => %s", ipstr); + address.fromString(ipstr); + + return address; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicClient.h b/lib/PsychicHttp/src/PsychicClient.h new file mode 100644 index 0000000..b823df7 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicClient.h @@ -0,0 +1,35 @@ +#ifndef PsychicClient_h +#define PsychicClient_h + +#include "PsychicCore.h" + +/* +* PsychicClient :: Generic wrapper around the ESP-IDF socket +*/ + +class PsychicClient { + protected: + httpd_handle_t _server; + int _socket; + + public: + PsychicClient(httpd_handle_t server, int socket); + ~PsychicClient(); + + //no idea if this is the right way to do it or not, but lets see. + //pointer to our derived class (eg. PsychicWebSocketConnection) + void *_friend; + + bool isNew = false; + + bool operator==(PsychicClient& rhs) const { return _socket == rhs.socket(); } + + httpd_handle_t server(); + int socket(); + esp_err_t close(); + + IPAddress localIP(); + IPAddress remoteIP(); +}; + +#endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicCore.h b/lib/PsychicHttp/src/PsychicCore.h new file mode 100644 index 0000000..fe88b38 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicCore.h @@ -0,0 +1,107 @@ +#ifndef PsychicCore_h +#define PsychicCore_h + +#define PH_TAG "psychic" + +//version numbers +#define PSYCHIC_HTTP_VERSION_MAJOR 1 +#define PSYCHIC_HTTP_VERSION_MINOR 1 +#define PSYCHIC_HTTP_VERSION_PATCH 0 + +#ifndef MAX_COOKIE_SIZE + #define MAX_COOKIE_SIZE 512 +#endif + +#ifndef FILE_CHUNK_SIZE + #define FILE_CHUNK_SIZE 8*1024 +#endif + +#ifndef STREAM_CHUNK_SIZE + #define STREAM_CHUNK_SIZE 1024 +#endif + +#ifndef MAX_UPLOAD_SIZE + #define MAX_UPLOAD_SIZE (2048*1024) // 2MB +#endif + +#ifndef MAX_REQUEST_BODY_SIZE + #define MAX_REQUEST_BODY_SIZE (16*1024) //16K +#endif + +#ifdef ARDUINO + #include +#endif + +#include +#include +#include +#include +#include "esp_random.h" +#include "MD5Builder.h" +#include +#include "FS.h" +#include + +enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH }; + +String urlDecode(const char* encoded); + +class PsychicHttpServer; +class PsychicRequest; +class PsychicWebSocketRequest; +class PsychicClient; + +//filter function definition +typedef std::function PsychicRequestFilterFunction; + +//client connect callback +typedef std::function PsychicClientCallback; + +//callback definitions +typedef std::function PsychicHttpRequestCallback; +typedef std::function PsychicJsonRequestCallback; + +struct HTTPHeader { + char * field; + char * value; +}; + +class DefaultHeaders { + std::list _headers; + +public: + DefaultHeaders() {} + + void addHeader(const String& field, const String& value) + { + addHeader(field.c_str(), value.c_str()); + } + + void addHeader(const char * field, const char * value) + { + HTTPHeader header; + + //these are just going to stick around forever. + header.field =(char *)malloc(strlen(field)+1); + header.value = (char *)malloc(strlen(value)+1); + + strlcpy(header.field, field, strlen(field)+1); + strlcpy(header.value, value, strlen(value)+1); + + _headers.push_back(header); + } + + const std::list& getHeaders() { return _headers; } + + //delete the copy constructor, singleton class + DefaultHeaders(DefaultHeaders const &) = delete; + DefaultHeaders &operator=(DefaultHeaders const &) = delete; + + //single static class interface + static DefaultHeaders &Instance() { + static DefaultHeaders instance; + return instance; + } +}; + +#endif //PsychicCore_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicEndpoint.cpp b/lib/PsychicHttp/src/PsychicEndpoint.cpp new file mode 100644 index 0000000..e692658 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicEndpoint.cpp @@ -0,0 +1,90 @@ +#include "PsychicEndpoint.h" +#include "PsychicHttpServer.h" + +PsychicEndpoint::PsychicEndpoint() : + _server(NULL), + _uri(""), + _method(HTTP_GET), + _handler(NULL) +{ +} + +PsychicEndpoint::PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri) : + _server(server), + _uri(uri), + _method(method), + _handler(NULL) +{ +} + +PsychicEndpoint * PsychicEndpoint::setHandler(PsychicHandler *handler) +{ + //clean up old / default handler + if (_handler != NULL) + delete _handler; + + //get our new pointer + _handler = handler; + + //keep a pointer to the server + _handler->_server = _server; + + return this; +} + +PsychicHandler * PsychicEndpoint::handler() +{ + return _handler; +} + +String PsychicEndpoint::uri() { + return _uri; +} + +esp_err_t PsychicEndpoint::requestCallback(httpd_req_t *req) +{ + #ifdef ENABLE_ASYNC + if (is_on_async_worker_thread() == false) { + if (submit_async_req(req, PsychicEndpoint::requestCallback) == ESP_OK) { + return ESP_OK; + } else { + httpd_resp_set_status(req, "503 Busy"); + httpd_resp_sendstr(req, "No workers available. Server busy."); + return ESP_OK; + } + } + #endif + + PsychicEndpoint *self = (PsychicEndpoint *)req->user_ctx; + PsychicHandler *handler = self->handler(); + PsychicRequest request(self->_server, req); + + //make sure we have a handler + if (handler != NULL) + { + if (handler->filter(&request) && handler->canHandle(&request)) + { + //check our credentials + if (handler->needsAuthentication(&request)) + return handler->authenticate(&request); + + //pass it to our handler + return handler->handleRequest(&request); + } + //pass it to our generic handlers + else + return PsychicHttpServer::notFoundHandler(req, HTTPD_500_INTERNAL_SERVER_ERROR); + } + else + return request.reply(500, "text/html", "No handler registered."); +} + +PsychicEndpoint* PsychicEndpoint::setFilter(PsychicRequestFilterFunction fn) { + _handler->setFilter(fn); + return this; +} + +PsychicEndpoint* PsychicEndpoint::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) { + _handler->setAuthentication(username, password, method, realm, authFailMsg); + return this; +}; \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicEndpoint.h b/lib/PsychicHttp/src/PsychicEndpoint.h new file mode 100644 index 0000000..540b294 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicEndpoint.h @@ -0,0 +1,37 @@ +#ifndef PsychicEndpoint_h +#define PsychicEndpoint_h + +#include "PsychicCore.h" + +class PsychicHandler; + +#ifdef ENABLE_ASYNC + #include "async_worker.h" +#endif + +class PsychicEndpoint +{ + friend PsychicHttpServer; + + private: + PsychicHttpServer *_server; + String _uri; + http_method _method; + PsychicHandler *_handler; + + public: + PsychicEndpoint(); + PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri); + + PsychicEndpoint *setHandler(PsychicHandler *handler); + PsychicHandler *handler(); + + PsychicEndpoint* setFilter(PsychicRequestFilterFunction fn); + PsychicEndpoint* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = ""); + + String uri(); + + static esp_err_t requestCallback(httpd_req_t *req); +}; + +#endif // PsychicEndpoint_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicEventSource.cpp b/lib/PsychicHttp/src/PsychicEventSource.cpp new file mode 100644 index 0000000..25f947d --- /dev/null +++ b/lib/PsychicHttp/src/PsychicEventSource.cpp @@ -0,0 +1,225 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "PsychicEventSource.h" + +/*****************************************/ +// PsychicEventSource - Handler +/*****************************************/ + +PsychicEventSource::PsychicEventSource() : + PsychicHandler(), + _onOpen(NULL), + _onClose(NULL) +{} + +PsychicEventSource::~PsychicEventSource() { +} + +PsychicEventSourceClient * PsychicEventSource::getClient(int socket) +{ + PsychicClient *client = PsychicHandler::getClient(socket); + + if (client == NULL) + return NULL; + + return (PsychicEventSourceClient *)client->_friend; +} + +PsychicEventSourceClient * PsychicEventSource::getClient(PsychicClient *client) { + return getClient(client->socket()); +} + +esp_err_t PsychicEventSource::handleRequest(PsychicRequest *request) +{ + //start our open ended HTTP response + PsychicEventSourceResponse response(request); + esp_err_t err = response.send(); + + //lookup our client + PsychicClient *client = checkForNewClient(request->client()); + if (client->isNew) + { + //did we get our last id? + if(request->hasHeader("Last-Event-ID")) + { + PsychicEventSourceClient *buddy = getClient(client); + buddy->_lastId = atoi(request->header("Last-Event-ID").c_str()); + } + + //let our handler know. + openCallback(client); + } + + return err; +} + +PsychicEventSource * PsychicEventSource::onOpen(PsychicEventSourceClientCallback fn) { + _onOpen = fn; + return this; +} + +PsychicEventSource * PsychicEventSource::onClose(PsychicEventSourceClientCallback fn) { + _onClose = fn; + return this; +} + +void PsychicEventSource::addClient(PsychicClient *client) { + client->_friend = new PsychicEventSourceClient(client); + PsychicHandler::addClient(client); +} + +void PsychicEventSource::removeClient(PsychicClient *client) { + PsychicHandler::removeClient(client); + delete (PsychicEventSourceClient*)client->_friend; + client->_friend = NULL; +} + +void PsychicEventSource::openCallback(PsychicClient *client) { + PsychicEventSourceClient *buddy = getClient(client); + if (buddy == NULL) + { + return; + } + + if (_onOpen != NULL) + _onOpen(buddy); +} + +void PsychicEventSource::closeCallback(PsychicClient *client) { + PsychicEventSourceClient *buddy = getClient(client); + if (buddy == NULL) + { + return; + } + + if (_onClose != NULL) + _onClose(getClient(buddy)); +} + +void PsychicEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) +{ + String ev = generateEventMessage(message, event, id, reconnect); + for(PsychicClient *c : _clients) { + ((PsychicEventSourceClient*)c->_friend)->sendEvent(ev.c_str()); + } +} + +/*****************************************/ +// PsychicEventSourceClient +/*****************************************/ + +PsychicEventSourceClient::PsychicEventSourceClient(PsychicClient *client) : + PsychicClient(client->server(), client->socket()), + _lastId(0) +{ +} + +PsychicEventSourceClient::~PsychicEventSourceClient(){ +} + +void PsychicEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){ + String ev = generateEventMessage(message, event, id, reconnect); + sendEvent(ev.c_str()); +} + +void PsychicEventSourceClient::sendEvent(const char *event) { + int result; + do { + result = httpd_socket_send(this->server(), this->socket(), event, strlen(event), 0); + } while (result == HTTPD_SOCK_ERR_TIMEOUT); + + //if (result < 0) + //error log here +} + +/*****************************************/ +// PsychicEventSourceResponse +/*****************************************/ + +PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicRequest *request) + : PsychicResponse(request) +{ +} + +esp_err_t PsychicEventSourceResponse::send() { + + //build our main header + String out = String(); + out.concat("HTTP/1.1 200 OK\r\n"); + out.concat("Content-Type: text/event-stream\r\n"); + out.concat("Cache-Control: no-cache\r\n"); + out.concat("Connection: keep-alive\r\n"); + + //get our global headers out of the way first + for (HTTPHeader header : DefaultHeaders::Instance().getHeaders()) + out.concat(String(header.field) + ": " + String(header.value) + "\r\n"); + + //separator + out.concat("\r\n"); + + int result; + do { + result = httpd_send(_request->request(), out.c_str(), out.length()); + } while (result == HTTPD_SOCK_ERR_TIMEOUT); + + if (result < 0) + ESP_LOGE(PH_TAG, "EventSource send failed with %s", esp_err_to_name(result)); + + if (result > 0) + return ESP_OK; + else + return ESP_ERR_HTTPD_RESP_SEND; +} + +/*****************************************/ +// Event Message Generator +/*****************************************/ + +String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect) { + String ev = ""; + + if(reconnect){ + ev += "retry: "; + ev += String(reconnect); + ev += "\r\n"; + } + + if(id){ + ev += "id: "; + ev += String(id); + ev += "\r\n"; + } + + if(event != NULL){ + ev += "event: "; + ev += String(event); + ev += "\r\n"; + } + + if(message != NULL){ + ev += "data: "; + ev += String(message); + ev += "\r\n"; + } + ev += "\r\n"; + + return ev; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicEventSource.h b/lib/PsychicHttp/src/PsychicEventSource.h new file mode 100644 index 0000000..ab31980 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicEventSource.h @@ -0,0 +1,82 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef PsychicEventSource_H_ +#define PsychicEventSource_H_ + +#include "PsychicCore.h" +#include "PsychicHandler.h" +#include "PsychicClient.h" +#include "PsychicResponse.h" + +class PsychicEventSource; +class PsychicEventSourceResponse; +class PsychicEventSourceClient; +class PsychicResponse; + +typedef std::function PsychicEventSourceClientCallback; + +class PsychicEventSourceClient : public PsychicClient { + friend PsychicEventSource; + + protected: + uint32_t _lastId; + + public: + PsychicEventSourceClient(PsychicClient *client); + ~PsychicEventSourceClient(); + + uint32_t lastId() const { return _lastId; } + void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); + void sendEvent(const char *event); +}; + +class PsychicEventSource : public PsychicHandler { + private: + PsychicEventSourceClientCallback _onOpen; + PsychicEventSourceClientCallback _onClose; + + public: + PsychicEventSource(); + ~PsychicEventSource(); + + PsychicEventSourceClient * getClient(int socket) override; + PsychicEventSourceClient * getClient(PsychicClient *client) override; + void addClient(PsychicClient *client) override; + void removeClient(PsychicClient *client) override; + void openCallback(PsychicClient *client) override; + void closeCallback(PsychicClient *client) override; + + PsychicEventSource *onOpen(PsychicEventSourceClientCallback fn); + PsychicEventSource *onClose(PsychicEventSourceClientCallback fn); + + esp_err_t handleRequest(PsychicRequest *request) override final; + + void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); +}; + +class PsychicEventSourceResponse: public PsychicResponse { + public: + PsychicEventSourceResponse(PsychicRequest *request); + virtual esp_err_t send() override; +}; + +String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect); + +#endif /* PsychicEventSource_H_ */ \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicFileResponse.cpp b/lib/PsychicHttp/src/PsychicFileResponse.cpp new file mode 100644 index 0000000..5fc9822 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicFileResponse.cpp @@ -0,0 +1,159 @@ +#include "PsychicFileResponse.h" +#include "PsychicResponse.h" +#include "PsychicRequest.h" + + +PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path, const String& contentType, bool download) + : PsychicResponse(request) { + //_code = 200; + String _path(path); + + if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){ + _path = _path+".gz"; + addHeader("Content-Encoding", "gzip"); + } + + _content = fs.open(_path, "r"); + _contentLength = _content.size(); + + if(contentType == "") + _setContentType(path); + else + setContentType(contentType.c_str()); + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26+path.length()-filenameStart]; + char* filename = (char*)path.c_str() + filenameStart; + + if(download) { + // set filename and force download + snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename); + } else { + // set filename and force rendering + snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename); + } + addHeader("Content-Disposition", buf); +} + +PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, File content, const String& path, const String& contentType, bool download) + : PsychicResponse(request) { + String _path(path); + + if(!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")){ + addHeader("Content-Encoding", "gzip"); + } + + _content = content; + _contentLength = _content.size(); + + if(contentType == "") + _setContentType(path); + else + setContentType(contentType.c_str()); + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26+path.length()-filenameStart]; + char* filename = (char*)path.c_str() + filenameStart; + + if(download) { + snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename); + } else { + snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename); + } + addHeader("Content-Disposition", buf); +} + +PsychicFileResponse::~PsychicFileResponse() +{ + if(_content) + _content.close(); +} + +void PsychicFileResponse::_setContentType(const String& path){ + const char *_contentType; + + if (path.endsWith(".html")) _contentType = "text/html"; + else if (path.endsWith(".htm")) _contentType = "text/html"; + else if (path.endsWith(".css")) _contentType = "text/css"; + else if (path.endsWith(".json")) _contentType = "application/json"; + else if (path.endsWith(".js")) _contentType = "application/javascript"; + else if (path.endsWith(".png")) _contentType = "image/png"; + else if (path.endsWith(".gif")) _contentType = "image/gif"; + else if (path.endsWith(".jpg")) _contentType = "image/jpeg"; + else if (path.endsWith(".ico")) _contentType = "image/x-icon"; + else if (path.endsWith(".svg")) _contentType = "image/svg+xml"; + else if (path.endsWith(".eot")) _contentType = "font/eot"; + else if (path.endsWith(".woff")) _contentType = "font/woff"; + else if (path.endsWith(".woff2")) _contentType = "font/woff2"; + else if (path.endsWith(".ttf")) _contentType = "font/ttf"; + else if (path.endsWith(".xml")) _contentType = "text/xml"; + else if (path.endsWith(".pdf")) _contentType = "application/pdf"; + else if (path.endsWith(".zip")) _contentType = "application/zip"; + else if(path.endsWith(".gz")) _contentType = "application/x-gzip"; + else _contentType = "text/plain"; + + setContentType(_contentType); +} + +esp_err_t PsychicFileResponse::send() +{ + esp_err_t err = ESP_OK; + + //just send small files directly + size_t size = getContentLength(); + if (size < FILE_CHUNK_SIZE) + { + uint8_t *buffer = (uint8_t *)malloc(size); + if (buffer == NULL) + { + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); + return ESP_FAIL; + } + + size_t readSize = _content.readBytes((char *)buffer, size); + + this->setContent(buffer, readSize); + err = PsychicResponse::send(); + + free(buffer); + } + else + { + /* Retrieve the pointer to scratch buffer for temporary storage */ + char *chunk = (char *)malloc(FILE_CHUNK_SIZE); + if (chunk == NULL) + { + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); + return ESP_FAIL; + } + + this->sendHeaders(); + + size_t chunksize; + do { + /* Read file in chunks into the scratch buffer */ + chunksize = _content.readBytes(chunk, FILE_CHUNK_SIZE); + if (chunksize > 0) + { + err = this->sendChunk((uint8_t *)chunk, chunksize); + if (err != ESP_OK) + break; + } + + /* Keep looping till the whole file is sent */ + } while (chunksize != 0); + + //keep track of our memory + free(chunk); + + if (err == ESP_OK) + { + ESP_LOGD(PH_TAG, "File sending complete"); + this->finishChunking(); + } + } + + return err; +} diff --git a/lib/PsychicHttp/src/PsychicFileResponse.h b/lib/PsychicHttp/src/PsychicFileResponse.h new file mode 100644 index 0000000..8eb75fc --- /dev/null +++ b/lib/PsychicHttp/src/PsychicFileResponse.h @@ -0,0 +1,23 @@ +#ifndef PsychicFileResponse_h +#define PsychicFileResponse_h + +#include "PsychicCore.h" +#include "PsychicResponse.h" + +class PsychicRequest; + +class PsychicFileResponse: public PsychicResponse +{ + using File = fs::File; + using FS = fs::FS; + private: + File _content; + void _setContentType(const String& path); + public: + PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path, const String& contentType=String(), bool download=false); + PsychicFileResponse(PsychicRequest *request, File content, const String& path, const String& contentType=String(), bool download=false); + ~PsychicFileResponse(); + esp_err_t send(); +}; + +#endif // PsychicFileResponse_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHandler.cpp b/lib/PsychicHttp/src/PsychicHandler.cpp new file mode 100644 index 0000000..097b30e --- /dev/null +++ b/lib/PsychicHttp/src/PsychicHandler.cpp @@ -0,0 +1,111 @@ +#include "PsychicHandler.h" + +PsychicHandler::PsychicHandler() : + _filter(NULL), + _server(NULL), + _username(""), + _password(""), + _method(DIGEST_AUTH), + _realm(""), + _authFailMsg(""), + _subprotocol("") + {} + +PsychicHandler::~PsychicHandler() { + // actual PsychicClient deletion handled by PsychicServer + // for (PsychicClient *client : _clients) + // delete(client); + _clients.clear(); +} + +PsychicHandler* PsychicHandler::setFilter(PsychicRequestFilterFunction fn) { + _filter = fn; + return this; +} + +bool PsychicHandler::filter(PsychicRequest *request){ + return _filter == NULL || _filter(request); +} + +void PsychicHandler::setSubprotocol(const String& subprotocol) { + this->_subprotocol = subprotocol; +} +const char* PsychicHandler::getSubprotocol() const { + return _subprotocol.c_str(); +} + +PsychicHandler* PsychicHandler::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) { + _username = String(username); + _password = String(password); + _method = method; + _realm = String(realm); + _authFailMsg = String(authFailMsg); + return this; +}; + +bool PsychicHandler::needsAuthentication(PsychicRequest *request) { + return (_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()); +} + +esp_err_t PsychicHandler::authenticate(PsychicRequest *request) { + return request->requestAuthentication(_method, _realm.c_str(), _authFailMsg.c_str()); +} + +PsychicClient * PsychicHandler::checkForNewClient(PsychicClient *client) +{ + PsychicClient *c = PsychicHandler::getClient(client); + if (c == NULL) + { + c = client; + addClient(c); + c->isNew = true; + } + else + c->isNew = false; + + return c; +} + +void PsychicHandler::checkForClosedClient(PsychicClient *client) +{ + if (hasClient(client)) + { + closeCallback(client); + removeClient(client); + } +} + +void PsychicHandler::addClient(PsychicClient *client) { + _clients.push_back(client); +} + +void PsychicHandler::removeClient(PsychicClient *client) { + _clients.remove(client); +} + +PsychicClient * PsychicHandler::getClient(int socket) +{ + //make sure the server has it too. + if (!_server->hasClient(socket)) + return NULL; + + //what about us? + for (PsychicClient *client : _clients) + if (client->socket() == socket) + return client; + + //nothing found. + return NULL; +} + +PsychicClient * PsychicHandler::getClient(PsychicClient *client) { + return PsychicHandler::getClient(client->socket()); +} + +bool PsychicHandler::hasClient(PsychicClient *socket) { + return PsychicHandler::getClient(socket) != NULL; +} + +const std::list& PsychicHandler::getClientList() { + return _clients; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHandler.h b/lib/PsychicHttp/src/PsychicHandler.h new file mode 100644 index 0000000..bad62bf --- /dev/null +++ b/lib/PsychicHttp/src/PsychicHandler.h @@ -0,0 +1,66 @@ +#ifndef PsychicHandler_h +#define PsychicHandler_h + +#include "PsychicCore.h" +#include "PsychicRequest.h" + +class PsychicEndpoint; +class PsychicHttpServer; + +/* +* HANDLER :: Can be attached to any endpoint or as a generic request handler. +*/ + +class PsychicHandler { + friend PsychicEndpoint; + + protected: + PsychicRequestFilterFunction _filter; + PsychicHttpServer *_server; + + String _username; + String _password; + HTTPAuthMethod _method; + String _realm; + String _authFailMsg; + + String _subprotocol; + + std::list _clients; + + public: + PsychicHandler(); + virtual ~PsychicHandler(); + + PsychicHandler* setFilter(PsychicRequestFilterFunction fn); + bool filter(PsychicRequest *request); + + PsychicHandler* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = ""); + bool needsAuthentication(PsychicRequest *request); + esp_err_t authenticate(PsychicRequest *request); + + virtual bool isWebSocket() { return false; }; + + void setSubprotocol(const String& subprotocol); + const char* getSubprotocol() const; + + PsychicClient * checkForNewClient(PsychicClient *client); + void checkForClosedClient(PsychicClient *client); + + virtual void addClient(PsychicClient *client); + virtual void removeClient(PsychicClient *client); + virtual PsychicClient * getClient(int socket); + virtual PsychicClient * getClient(PsychicClient *client); + virtual void openCallback(PsychicClient *client) {}; + virtual void closeCallback(PsychicClient *client) {}; + + bool hasClient(PsychicClient *client); + int count() { return _clients.size(); }; + const std::list& getClientList(); + + //derived classes must implement these functions + virtual bool canHandle(PsychicRequest *request) { return true; }; + virtual esp_err_t handleRequest(PsychicRequest *request) = 0; +}; + +#endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttp.h b/lib/PsychicHttp/src/PsychicHttp.h new file mode 100644 index 0000000..3e9da55 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicHttp.h @@ -0,0 +1,24 @@ +#ifndef PsychicHttp_h +#define PsychicHttp_h + +//#define ENABLE_ASYNC // This is something added in ESP-IDF 5.1.x where each request can be handled in its own thread + +#include +#include "PsychicHttpServer.h" +#include "PsychicRequest.h" +#include "PsychicResponse.h" +#include "PsychicEndpoint.h" +#include "PsychicHandler.h" +#include "PsychicStaticFileHandler.h" +#include "PsychicFileResponse.h" +#include "PsychicStreamResponse.h" +#include "PsychicUploadHandler.h" +#include "PsychicWebSocket.h" +#include "PsychicEventSource.h" +#include "PsychicJson.h" + +#ifdef ENABLE_ASYNC + #include "async_worker.h" +#endif + +#endif /* PsychicHttp_h */ \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttpServer.cpp b/lib/PsychicHttp/src/PsychicHttpServer.cpp new file mode 100644 index 0000000..628f38b --- /dev/null +++ b/lib/PsychicHttp/src/PsychicHttpServer.cpp @@ -0,0 +1,366 @@ +#include "PsychicHttpServer.h" +#include "PsychicEndpoint.h" +#include "PsychicHandler.h" +#include "PsychicWebHandler.h" +#include "PsychicStaticFileHandler.h" +#include "PsychicWebSocket.h" +#include "PsychicJson.h" +#include "WiFi.h" + +PsychicHttpServer::PsychicHttpServer() : + _onOpen(NULL), + _onClose(NULL) +{ + maxRequestBodySize = MAX_REQUEST_BODY_SIZE; + maxUploadSize = MAX_UPLOAD_SIZE; + + defaultEndpoint = new PsychicEndpoint(this, HTTP_GET, ""); + onNotFound(PsychicHttpServer::defaultNotFoundHandler); + + //for a regular server + config = HTTPD_DEFAULT_CONFIG(); + config.open_fn = PsychicHttpServer::openCallback; + config.close_fn = PsychicHttpServer::closeCallback; + config.uri_match_fn = httpd_uri_match_wildcard; + config.global_user_ctx = this; + config.global_user_ctx_free_fn = destroy; + config.max_uri_handlers = 20; + + #ifdef ENABLE_ASYNC + // It is advisable that httpd_config_t->max_open_sockets > MAX_ASYNC_REQUESTS + // Why? This leaves at least one socket still available to handle + // quick synchronous requests. Otherwise, all the sockets will + // get taken by the long async handlers, and your server will no + // longer be responsive. + config.max_open_sockets = ASYNC_WORKER_COUNT + 1; + config.lru_purge_enable = true; + #endif +} + +PsychicHttpServer::~PsychicHttpServer() +{ + for (auto *client : _clients) + delete(client); + _clients.clear(); + + for (auto *endpoint : _endpoints) + delete(endpoint); + _endpoints.clear(); + + for (auto *handler : _handlers) + delete(handler); + _handlers.clear(); + + delete defaultEndpoint; +} + +void PsychicHttpServer::destroy(void *ctx) +{ + // do not release any resource for PsychicHttpServer in order to be able to restart it after stopping +} + +esp_err_t PsychicHttpServer::listen(uint16_t port) +{ + this->_use_ssl = false; + this->config.server_port = port; + + return this->_start(); +} + +esp_err_t PsychicHttpServer::_start() +{ + esp_err_t ret; + + #ifdef ENABLE_ASYNC + // start workers + start_async_req_workers(); + #endif + + //fire it up. + ret = _startServer(); + if (ret != ESP_OK) + { + ESP_LOGE(PH_TAG, "Server start failed (%s)", esp_err_to_name(ret)); + return ret; + } + + // Register handler + ret = httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, PsychicHttpServer::notFoundHandler); + if (ret != ESP_OK) + ESP_LOGE(PH_TAG, "Add 404 handler failed (%s)", esp_err_to_name(ret)); + + return ret; +} + +esp_err_t PsychicHttpServer::_startServer() { + return httpd_start(&this->server, &this->config); +} + +void PsychicHttpServer::stop() +{ + httpd_stop(this->server); +} + +PsychicHandler& PsychicHttpServer::addHandler(PsychicHandler* handler){ + _handlers.push_back(handler); + return *handler; +} + +void PsychicHttpServer::removeHandler(PsychicHandler *handler){ + _handlers.remove(handler); +} + +PsychicEndpoint* PsychicHttpServer::on(const char* uri) { + return on(uri, HTTP_GET); +} + +PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method) +{ + PsychicWebHandler *handler = new PsychicWebHandler(); + + return on(uri, method, handler); +} + +PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHandler *handler) +{ + return on(uri, HTTP_GET, handler); +} + +PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicHandler *handler) +{ + //make our endpoint + PsychicEndpoint *endpoint = new PsychicEndpoint(this, method, uri); + + //set our handler + endpoint->setHandler(handler); + + // URI handler structure + httpd_uri_t my_uri { + .uri = uri, + .method = method, + .handler = PsychicEndpoint::requestCallback, + .user_ctx = endpoint, + .is_websocket = handler->isWebSocket(), + .supported_subprotocol = handler->getSubprotocol() + }; + + // Register endpoint with ESP-IDF server + esp_err_t ret = httpd_register_uri_handler(this->server, &my_uri); + if (ret != ESP_OK) + ESP_LOGE(PH_TAG, "Add endpoint failed (%s)", esp_err_to_name(ret)); + + //save it for later + _endpoints.push_back(endpoint); + + return endpoint; +} + +PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHttpRequestCallback fn) +{ + return on(uri, HTTP_GET, fn); +} + +PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicHttpRequestCallback fn) +{ + //these basic requests need a basic web handler + PsychicWebHandler *handler = new PsychicWebHandler(); + handler->onRequest(fn); + + return on(uri, method, handler); +} + +PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicJsonRequestCallback fn) +{ + return on(uri, HTTP_GET, fn); +} + +PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicJsonRequestCallback fn) +{ + //these basic requests need a basic web handler + PsychicJsonHandler *handler = new PsychicJsonHandler(); + handler->onRequest(fn); + + return on(uri, method, handler); +} + +void PsychicHttpServer::onNotFound(PsychicHttpRequestCallback fn) +{ + PsychicWebHandler *handler = new PsychicWebHandler(); + handler->onRequest(fn == nullptr ? PsychicHttpServer::defaultNotFoundHandler : fn); + + this->defaultEndpoint->setHandler(handler); +} + +esp_err_t PsychicHttpServer::notFoundHandler(httpd_req_t *req, httpd_err_code_t err) +{ + PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(req->handle); + PsychicRequest request(server, req); + + //loop through our global handlers and see if anyone wants it + for(auto *handler: server->_handlers) + { + //are we capable of handling this? + if (handler->filter(&request) && handler->canHandle(&request)) + { + //check our credentials + if (handler->needsAuthentication(&request)) + return handler->authenticate(&request); + else + return handler->handleRequest(&request); + } + } + + //nothing found, give it to our defaultEndpoint + PsychicHandler *handler = server->defaultEndpoint->handler(); + if (handler->filter(&request) && handler->canHandle(&request)) + return handler->handleRequest(&request); + + //not sure how we got this far. + return ESP_ERR_HTTPD_INVALID_REQ; +} + +esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest *request) +{ + request->reply(404, "text/html", "That URI does not exist."); + + return ESP_OK; +} + +void PsychicHttpServer::onOpen(PsychicClientCallback handler) { + this->_onOpen = handler; +} + +esp_err_t PsychicHttpServer::openCallback(httpd_handle_t hd, int sockfd) +{ + ESP_LOGD(PH_TAG, "New client connected %d", sockfd); + + //get our global server reference + PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(hd); + + //lookup our client + PsychicClient *client = server->getClient(sockfd); + if (client == NULL) + { + client = new PsychicClient(hd, sockfd); + server->addClient(client); + } + + //user callback + if (server->_onOpen != NULL) + server->_onOpen(client); + + return ESP_OK; +} + +void PsychicHttpServer::onClose(PsychicClientCallback handler) { + this->_onClose = handler; +} + +void PsychicHttpServer::closeCallback(httpd_handle_t hd, int sockfd) +{ + ESP_LOGD(PH_TAG, "Client disconnected %d", sockfd); + + PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(hd); + + //lookup our client + PsychicClient *client = server->getClient(sockfd); + if (client != NULL) + { + //give our handlers a chance to handle a disconnect first + for (PsychicEndpoint * endpoint : server->_endpoints) + { + PsychicHandler *handler = endpoint->handler(); + handler->checkForClosedClient(client); + } + + //do we have a callback attached? + if (server->_onClose != NULL) + server->_onClose(client); + + //remove it from our list + server->removeClient(client); + } + else + ESP_LOGE(PH_TAG, "No client record %d", sockfd); + + //finally close it out. + close(sockfd); +} + +PsychicStaticFileHandler* PsychicHttpServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control) +{ + PsychicStaticFileHandler* handler = new PsychicStaticFileHandler(uri, fs, path, cache_control); + this->addHandler(handler); + + return handler; +} + +void PsychicHttpServer::addClient(PsychicClient *client) { + _clients.push_back(client); +} + +void PsychicHttpServer::removeClient(PsychicClient *client) { + _clients.remove(client); + delete client; +} + +PsychicClient * PsychicHttpServer::getClient(int socket) { + for (PsychicClient * client : _clients) + if (client->socket() == socket) + return client; + + return NULL; +} + +PsychicClient * PsychicHttpServer::getClient(httpd_req_t *req) { + return getClient(httpd_req_to_sockfd(req)); +} + +bool PsychicHttpServer::hasClient(int socket) { + return getClient(socket) != NULL; +} + +const std::list& PsychicHttpServer::getClientList() { + return _clients; +} + +bool ON_STA_FILTER(PsychicRequest *request) { + return WiFi.localIP() == request->client()->localIP(); +} + +bool ON_AP_FILTER(PsychicRequest *request) { + return WiFi.softAPIP() == request->client()->localIP(); +} + +String urlDecode(const char* encoded) +{ + size_t length = strlen(encoded); + char* decoded = (char*)malloc(length + 1); + if (!decoded) { + return ""; + } + + size_t i, j = 0; + for (i = 0; i < length; ++i) { + if (encoded[i] == '%' && isxdigit(encoded[i + 1]) && isxdigit(encoded[i + 2])) { + // Valid percent-encoded sequence + int hex; + sscanf(encoded + i + 1, "%2x", &hex); + decoded[j++] = (char)hex; + i += 2; // Skip the two hexadecimal characters + } else if (encoded[i] == '+') { + // Convert '+' to space + decoded[j++] = ' '; + } else { + // Copy other characters as they are + decoded[j++] = encoded[i]; + } + } + + decoded[j] = '\0'; // Null-terminate the decoded string + + String output(decoded); + free(decoded); + + return output; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttpServer.h b/lib/PsychicHttp/src/PsychicHttpServer.h new file mode 100644 index 0000000..40e5442 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicHttpServer.h @@ -0,0 +1,81 @@ +#ifndef PsychicHttpServer_h +#define PsychicHttpServer_h + +#include "PsychicCore.h" +#include "PsychicClient.h" +#include "PsychicHandler.h" + +class PsychicEndpoint; +class PsychicHandler; +class PsychicStaticFileHandler; + +class PsychicHttpServer +{ + protected: + bool _use_ssl = false; + std::list _endpoints; + std::list _handlers; + std::list _clients; + + PsychicClientCallback _onOpen; + PsychicClientCallback _onClose; + + esp_err_t _start(); + virtual esp_err_t _startServer(); + + public: + PsychicHttpServer(); + virtual ~PsychicHttpServer(); + + //esp-idf specific stuff + httpd_handle_t server; + httpd_config_t config; + + //some limits on what we will accept + unsigned long maxUploadSize; + unsigned long maxRequestBodySize; + + PsychicEndpoint *defaultEndpoint; + + static void destroy(void *ctx); + + esp_err_t listen(uint16_t port); + + virtual void stop(); + + PsychicHandler& addHandler(PsychicHandler* handler); + void removeHandler(PsychicHandler* handler); + + void addClient(PsychicClient *client); + void removeClient(PsychicClient *client); + PsychicClient* getClient(int socket); + PsychicClient* getClient(httpd_req_t *req); + bool hasClient(int socket); + int count() { return _clients.size(); }; + const std::list& getClientList(); + + PsychicEndpoint* on(const char* uri); + PsychicEndpoint* on(const char* uri, http_method method); + PsychicEndpoint* on(const char* uri, PsychicHandler *handler); + PsychicEndpoint* on(const char* uri, http_method method, PsychicHandler *handler); + PsychicEndpoint* on(const char* uri, PsychicHttpRequestCallback onRequest); + PsychicEndpoint* on(const char* uri, http_method method, PsychicHttpRequestCallback onRequest); + PsychicEndpoint* on(const char* uri, PsychicJsonRequestCallback onRequest); + PsychicEndpoint* on(const char* uri, http_method method, PsychicJsonRequestCallback onRequest); + + static esp_err_t notFoundHandler(httpd_req_t *req, httpd_err_code_t err); + static esp_err_t defaultNotFoundHandler(PsychicRequest *request); + void onNotFound(PsychicHttpRequestCallback fn); + + void onOpen(PsychicClientCallback handler); + void onClose(PsychicClientCallback handler); + static esp_err_t openCallback(httpd_handle_t hd, int sockfd); + static void closeCallback(httpd_handle_t hd, int sockfd); + + PsychicStaticFileHandler* serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL); +}; + +bool ON_STA_FILTER(PsychicRequest *request); +bool ON_AP_FILTER(PsychicRequest *request); + +#endif // PsychicHttpServer_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttpsServer.cpp b/lib/PsychicHttp/src/PsychicHttpsServer.cpp new file mode 100644 index 0000000..5df225e --- /dev/null +++ b/lib/PsychicHttp/src/PsychicHttpsServer.cpp @@ -0,0 +1,61 @@ +#include "PsychicHttpsServer.h" + +#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE + +PsychicHttpsServer::PsychicHttpsServer() : PsychicHttpServer() +{ + //for a SSL server + ssl_config = HTTPD_SSL_CONFIG_DEFAULT(); + ssl_config.httpd.open_fn = PsychicHttpServer::openCallback; + ssl_config.httpd.close_fn = PsychicHttpServer::closeCallback; + ssl_config.httpd.uri_match_fn = httpd_uri_match_wildcard; + ssl_config.httpd.global_user_ctx = this; + ssl_config.httpd.global_user_ctx_free_fn = destroy; + ssl_config.httpd.max_uri_handlers = 20; + + // each SSL connection takes about 45kb of heap + // a barebones sketch with PsychicHttp has ~150kb of heap available + // if we set it higher than 2 and use all the connections, we get lots of memory errors. + // not to mention there is no heap left over for the program itself. + ssl_config.httpd.max_open_sockets = 2; +} + +PsychicHttpsServer::~PsychicHttpsServer() {} + +esp_err_t PsychicHttpsServer::listen(uint16_t port, const char *cert, const char *private_key) +{ + this->_use_ssl = true; + + this->ssl_config.port_secure = port; + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2) + this->ssl_config.servercert = (uint8_t *)cert; + this->ssl_config.servercert_len = strlen(cert)+1; +#else + this->ssl_config.cacert_pem = (uint8_t *)cert; + this->ssl_config.cacert_len = strlen(cert)+1; +#endif + + this->ssl_config.prvtkey_pem = (uint8_t *)private_key; + this->ssl_config.prvtkey_len = strlen(private_key)+1; + + return this->_start(); +} + +esp_err_t PsychicHttpsServer::_startServer() +{ + if (this->_use_ssl) + return httpd_ssl_start(&this->server, &this->ssl_config); + else + return httpd_start(&this->server, &this->config); +} + +void PsychicHttpsServer::stop() +{ + if (this->_use_ssl) + httpd_ssl_stop(this->server); + else + httpd_stop(this->server); +} + +#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttpsServer.h b/lib/PsychicHttp/src/PsychicHttpsServer.h new file mode 100644 index 0000000..51f8210 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicHttpsServer.h @@ -0,0 +1,39 @@ +#ifndef PsychicHttpsServer_h +#define PsychicHttpsServer_h + +#include + +#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE + +#include "PsychicCore.h" +#include "PsychicHttpServer.h" +#include +#if !CONFIG_HTTPD_WS_SUPPORT + #error PsychicHttpsServer cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration +#endif + +#define PSY_ENABLE_SSL //you can use this define in your code to enable/disable these features + +class PsychicHttpsServer : public PsychicHttpServer +{ + protected: + bool _use_ssl = false; + + public: + PsychicHttpsServer(); + ~PsychicHttpsServer(); + + httpd_ssl_config_t ssl_config; + + using PsychicHttpServer::listen; //keep the regular version + esp_err_t listen(uint16_t port, const char *cert, const char *private_key); + + virtual esp_err_t _startServer() override final; + virtual void stop() override final; +}; + +#endif // PsychicHttpsServer_h + +#else + #warning ESP-IDF https server support not enabled. +#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicJson.cpp b/lib/PsychicHttp/src/PsychicJson.cpp new file mode 100644 index 0000000..ca0f0a5 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicJson.cpp @@ -0,0 +1,133 @@ +#include "PsychicJson.h" + +#ifdef ARDUINOJSON_6_COMPATIBILITY + PsychicJsonResponse::PsychicJsonResponse(PsychicRequest *request, bool isArray, size_t maxJsonBufferSize) : + PsychicResponse(request), + _jsonBuffer(maxJsonBufferSize) + { + setContentType(JSON_MIMETYPE); + if (isArray) + _root = _jsonBuffer.createNestedArray(); + else + _root = _jsonBuffer.createNestedObject(); + } +#else + PsychicJsonResponse::PsychicJsonResponse(PsychicRequest *request, bool isArray) : PsychicResponse(request) + { + setContentType(JSON_MIMETYPE); + if (isArray) + _root = _jsonBuffer.add(); + else + _root = _jsonBuffer.add(); + } +#endif + +JsonVariant &PsychicJsonResponse::getRoot() { return _root; } + +size_t PsychicJsonResponse::getLength() +{ + return measureJson(_root); +} + +esp_err_t PsychicJsonResponse::send() +{ + esp_err_t err = ESP_OK; + size_t length = getLength(); + size_t buffer_size; + char *buffer; + + //how big of a buffer do we want? + if (length < JSON_BUFFER_SIZE) + buffer_size = length+1; + else + buffer_size = JSON_BUFFER_SIZE; + + buffer = (char *)malloc(buffer_size); + if (buffer == NULL) { + httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); + return ESP_FAIL; + } + + //send it in one shot or no? + if (length < JSON_BUFFER_SIZE) + { + serializeJson(_root, buffer, buffer_size); + + this->setContent((uint8_t *)buffer, length); + this->setContentType(JSON_MIMETYPE); + + err = PsychicResponse::send(); + } + else + { + //helper class that acts as a stream to print chunked responses + ChunkPrinter dest(this, (uint8_t *)buffer, buffer_size); + + //keep our headers + this->sendHeaders(); + + serializeJson(_root, dest); + + //send the last bits + dest.flush(); + + //done with our chunked response too + err = this->finishChunking(); + } + + //let the buffer go + free(buffer); + + return err; +} + +#ifdef ARDUINOJSON_6_COMPATIBILITY + PsychicJsonHandler::PsychicJsonHandler(size_t maxJsonBufferSize) : + _onRequest(NULL), + _maxJsonBufferSize(maxJsonBufferSize) + {}; + + PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize) : + _onRequest(onRequest), + _maxJsonBufferSize(maxJsonBufferSize) + {} +#else + PsychicJsonHandler::PsychicJsonHandler() : + _onRequest(NULL) + {}; + + PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest) : + _onRequest(onRequest) + {} +#endif + +void PsychicJsonHandler::onRequest(PsychicJsonRequestCallback fn) { _onRequest = fn; } + +esp_err_t PsychicJsonHandler::handleRequest(PsychicRequest *request) +{ + //process basic stuff + PsychicWebHandler::handleRequest(request); + + if (_onRequest) + { + #ifdef ARDUINOJSON_6_COMPATIBILITY + DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize); + DeserializationError error = deserializeJson(jsonBuffer, request->body()); + if (error) + return request->reply(400); + + JsonVariant json = jsonBuffer.as(); + #else + JsonDocument jsonBuffer; + DeserializationError error = deserializeJson(jsonBuffer, request->body()); + if (error) + return request->reply(400); + + JsonVariant json = jsonBuffer.as(); + #endif + + return _onRequest(request, json); + } + else + return request->reply(500); +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicJson.h b/lib/PsychicHttp/src/PsychicJson.h new file mode 100644 index 0000000..95ca894 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicJson.h @@ -0,0 +1,89 @@ +// PsychicJson.h +/* + Async Response to use with ArduinoJson and AsyncWebServer + Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon. + Ported to PsychicHttp by Zach Hoeken + +*/ +#ifndef PSYCHIC_JSON_H_ +#define PSYCHIC_JSON_H_ + +#include "PsychicRequest.h" +#include "PsychicWebHandler.h" +#include "ChunkPrinter.h" +#include + +#if ARDUINOJSON_VERSION_MAJOR == 6 + #define ARDUINOJSON_6_COMPATIBILITY + #ifndef DYNAMIC_JSON_DOCUMENT_SIZE + #define DYNAMIC_JSON_DOCUMENT_SIZE 4096 + #endif +#endif + + +#ifndef JSON_BUFFER_SIZE + #define JSON_BUFFER_SIZE 4*1024 +#endif + +constexpr const char *JSON_MIMETYPE = "application/json"; + +/* + * Json Response + * */ + +class PsychicJsonResponse : public PsychicResponse +{ + protected: + #ifdef ARDUINOJSON_5_COMPATIBILITY + DynamicJsonBuffer _jsonBuffer; + #elif ARDUINOJSON_VERSION_MAJOR == 6 + DynamicJsonDocument _jsonBuffer; + #else + JsonDocument _jsonBuffer; + #endif + + JsonVariant _root; + size_t _contentLength; + + public: + #ifdef ARDUINOJSON_5_COMPATIBILITY + PsychicJsonResponse(PsychicRequest *request, bool isArray = false); + #elif ARDUINOJSON_VERSION_MAJOR == 6 + PsychicJsonResponse(PsychicRequest *request, bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); + #else + PsychicJsonResponse(PsychicRequest *request, bool isArray = false); + #endif + + ~PsychicJsonResponse() {} + + JsonVariant &getRoot(); + size_t getLength(); + + virtual esp_err_t send() override; +}; + +class PsychicJsonHandler : public PsychicWebHandler +{ + protected: + PsychicJsonRequestCallback _onRequest; + #if ARDUINOJSON_VERSION_MAJOR == 6 + const size_t _maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE; + #endif + + public: + #ifdef ARDUINOJSON_5_COMPATIBILITY + PsychicJsonHandler(); + PsychicJsonHandler(PsychicJsonRequestCallback onRequest); + #elif ARDUINOJSON_VERSION_MAJOR == 6 + PsychicJsonHandler(size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); + PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); + #else + PsychicJsonHandler(); + PsychicJsonHandler(PsychicJsonRequestCallback onRequest); + #endif + + void onRequest(PsychicJsonRequestCallback fn); + virtual esp_err_t handleRequest(PsychicRequest *request) override; +}; + +#endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicRequest.cpp b/lib/PsychicHttp/src/PsychicRequest.cpp new file mode 100644 index 0000000..4244358 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicRequest.cpp @@ -0,0 +1,542 @@ +#include "PsychicRequest.h" +#include "http_status.h" +#include "PsychicHttpServer.h" + + +PsychicRequest::PsychicRequest(PsychicHttpServer *server, httpd_req_t *req) : + _server(server), + _req(req), + _method(HTTP_GET), + _query(""), + _body(""), + _tempObject(NULL) +{ + //load up our client. + this->_client = server->getClient(req); + + //handle our session data + if (req->sess_ctx != NULL) + this->_session = (SessionData *)req->sess_ctx; + else + { + this->_session = new SessionData(); + req->sess_ctx = this->_session; + } + + //callback for freeing the session later + req->free_ctx = this->freeSession; + + //load up some data + this->_uri = String(this->_req->uri); +} + +PsychicRequest::~PsychicRequest() +{ + //temorary user object + if (_tempObject != NULL) + free(_tempObject); + + //our web parameters + for (auto *param : _params) + delete(param); + _params.clear(); +} + +void PsychicRequest::freeSession(void *ctx) +{ + if (ctx != NULL) + { + SessionData *session = (SessionData*)ctx; + delete session; + } +} + +PsychicHttpServer * PsychicRequest::server() { + return _server; +} + +httpd_req_t * PsychicRequest::request() { + return _req; +} + +PsychicClient * PsychicRequest::client() { + return _client; +} + +const String PsychicRequest::getFilename() +{ + //parse the content-disposition header + if (this->hasHeader("Content-Disposition")) + { + ContentDisposition cd = this->getContentDisposition(); + if (cd.filename != "") + return cd.filename; + } + + //fall back to passed in query string + PsychicWebParameter *param = getParam("_filename"); + if (param != NULL) + return param->name(); + + //fall back to parsing it from url (useful for wildcard uploads) + String uri = this->uri(); + int filenameStart = uri.lastIndexOf('/') + 1; + String filename = uri.substring(filenameStart); + if (filename != "") + return filename; + + //finally, unknown. + ESP_LOGE(PH_TAG, "Did not get a valid filename from the upload."); + return "unknown.txt"; +} + +const ContentDisposition PsychicRequest::getContentDisposition() +{ + ContentDisposition cd; + String header = this->header("Content-Disposition"); + int start; + int end; + + if (header.indexOf("form-data") == 0) + cd.disposition = FORM_DATA; + else if (header.indexOf("attachment") == 0) + cd.disposition = ATTACHMENT; + else if (header.indexOf("inline") == 0) + cd.disposition = INLINE; + else + cd.disposition = NONE; + + start = header.indexOf("filename="); + if (start) + { + end = header.indexOf('"', start+10); + cd.filename = header.substring(start+10, end-1); + } + + start = header.indexOf("name="); + if (start) + { + end = header.indexOf('"', start+6); + cd.name = header.substring(start+6, end-1); + } + + return cd; +} + +esp_err_t PsychicRequest::loadBody() +{ + esp_err_t err = ESP_OK; + + this->_body = String(); + + size_t remaining = this->_req->content_len; + size_t actuallyReceived = 0; + char *buf = (char *)malloc(remaining + 1); + if (buf == NULL) { + ESP_LOGE(PH_TAG, "Failed to allocate memory for body"); + return ESP_FAIL; + } + + while (remaining > 0) { + int received = httpd_req_recv(this->_req, buf + actuallyReceived, remaining); + + if (received == HTTPD_SOCK_ERR_TIMEOUT) { + continue; + } + else if (received == HTTPD_SOCK_ERR_FAIL) { + ESP_LOGE(PH_TAG, "Failed to receive data."); + err = ESP_FAIL; + break; + } + + remaining -= received; + actuallyReceived += received; + } + + buf[actuallyReceived] = '\0'; + this->_body = String(buf); + free(buf); + return err; +} + +http_method PsychicRequest::method() { + return (http_method)this->_req->method; +} + +const String PsychicRequest::methodStr() { + return String(http_method_str((http_method)this->_req->method)); +} + +const String PsychicRequest::path() { + int index = _uri.indexOf("?"); + if(index == -1) + return _uri; + else + return _uri.substring(0, index); +} + +const String& PsychicRequest::uri() { + return this->_uri; +} + +const String& PsychicRequest::query() { + return this->_query; +} + +// no way to get list of headers yet.... +// int PsychicRequest::headers() +// { +// } + +const String PsychicRequest::header(const char *name) +{ + size_t header_len = httpd_req_get_hdr_value_len(this->_req, name); + + //if we've got one, allocated it and load it + if (header_len) + { + char header[header_len+1]; + httpd_req_get_hdr_value_str(this->_req, name, header, sizeof(header)); + return String(header); + } + else + return ""; +} + +bool PsychicRequest::hasHeader(const char *name) +{ + return httpd_req_get_hdr_value_len(this->_req, name) > 0; +} + +const String PsychicRequest::host() { + return this->header("Host"); +} + +const String PsychicRequest::contentType() { + return header("Content-Type"); +} + +size_t PsychicRequest::contentLength() { + return this->_req->content_len; +} + +const String& PsychicRequest::body() +{ + return this->_body; +} + +bool PsychicRequest::isMultipart() +{ + const String& type = this->contentType(); + + return (this->contentType().indexOf("multipart/form-data") >= 0); +} + +esp_err_t PsychicRequest::redirect(const char *url) +{ + PsychicResponse response(this); + response.setCode(301); + response.addHeader("Location", url); + + return response.send(); +} + +bool PsychicRequest::hasCookie(const char *key) +{ + char cookie[MAX_COOKIE_SIZE]; + size_t cookieSize = MAX_COOKIE_SIZE; + esp_err_t err = httpd_req_get_cookie_val(this->_req, key, cookie, &cookieSize); + + //did we get anything? + if (err == ESP_OK) + return true; + else if (err == ESP_ERR_HTTPD_RESULT_TRUNC) + ESP_LOGE(PH_TAG, "cookie too large (%d bytes).\n", cookieSize); + + return false; +} + +const String PsychicRequest::getCookie(const char *key) +{ + char cookie[MAX_COOKIE_SIZE]; + size_t cookieSize = MAX_COOKIE_SIZE; + esp_err_t err = httpd_req_get_cookie_val(this->_req, key, cookie, &cookieSize); + + //did we get anything? + if (err == ESP_OK) + return String(cookie); + else + return ""; +} + +void PsychicRequest::loadParams() +{ + //did we get a query string? + size_t query_len = httpd_req_get_url_query_len(_req); + if (query_len) + { + char query[query_len+1]; + httpd_req_get_url_query_str(_req, query, sizeof(query)); + _query.concat(query); + + //parse them. + _addParams(_query, false); + } + + //did we get form data as body? + if (this->method() == HTTP_POST && this->contentType().startsWith("application/x-www-form-urlencoded")) + { + _addParams(_body, true); + } +} + +void PsychicRequest::_addParams(const String& params, bool post){ + size_t start = 0; + while (start < params.length()){ + int end = params.indexOf('&', start); + if (end < 0) end = params.length(); + int equal = params.indexOf('=', start); + if (equal < 0 || equal > end) equal = end; + String name = params.substring(start, equal); + String value = equal + 1 < end ? params.substring(equal + 1, end) : String(); + addParam(name, value, true, post); + start = end + 1; + } +} + +PsychicWebParameter * PsychicRequest::addParam(const String &name, const String &value, bool decode, bool post) +{ + if (decode) + return addParam(new PsychicWebParameter(urlDecode(name.c_str()), urlDecode(value.c_str()), post)); + else + return addParam(new PsychicWebParameter(name, value, post)); +} + +PsychicWebParameter * PsychicRequest::addParam(PsychicWebParameter *param) { + // ESP_LOGD(PH_TAG, "Adding param: '%s' = '%s'", param->name().c_str(), param->value().c_str()); + _params.push_back(param); + return param; +} + +bool PsychicRequest::hasParam(const char *key) +{ + return getParam(key) != NULL; +} + +PsychicWebParameter * PsychicRequest::getParam(const char *key) +{ + for (auto *param : _params) + if (param->name().equals(key)) + return param; + + return NULL; +} + +bool PsychicRequest::hasSessionKey(const String& key) +{ + return this->_session->find(key) != this->_session->end(); +} + +const String PsychicRequest::getSessionKey(const String& key) +{ + auto it = this->_session->find(key); + if (it != this->_session->end()) + return it->second; + else + return ""; +} + +void PsychicRequest::setSessionKey(const String& key, const String& value) +{ + this->_session->insert(std::pair(key, value)); +} + +static const String md5str(const String &in){ + MD5Builder md5 = MD5Builder(); + md5.begin(); + md5.add(in); + md5.calculate(); + return md5.toString(); +} + +bool PsychicRequest::authenticate(const char * username, const char * password) +{ + if(hasHeader("Authorization")) + { + String authReq = header("Authorization"); + if(authReq.startsWith("Basic")){ + authReq = authReq.substring(6); + authReq.trim(); + char toencodeLen = strlen(username)+strlen(password)+1; + char *toencode = new char[toencodeLen + 1]; + if(toencode == NULL){ + authReq = ""; + return false; + } + char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; + if(encoded == NULL){ + authReq = ""; + delete[] toencode; + return false; + } + sprintf(toencode, "%s:%s", username, password); + if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) { + authReq = ""; + delete[] toencode; + delete[] encoded; + return true; + } + delete[] toencode; + delete[] encoded; + } + else if(authReq.startsWith(F("Digest"))) + { + authReq = authReq.substring(7); + String _username = _extractParam(authReq,F("username=\""),'\"'); + if(!_username.length() || _username != String(username)) { + authReq = ""; + return false; + } + // extracting required parameters for RFC 2069 simpler Digest + String _realm = _extractParam(authReq, F("realm=\""),'\"'); + String _nonce = _extractParam(authReq, F("nonce=\""),'\"'); + String _uri = _extractParam(authReq, F("uri=\""),'\"'); + String _resp = _extractParam(authReq, F("response=\""),'\"'); + String _opaque = _extractParam(authReq, F("opaque=\""),'\"'); + + if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_resp.length()) || (!_opaque.length())) { + authReq = ""; + return false; + } + if((_opaque != this->getSessionKey("opaque")) || (_nonce != this->getSessionKey("nonce")) || (_realm != this->getSessionKey("realm"))) + { + authReq = ""; + return false; + } + // parameters for the RFC 2617 newer Digest + String _nc,_cnonce; + if(authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) { + _nc = _extractParam(authReq, F("nc="), ','); + _cnonce = _extractParam(authReq, F("cnonce=\""),'\"'); + } + + String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password)); + //ESP_LOGD(PH_TAG, "Hash of user:realm:pass=%s", _H1.c_str()); + + String _H2 = ""; + if(_method == HTTP_GET){ + _H2 = md5str(String(F("GET:")) + _uri); + }else if(_method == HTTP_POST){ + _H2 = md5str(String(F("POST:")) + _uri); + }else if(_method == HTTP_PUT){ + _H2 = md5str(String(F("PUT:")) + _uri); + }else if(_method == HTTP_DELETE){ + _H2 = md5str(String(F("DELETE:")) + _uri); + }else{ + _H2 = md5str(String(F("GET:")) + _uri); + } + //ESP_LOGD(PH_TAG, "Hash of GET:uri=%s", _H2.c_str()); + + String _responsecheck = ""; + if(authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) { + _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2); + } else { + _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2); + } + + //ESP_LOGD(PH_TAG, "The Proper response=%s", _responsecheck.c_str()); + if(_resp == _responsecheck){ + authReq = ""; + return true; + } + } + authReq = ""; + } + return false; +} + +const String PsychicRequest::_extractParam(const String& authReq, const String& param, const char delimit) +{ + int _begin = authReq.indexOf(param); + if (_begin == -1) + return ""; + return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length())); +} + +const String PsychicRequest::_getRandomHexString() +{ + char buffer[33]; // buffer to hold 32 Hex Digit + /0 + int i; + for(i = 0; i < 4; i++) { + sprintf (buffer + (i*8), "%08lx", (unsigned long int)esp_random()); + } + return String(buffer); +} + +esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, const char* realm, const char* authFailMsg) +{ + //what is thy realm, sire? + if(!strcmp(realm, "")) + this->setSessionKey("realm", "Login Required"); + else + this->setSessionKey("realm", realm); + + PsychicResponse response(this); + String authStr; + + //what kind of auth? + if(mode == BASIC_AUTH) + { + authStr = "Basic realm=\"" + this->getSessionKey("realm") + "\""; + response.addHeader("WWW-Authenticate", authStr.c_str()); + } + else + { + //only make new ones if we havent sent them yet + if (this->getSessionKey("nonce").isEmpty()) + this->setSessionKey("nonce", _getRandomHexString()); + if (this->getSessionKey("opaque").isEmpty()) + this->setSessionKey("opaque", _getRandomHexString()); + + authStr = "Digest realm=\"" + this->getSessionKey("realm") + "\", qop=\"auth\", nonce=\"" + this->getSessionKey("nonce") + "\", opaque=\"" + this->getSessionKey("opaque") + "\""; + response.addHeader("WWW-Authenticate", authStr.c_str()); + } + + response.setCode(401); + response.setContentType("text/html"); + response.setContent(authStr.c_str()); + return response.send(); +} + +esp_err_t PsychicRequest::reply(int code) +{ + PsychicResponse response(this); + + response.setCode(code); + response.setContentType("text/plain"); + response.setContent(http_status_reason(code)); + + return response.send(); +} + +esp_err_t PsychicRequest::reply(const char *content) +{ + PsychicResponse response(this); + + response.setCode(200); + response.setContentType("text/html"); + response.setContent(content); + + return response.send(); +} + +esp_err_t PsychicRequest::reply(int code, const char *contentType, const char *content) +{ + PsychicResponse response(this); + + response.setCode(code); + response.setContentType(contentType); + response.setContent(content); + + return response.send(); +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicRequest.h b/lib/PsychicHttp/src/PsychicRequest.h new file mode 100644 index 0000000..fe48a1b --- /dev/null +++ b/lib/PsychicHttp/src/PsychicRequest.h @@ -0,0 +1,98 @@ +#ifndef PsychicRequest_h +#define PsychicRequest_h + +#include "PsychicCore.h" +#include "PsychicHttpServer.h" +#include "PsychicClient.h" +#include "PsychicWebParameter.h" +#include "PsychicResponse.h" + +typedef std::map SessionData; + +enum Disposition { NONE, INLINE, ATTACHMENT, FORM_DATA}; + +struct ContentDisposition { + Disposition disposition; + String filename; + String name; +}; + +class PsychicRequest { + friend PsychicHttpServer; + + protected: + PsychicHttpServer *_server; + httpd_req_t *_req; + SessionData *_session; + PsychicClient *_client; + + http_method _method; + String _uri; + String _query; + String _body; + + std::list _params; + + void _addParams(const String& params, bool post); + void _parseGETParams(); + void _parsePOSTParams(); + + const String _extractParam(const String& authReq, const String& param, const char delimit); + const String _getRandomHexString(); + + public: + PsychicRequest(PsychicHttpServer *server, httpd_req_t *req); + virtual ~PsychicRequest(); + + void *_tempObject; + + PsychicHttpServer * server(); + httpd_req_t * request(); + virtual PsychicClient * client(); + + bool isMultipart(); + esp_err_t loadBody(); + + const String header(const char *name); + bool hasHeader(const char *name); + + static void freeSession(void *ctx); + bool hasSessionKey(const String& key); + const String getSessionKey(const String& key); + void setSessionKey(const String& key, const String& value); + + bool hasCookie(const char * key); + const String getCookie(const char * key); + + http_method method(); // returns the HTTP method used as enum value (eg. HTTP_GET) + const String methodStr(); // returns the HTTP method used as a string (eg. "GET") + const String path(); // returns the request path (eg /page?foo=bar returns "/page") + const String& uri(); // returns the full request uri (eg /page?foo=bar) + const String& query(); // returns the request query data (eg /page?foo=bar returns "foo=bar") + const String host(); // returns the requested host (request to http://psychic.local/foo will return "psychic.local") + const String contentType(); // returns the Content-Type header value + size_t contentLength(); // returns the Content-Length header value + const String& body(); // returns the body of the request + const ContentDisposition getContentDisposition(); + + const String& queryString() { return query(); } //compatability function. same as query() + const String& url() { return uri(); } //compatability function. same as uri() + + void loadParams(); + PsychicWebParameter * addParam(PsychicWebParameter *param); + PsychicWebParameter * addParam(const String &name, const String &value, bool decode = true, bool post = false); + bool hasParam(const char *key); + PsychicWebParameter * getParam(const char *name); + + const String getFilename(); + + bool authenticate(const char * username, const char * password); + esp_err_t requestAuthentication(HTTPAuthMethod mode, const char* realm, const char* authFailMsg); + + esp_err_t redirect(const char *url); + esp_err_t reply(int code); + esp_err_t reply(const char *content); + esp_err_t reply(int code, const char *contentType, const char *content); +}; + +#endif // PsychicRequest_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicResponse.cpp b/lib/PsychicHttp/src/PsychicResponse.cpp new file mode 100644 index 0000000..5046441 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicResponse.cpp @@ -0,0 +1,162 @@ +#include "PsychicResponse.h" +#include "PsychicRequest.h" +#include + +PsychicResponse::PsychicResponse(PsychicRequest *request) : + _request(request), + _code(200), + _status(""), + _contentLength(0), + _body("") +{ +} + +PsychicResponse::~PsychicResponse() +{ + //clean up our header variables. we have to do this on desctruct since httpd_resp_send doesn't store copies + for (HTTPHeader header : _headers) + { + free(header.field); + free(header.value); + } + _headers.clear(); +} + +void PsychicResponse::addHeader(const char *field, const char *value) +{ + //these get freed after send by the destructor + HTTPHeader header; + header.field =(char *)malloc(strlen(field)+1); + header.value = (char *)malloc(strlen(value)+1); + + strlcpy(header.field, field, strlen(field)+1); + strlcpy(header.value, value, strlen(value)+1); + + _headers.push_back(header); +} + +void PsychicResponse::setCookie(const char *name, const char *value, unsigned long secondsFromNow, const char *extras) +{ + time_t now = time(nullptr); + + String output; + output = urlEncode(name) + "=" + urlEncode(value); + + //if current time isn't modern, default to using max age + if (now < 1700000000) + output += "; Max-Age=" + String(secondsFromNow); + //otherwise, set an expiration date + else + { + time_t expirationTimestamp = now + secondsFromNow; + + // Convert the expiration timestamp to a formatted string for the "expires" attribute + struct tm* tmInfo = gmtime(&expirationTimestamp); + char expires[30]; + strftime(expires, sizeof(expires), "%a, %d %b %Y %H:%M:%S GMT", tmInfo); + output += "; Expires=" + String(expires); + } + + //did we get any extras? + if (strlen(extras)) + output += "; " + String(extras); + + //okay, add it in. + addHeader("Set-Cookie", output.c_str()); +} + +void PsychicResponse::setCode(int code) +{ + _code = code; +} + +void PsychicResponse::setContentType(const char *contentType) +{ + httpd_resp_set_type(_request->request(), contentType); +} + +void PsychicResponse::setContent(const char *content) +{ + _body = content; + setContentLength(strlen(content)); +} + +void PsychicResponse::setContent(const uint8_t *content, size_t len) +{ + _body = (char *)content; + setContentLength(len); +} + +const char * PsychicResponse::getContent() +{ + return _body; +} + +size_t PsychicResponse::getContentLength() +{ + return _contentLength; +} + +esp_err_t PsychicResponse::send() +{ + //esp-idf makes you set the whole status. + sprintf(_status, "%u %s", _code, http_status_reason(_code)); + httpd_resp_set_status(_request->request(), _status); + + //our headers too + this->sendHeaders(); + + //now send it off + esp_err_t err = httpd_resp_send(_request->request(), getContent(), getContentLength()); + + //did something happen? + if (err != ESP_OK) + ESP_LOGE(PH_TAG, "Send response failed (%s)", esp_err_to_name(err)); + + return err; +} + +void PsychicResponse::sendHeaders() +{ + //get our global headers out of the way first + for (HTTPHeader header : DefaultHeaders::Instance().getHeaders()) + httpd_resp_set_hdr(_request->request(), header.field, header.value); + + //now do our individual headers + for (HTTPHeader header : _headers) + httpd_resp_set_hdr(this->_request->request(), header.field, header.value); + + // DO NOT RELEASE HEADERS HERE... released in the PsychicResponse destructor after they have been sent. + // httpd_resp_set_hdr just passes on the pointer, but its needed after this call. + // clean up our header variables after send + // for (HTTPHeader header : _headers) + // { + // free(header.field); + // free(header.value); + // } + // _headers.clear(); +} + +esp_err_t PsychicResponse::sendChunk(uint8_t *chunk, size_t chunksize) +{ + /* Send the buffer contents as HTTP response chunk */ + esp_err_t err = httpd_resp_send_chunk(this->_request->request(), (char *)chunk, chunksize); + if (err != ESP_OK) + { + ESP_LOGE(PH_TAG, "File sending failed (%s)", esp_err_to_name(err)); + + /* Abort sending file */ + httpd_resp_sendstr_chunk(this->_request->request(), NULL); + + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file"); + } + + return err; +} + +esp_err_t PsychicResponse::finishChunking() +{ + /* Respond with an empty chunk to signal HTTP response completion */ + return httpd_resp_send_chunk(this->_request->request(), NULL, 0); +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicResponse.h b/lib/PsychicHttp/src/PsychicResponse.h new file mode 100644 index 0000000..a36de65 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicResponse.h @@ -0,0 +1,46 @@ +#ifndef PsychicResponse_h +#define PsychicResponse_h + +#include "PsychicCore.h" +#include "time.h" + +class PsychicRequest; + +class PsychicResponse +{ + protected: + PsychicRequest *_request; + + int _code; + char _status[60]; + std::list _headers; + int64_t _contentLength; + const char * _body; + + public: + PsychicResponse(PsychicRequest *request); + virtual ~PsychicResponse(); + + void setCode(int code); + + void setContentType(const char *contentType); + void setContentLength(int64_t contentLength) { _contentLength = contentLength; } + int64_t getContentLength(int64_t contentLength) { return _contentLength; } + + void addHeader(const char *field, const char *value); + + void setCookie(const char *key, const char *value, unsigned long max_age = 60*60*24*30, const char *extras = ""); + + void setContent(const char *content); + void setContent(const uint8_t *content, size_t len); + + const char * getContent(); + size_t getContentLength(); + + virtual esp_err_t send(); + void sendHeaders(); + esp_err_t sendChunk(uint8_t *chunk, size_t chunksize); + esp_err_t finishChunking(); +}; + +#endif // PsychicResponse_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicStaticFileHander.cpp b/lib/PsychicHttp/src/PsychicStaticFileHander.cpp new file mode 100644 index 0000000..54fafc4 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicStaticFileHander.cpp @@ -0,0 +1,181 @@ +#include "PsychicStaticFileHandler.h" + +/*************************************/ +/* PsychicStaticFileHandler */ +/*************************************/ + +PsychicStaticFileHandler::PsychicStaticFileHandler(const char* uri, FS& fs, const char* path, const char* cache_control) + : _fs(fs), _uri(uri), _path(path), _default_file("index.html"), _cache_control(cache_control), _last_modified("") +{ + // Ensure leading '/' + if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri; + if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path; + + // If path ends with '/' we assume a hint that this is a directory to improve performance. + // However - if it does not end with '/' we, can't assume a file, path can still be a directory. + _isDir = _path[_path.length()-1] == '/'; + + // Remove the trailing '/' so we can handle default file + // Notice that root will be "" not "/" + if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1); + if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1); + + // Reset stats + _gzipFirst = false; + _gzipStats = 0xF8; +} + +PsychicStaticFileHandler& PsychicStaticFileHandler::setIsDir(bool isDir){ + _isDir = isDir; + return *this; +} + +PsychicStaticFileHandler& PsychicStaticFileHandler::setDefaultFile(const char* filename){ + _default_file = String(filename); + return *this; +} + +PsychicStaticFileHandler& PsychicStaticFileHandler::setCacheControl(const char* cache_control){ + _cache_control = String(cache_control); + return *this; +} + +PsychicStaticFileHandler& PsychicStaticFileHandler::setLastModified(const char* last_modified){ + _last_modified = String(last_modified); + return *this; +} + +PsychicStaticFileHandler& PsychicStaticFileHandler::setLastModified(struct tm* last_modified){ + char result[30]; + strftime (result,30,"%a, %d %b %Y %H:%M:%S %Z", last_modified); + return setLastModified((const char *)result); +} + +bool PsychicStaticFileHandler::canHandle(PsychicRequest *request) +{ + if(request->method() != HTTP_GET || !request->uri().startsWith(_uri) ) + return false; + + if (_getFile(request)) + return true; + + return false; +} + +bool PsychicStaticFileHandler::_getFile(PsychicRequest *request) +{ + // Remove the found uri + String path = request->uri().substring(_uri.length()); + + // We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/' + bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/'); + + path = _path + path; + + // Do we have a file or .gz file + if (!canSkipFileCheck && _fileExists(path)) + return true; + + // Can't handle if not default file + if (_default_file.length() == 0) + return false; + + // Try to add default file, ensure there is a trailing '/' ot the path. + if (path.length() == 0 || path[path.length()-1] != '/') + path += "/"; + path += _default_file; + + return _fileExists(path); +} + +#define FILE_IS_REAL(f) (f == true && !f.isDirectory()) + +bool PsychicStaticFileHandler::_fileExists(const String& path) +{ + bool fileFound = false; + bool gzipFound = false; + + String gzip = path + ".gz"; + + if (_gzipFirst) { + _file = _fs.open(gzip, "r"); + gzipFound = FILE_IS_REAL(_file); + if (!gzipFound){ + _file = _fs.open(path, "r"); + fileFound = FILE_IS_REAL(_file); + } + } else { + _file = _fs.open(path, "r"); + fileFound = FILE_IS_REAL(_file); + if (!fileFound){ + _file = _fs.open(gzip, "r"); + gzipFound = FILE_IS_REAL(_file); + } + } + + bool found = fileFound || gzipFound; + + if (found) + { + _filename = path; + + // Calculate gzip statistic + _gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); + if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip + else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip + else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first + } + + return found; +} + +uint8_t PsychicStaticFileHandler::_countBits(const uint8_t value) const +{ + uint8_t w = value; + uint8_t n; + for (n=0; w!=0; n++) w&=w-1; + return n; +} + +esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest *request) +{ + if (_file == true) + { + //is it not modified? + String etag = String(_file.size()); + if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) + { + _file.close(); + request->reply(304); // Not modified + } + //does our Etag match? + else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) + { + _file.close(); + + PsychicResponse response(request); + response.addHeader("Cache-Control", _cache_control.c_str()); + response.addHeader("ETag", etag.c_str()); + response.setCode(304); + response.send(); + } + //nope, send them the full file. + else + { + PsychicFileResponse response(request, _fs, _filename); + + if (_last_modified.length()) + response.addHeader("Last-Modified", _last_modified.c_str()); + if (_cache_control.length()) { + response.addHeader("Cache-Control", _cache_control.c_str()); + response.addHeader("ETag", etag.c_str()); + } + + return response.send(); + } + } else { + return request->reply(404); + } + + return ESP_OK; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicStaticFileHandler.h b/lib/PsychicHttp/src/PsychicStaticFileHandler.h new file mode 100644 index 0000000..f757111 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicStaticFileHandler.h @@ -0,0 +1,41 @@ +#ifndef PsychicStaticFileHandler_h +#define PsychicStaticFileHandler_h + +#include "PsychicCore.h" +#include "PsychicWebHandler.h" +#include "PsychicRequest.h" +#include "PsychicResponse.h" +#include "PsychicFileResponse.h" + +class PsychicStaticFileHandler : public PsychicWebHandler { + using File = fs::File; + using FS = fs::FS; + private: + bool _getFile(PsychicRequest *request); + bool _fileExists(const String& path); + uint8_t _countBits(const uint8_t value) const; + protected: + FS _fs; + File _file; + String _filename; + String _uri; + String _path; + String _default_file; + String _cache_control; + String _last_modified; + bool _isDir; + bool _gzipFirst; + uint8_t _gzipStats; + public: + PsychicStaticFileHandler(const char* uri, FS& fs, const char* path, const char* cache_control); + bool canHandle(PsychicRequest *request) override; + esp_err_t handleRequest(PsychicRequest *request) override; + PsychicStaticFileHandler& setIsDir(bool isDir); + PsychicStaticFileHandler& setDefaultFile(const char* filename); + PsychicStaticFileHandler& setCacheControl(const char* cache_control); + PsychicStaticFileHandler& setLastModified(const char* last_modified); + PsychicStaticFileHandler& setLastModified(struct tm* last_modified); + //PsychicStaticFileHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} +}; + +#endif /* PsychicHttp_h */ \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicStreamResponse.cpp b/lib/PsychicHttp/src/PsychicStreamResponse.cpp new file mode 100644 index 0000000..5132104 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicStreamResponse.cpp @@ -0,0 +1,94 @@ +#include "PsychicStreamResponse.h" +#include "PsychicResponse.h" +#include "PsychicRequest.h" + +PsychicStreamResponse::PsychicStreamResponse(PsychicRequest *request, const String& contentType) + : PsychicResponse(request), _buffer(NULL) { + + setContentType(contentType.c_str()); + addHeader("Content-Disposition", "inline"); +} + + +PsychicStreamResponse::PsychicStreamResponse(PsychicRequest *request, const String& contentType, const String& name) + : PsychicResponse(request), _buffer(NULL) { + + setContentType(contentType.c_str()); + + char buf[26+name.length()]; + snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", name.c_str()); + addHeader("Content-Disposition", buf); +} + + +PsychicStreamResponse::~PsychicStreamResponse() +{ + endSend(); +} + + +esp_err_t PsychicStreamResponse::beginSend() +{ + if(_buffer) + return ESP_OK; + + //Buffer to hold ChunkPrinter and stream buffer. Using placement new will keep us at a single allocation. + _buffer = (uint8_t*)malloc(STREAM_CHUNK_SIZE + sizeof(ChunkPrinter)); + + if(!_buffer) + { + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); + return ESP_FAIL; + } + + _printer = new (_buffer) ChunkPrinter(this, _buffer + sizeof(ChunkPrinter), STREAM_CHUNK_SIZE); + + sendHeaders(); + return ESP_OK; +} + + +esp_err_t PsychicStreamResponse::endSend() +{ + esp_err_t err = ESP_OK; + + if(!_buffer) + err = ESP_FAIL; + else + { + _printer->~ChunkPrinter(); //flushed on destruct + err = finishChunking(); + free(_buffer); + _buffer = NULL; + } + return err; +} + + +void PsychicStreamResponse::flush() +{ + if(_buffer) + _printer->flush(); +} + + +size_t PsychicStreamResponse::write(uint8_t data) +{ + return _buffer ? _printer->write(data) : 0; +} + + +size_t PsychicStreamResponse::write(const uint8_t *buffer, size_t size) +{ + return _buffer ? _printer->write(buffer, size) : 0; +} + + +size_t PsychicStreamResponse::copyFrom(Stream &stream) +{ + if(_buffer) + return _printer->copyFrom(stream); + + return 0; +} diff --git a/lib/PsychicHttp/src/PsychicStreamResponse.h b/lib/PsychicHttp/src/PsychicStreamResponse.h new file mode 100644 index 0000000..888ec9c --- /dev/null +++ b/lib/PsychicHttp/src/PsychicStreamResponse.h @@ -0,0 +1,35 @@ +#ifndef PsychicStreamResponse_h +#define PsychicStreamResponse_h + +#include "PsychicCore.h" +#include "PsychicResponse.h" +#include "ChunkPrinter.h" + +class PsychicRequest; + +class PsychicStreamResponse : public PsychicResponse, public Print +{ + private: + ChunkPrinter *_printer; + uint8_t *_buffer; + public: + + PsychicStreamResponse(PsychicRequest *request, const String& contentType); + PsychicStreamResponse(PsychicRequest *request, const String& contentType, const String& name); //Download + + ~PsychicStreamResponse(); + + esp_err_t beginSend(); + esp_err_t endSend(); + + void flush() override; + + size_t write(uint8_t data) override; + size_t write(const uint8_t *buffer, size_t size) override; + + size_t copyFrom(Stream &stream); + + using Print::write; +}; + +#endif // PsychicStreamResponse_h diff --git a/lib/PsychicHttp/src/PsychicUploadHandler.cpp b/lib/PsychicHttp/src/PsychicUploadHandler.cpp new file mode 100644 index 0000000..d8a55e3 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicUploadHandler.cpp @@ -0,0 +1,395 @@ +#include "PsychicUploadHandler.h" + +PsychicUploadHandler::PsychicUploadHandler() : + PsychicWebHandler() + , _temp() + , _parsedLength(0) + , _multiParseState(EXPECT_BOUNDARY) + , _boundaryPosition(0) + , _itemStartIndex(0) + , _itemSize(0) + , _itemName() + , _itemFilename() + , _itemType() + , _itemValue() + , _itemBuffer(0) + , _itemBufferIndex(0) + , _itemIsFile(false) + {} +PsychicUploadHandler::~PsychicUploadHandler() {} + +bool PsychicUploadHandler::canHandle(PsychicRequest *request) { + return true; +} + +esp_err_t PsychicUploadHandler::handleRequest(PsychicRequest *request) +{ + esp_err_t err = ESP_OK; + + //save it for later (multipart) + _request = request; + _parsedLength = 0; + /* File cannot be larger than a limit */ + if (request->contentLength() > request->server()->maxUploadSize) + { + ESP_LOGE(PH_TAG, "File too large : %d bytes", request->contentLength()); + + /* Respond with 400 Bad Request */ + char error[50]; + sprintf(error, "File size must be less than %lu bytes!", request->server()->maxUploadSize); + httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error); + + /* Return failure to close underlying connection else the incoming file content will keep the socket busy */ + return ESP_FAIL; + } + + //we might want to access some of these params + request->loadParams(); + + //TODO: support for the 100 header. not sure if we can do it. + // if (request->header("Expect").equals("100-continue")) + // { + // char response[] = "100 Continue"; + // httpd_socket_send(self->server, httpd_req_to_sockfd(req), response, strlen(response), 0); + // } + + //2 types of upload requests + if (request->isMultipart()) + err = _multipartUploadHandler(request); + else + err = _basicUploadHandler(request); + + //we can also call onRequest for some final processing and response + if (err == ESP_OK) + { + if (_requestCallback != NULL) + err = _requestCallback(request); + else + err = request->reply("Upload Successful."); + } + else + request->reply(500, "text/html", "Error processing upload."); + + return err; +} + +esp_err_t PsychicUploadHandler::_basicUploadHandler(PsychicRequest *request) +{ + esp_err_t err = ESP_OK; + + String filename = request->getFilename(); + + /* Retrieve the pointer to scratch buffer for temporary storage */ + char *buf = (char *)malloc(FILE_CHUNK_SIZE); + int received; + unsigned long index = 0; + + /* Content length of the request gives the size of the file being uploaded */ + int remaining = request->contentLength(); + + while (remaining > 0) + { + #ifdef ENABLE_ASYNC + httpd_sess_update_lru_counter(request->server()->server, request->client()->socket()); + #endif + + //ESP_LOGD(PH_TAG, "Remaining size : %d", remaining); + + /* Receive the file part by part into a buffer */ + if ((received = httpd_req_recv(request->request(), buf, min(remaining, FILE_CHUNK_SIZE))) <= 0) + { + /* Retry if timeout occurred */ + if (received == HTTPD_SOCK_ERR_TIMEOUT) + continue; + //bail if we got an error + else if (received == HTTPD_SOCK_ERR_FAIL) + { + ESP_LOGE(PH_TAG, "Socket error"); + err = ESP_FAIL; + break; + } + } + + //call our upload callback here. + if (_uploadCallback != NULL) + { + err = _uploadCallback(request, filename, index, (uint8_t *)buf, received, (remaining - received == 0)); + if (err != ESP_OK) + break; + } + else + { + ESP_LOGE(PH_TAG, "No upload callback specified!"); + err = ESP_FAIL; + break; + } + + /* Keep track of remaining size of the file left to be uploaded */ + remaining -= received; + index += received; + } + + //dont forget to free our buffer + free(buf); + + return err; +} + +esp_err_t PsychicUploadHandler::_multipartUploadHandler(PsychicRequest *request) +{ + esp_err_t err = ESP_OK; + + String value = request->header("Content-Type"); + if (value.startsWith("multipart/")){ + _boundary = value.substring(value.indexOf('=')+1); + _boundary.replace("\"",""); + } else { + ESP_LOGE(PH_TAG, "No multipart boundary found."); + return request->reply(400, "text/html", "No multipart boundary found."); + } + + char *buf = (char *)malloc(FILE_CHUNK_SIZE); + int received; + unsigned long index = 0; + + /* Content length of the request gives the size of the file being uploaded */ + int remaining = request->contentLength(); + + while (remaining > 0) + { + #ifdef ENABLE_ASYNC + httpd_sess_update_lru_counter(request->server()->server, request->client()->socket()); + #endif + + //ESP_LOGD(PH_TAG, "Remaining size : %d", remaining); + + /* Receive the file part by part into a buffer */ + if ((received = httpd_req_recv(request->request(), buf, min(remaining, FILE_CHUNK_SIZE))) <= 0) + { + /* Retry if timeout occurred */ + if (received == HTTPD_SOCK_ERR_TIMEOUT) + continue; + //bail if we got an error + else if (received == HTTPD_SOCK_ERR_FAIL) + { + ESP_LOGE(PH_TAG, "Socket error"); + err = ESP_FAIL; + break; + } + } + + //parse it 1 byte at a time. + for (int i=0; i 12 && _temp.substring(0, 12).equalsIgnoreCase("Content-Type")){ + _itemType = _temp.substring(14); + _itemIsFile = true; + } else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase("Content-Disposition")){ + _temp = _temp.substring(_temp.indexOf(';') + 2); + while(_temp.indexOf(';') > 0){ + String name = _temp.substring(0, _temp.indexOf('=')); + String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1); + if(name == "name"){ + _itemName = nameVal; + } else if(name == "filename"){ + _itemFilename = nameVal; + _itemIsFile = true; + } + _temp = _temp.substring(_temp.indexOf(';') + 2); + } + String name = _temp.substring(0, _temp.indexOf('=')); + String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1); + if(name == "name"){ + _itemName = nameVal; + } else if(name == "filename"){ + _itemFilename = nameVal; + _itemIsFile = true; + } + } + _temp = String(); + } else { + _multiParseState = WAIT_FOR_RETURN1; + //value starts from here + _itemSize = 0; + _itemStartIndex = _parsedLength; + _itemValue = String(); + if(_itemIsFile){ + if(_itemBuffer) + free(_itemBuffer); + _itemBuffer = (uint8_t*)malloc(FILE_CHUNK_SIZE); + if(_itemBuffer == NULL){ + ESP_LOGE(PH_TAG, "Multipart: Failed to allocate buffer"); + _multiParseState = PARSE_ERROR; + return; + } + _itemBufferIndex = 0; + } + } + } + } else if(_multiParseState == EXPECT_FEED1){ + if(data != '\n'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = EXPECT_DASH1; + } + } else if(_multiParseState == EXPECT_DASH1){ + if(data != '-'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = EXPECT_DASH2; + } + } else if(_multiParseState == EXPECT_DASH2){ + if(data != '-'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = BOUNDARY_OR_DATA; + _boundaryPosition = 0; + } + } else if(_multiParseState == BOUNDARY_OR_DATA){ + if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; + for(i=0; i<_boundaryPosition; i++) + itemWriteByte(_boundary.c_str()[i]); + _parseMultipartPostByte(data, last); + } else if(_boundaryPosition == _boundary.length() - 1){ + _multiParseState = DASH3_OR_RETURN2; + if(!_itemIsFile){ + _request->addParam(_itemName, _itemValue); + //_addParam(new AsyncWebParameter(_itemName, _itemValue, true)); + } else { + if(_itemSize){ + if(_uploadCallback) + _uploadCallback(_request, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true); + _itemBufferIndex = 0; + _request->addParam(new PsychicWebParameter(_itemName, _itemFilename, true, true, _itemSize)); + } + free(_itemBuffer); + _itemBuffer = NULL; + } + + } else { + _boundaryPosition++; + } + } else if(_multiParseState == DASH3_OR_RETURN2){ + if(data == '-' && (_request->contentLength() - _parsedLength - 4) != 0){ + ESP_LOGE(PH_TAG, "ERROR: The parser got to the end of the POST but is expecting more bytes!"); + _multiParseState = PARSE_ERROR; + return; + } + if(data == '\r'){ + _multiParseState = EXPECT_FEED2; + } else if(data == '-' && _request->contentLength() == (_parsedLength + 4)){ + _multiParseState = PARSING_FINISHED; + } else { + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); + _parseMultipartPostByte(data, last); + } + } else if(_multiParseState == EXPECT_FEED2){ + if(data == '\n'){ + _multiParseState = PARSE_HEADERS; + _itemIsFile = false; + } else { + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); + itemWriteByte('\r'); _parseMultipartPostByte(data, last); + } + } +} diff --git a/lib/PsychicHttp/src/PsychicUploadHandler.h b/lib/PsychicHttp/src/PsychicUploadHandler.h new file mode 100644 index 0000000..59e62b6 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicUploadHandler.h @@ -0,0 +1,68 @@ +#ifndef PsychicUploadHandler_h +#define PsychicUploadHandler_h + +#include "PsychicCore.h" +#include "PsychicHttpServer.h" +#include "PsychicRequest.h" +#include "PsychicWebHandler.h" +#include "PsychicWebParameter.h" + +//callback definitions +typedef std::function PsychicUploadCallback; + +/* +* HANDLER :: Can be attached to any endpoint or as a generic request handler. +*/ + +class PsychicUploadHandler : public PsychicWebHandler { + protected: + PsychicUploadCallback _uploadCallback; + + PsychicRequest *_request; + + String _temp; + size_t _parsedLength; + uint8_t _multiParseState; + String _boundary; + uint8_t _boundaryPosition; + size_t _itemStartIndex; + size_t _itemSize; + String _itemName; + String _itemFilename; + String _itemType; + String _itemValue; + uint8_t *_itemBuffer; + size_t _itemBufferIndex; + bool _itemIsFile; + + esp_err_t _basicUploadHandler(PsychicRequest *request); + esp_err_t _multipartUploadHandler(PsychicRequest *request); + + void _handleUploadByte(uint8_t data, bool last); + void _parseMultipartPostByte(uint8_t data, bool last); + + public: + PsychicUploadHandler(); + ~PsychicUploadHandler(); + + bool canHandle(PsychicRequest *request) override; + esp_err_t handleRequest(PsychicRequest *request) override; + + PsychicUploadHandler * onUpload(PsychicUploadCallback fn); +}; + +enum { + EXPECT_BOUNDARY, + PARSE_HEADERS, + WAIT_FOR_RETURN1, + EXPECT_FEED1, + EXPECT_DASH1, + EXPECT_DASH2, + BOUNDARY_OR_DATA, + DASH3_OR_RETURN2, + EXPECT_FEED2, + PARSING_FINISHED, + PARSE_ERROR +}; + +#endif // PsychicUploadHandler_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebHandler.cpp b/lib/PsychicHttp/src/PsychicWebHandler.cpp new file mode 100644 index 0000000..1e4997e --- /dev/null +++ b/lib/PsychicHttp/src/PsychicWebHandler.cpp @@ -0,0 +1,74 @@ +#include "PsychicWebHandler.h" + +PsychicWebHandler::PsychicWebHandler() : + PsychicHandler(), + _requestCallback(NULL), + _onOpen(NULL), + _onClose(NULL) + {} +PsychicWebHandler::~PsychicWebHandler() {} + +bool PsychicWebHandler::canHandle(PsychicRequest *request) { + return true; +} + +esp_err_t PsychicWebHandler::handleRequest(PsychicRequest *request) +{ + //lookup our client + PsychicClient *client = checkForNewClient(request->client()); + if (client->isNew) + openCallback(client); + + /* Request body cannot be larger than a limit */ + if (request->contentLength() > request->server()->maxRequestBodySize) + { + ESP_LOGE(PH_TAG, "Request body too large : %d bytes", request->contentLength()); + + /* Respond with 400 Bad Request */ + char error[60]; + sprintf(error, "Request body must be less than %lu bytes!", request->server()->maxRequestBodySize); + httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error); + + /* Return failure to close underlying connection else the incoming file content will keep the socket busy */ + return ESP_FAIL; + } + + //get our body loaded up. + esp_err_t err = request->loadBody(); + if (err != ESP_OK) + return err; + + //load our params in. + request->loadParams(); + + //okay, pass on to our callback. + if (this->_requestCallback != NULL) + err = this->_requestCallback(request); + + return err; +} + +PsychicWebHandler * PsychicWebHandler::onRequest(PsychicHttpRequestCallback fn) { + _requestCallback = fn; + return this; +} + +void PsychicWebHandler::openCallback(PsychicClient *client) { + if (_onOpen != NULL) + _onOpen(client); +} + +void PsychicWebHandler::closeCallback(PsychicClient *client) { + if (_onClose != NULL) + _onClose(getClient(client)); +} + +PsychicWebHandler * PsychicWebHandler::onOpen(PsychicClientCallback fn) { + _onOpen = fn; + return this; +} + +PsychicWebHandler * PsychicWebHandler::onClose(PsychicClientCallback fn) { + _onClose = fn; + return this; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebHandler.h b/lib/PsychicHttp/src/PsychicWebHandler.h new file mode 100644 index 0000000..07a6780 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicWebHandler.h @@ -0,0 +1,34 @@ +#ifndef PsychicWebHandler_h +#define PsychicWebHandler_h + +// #include "PsychicCore.h" +// #include "PsychicHttpServer.h" +// #include "PsychicRequest.h" +#include "PsychicHandler.h" + +/* +* HANDLER :: Can be attached to any endpoint or as a generic request handler. +*/ + +class PsychicWebHandler : public PsychicHandler { + protected: + PsychicHttpRequestCallback _requestCallback; + PsychicClientCallback _onOpen; + PsychicClientCallback _onClose; + + public: + PsychicWebHandler(); + ~PsychicWebHandler(); + + virtual bool canHandle(PsychicRequest *request) override; + virtual esp_err_t handleRequest(PsychicRequest *request) override; + PsychicWebHandler * onRequest(PsychicHttpRequestCallback fn); + + virtual void openCallback(PsychicClient *client); + virtual void closeCallback(PsychicClient *client); + + PsychicWebHandler *onOpen(PsychicClientCallback fn); + PsychicWebHandler *onClose(PsychicClientCallback fn); +}; + +#endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebParameter.h b/lib/PsychicHttp/src/PsychicWebParameter.h new file mode 100644 index 0000000..e09136b --- /dev/null +++ b/lib/PsychicHttp/src/PsychicWebParameter.h @@ -0,0 +1,25 @@ +#ifndef PsychicWebParameter_h +#define PsychicWebParameter_h + +/* + * PARAMETER :: Chainable object to hold GET/POST and FILE parameters + * */ + +class PsychicWebParameter { + private: + String _name; + String _value; + size_t _size; + bool _isForm; + bool _isFile; + + public: + PsychicWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){} + const String& name() const { return _name; } + const String& value() const { return _value; } + size_t size() const { return _size; } + bool isPost() const { return _isForm; } + bool isFile() const { return _isFile; } +}; + +#endif //PsychicWebParameter_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebSocket.cpp b/lib/PsychicHttp/src/PsychicWebSocket.cpp new file mode 100644 index 0000000..7a3c6b1 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicWebSocket.cpp @@ -0,0 +1,258 @@ +#include "PsychicWebSocket.h" + +/*************************************/ +/* PsychicWebSocketRequest */ +/*************************************/ + +PsychicWebSocketRequest::PsychicWebSocketRequest(PsychicRequest *req) : + PsychicRequest(req->server(), req->request()), + _client(req->client()) +{ +} + +PsychicWebSocketRequest::~PsychicWebSocketRequest() +{ +} + +PsychicWebSocketClient * PsychicWebSocketRequest::client() { + return &_client; +} + +esp_err_t PsychicWebSocketRequest::reply(httpd_ws_frame_t * ws_pkt) +{ + return httpd_ws_send_frame(this->_req, ws_pkt); +} + +esp_err_t PsychicWebSocketRequest::reply(httpd_ws_type_t op, const void *data, size_t len) +{ + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + + ws_pkt.payload = (uint8_t*)data; + ws_pkt.len = len; + ws_pkt.type = op; + + return this->reply(&ws_pkt); +} + +esp_err_t PsychicWebSocketRequest::reply(const char *buf) +{ + return this->reply(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); +} + +/*************************************/ +/* PsychicWebSocketClient */ +/*************************************/ + +PsychicWebSocketClient::PsychicWebSocketClient(PsychicClient *client) + : PsychicClient(client->server(), client->socket()) +{ +} + +PsychicWebSocketClient::~PsychicWebSocketClient() { +} + +esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_frame_t * ws_pkt) +{ + return httpd_ws_send_frame_async(this->server(), this->socket(), ws_pkt); +} + +esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_type_t op, const void *data, size_t len) +{ + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + + ws_pkt.payload = (uint8_t*)data; + ws_pkt.len = len; + ws_pkt.type = op; + + return this->sendMessage(&ws_pkt); +} + +esp_err_t PsychicWebSocketClient::sendMessage(const char *buf) +{ + return this->sendMessage(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); +} + +PsychicWebSocketHandler::PsychicWebSocketHandler() : + PsychicHandler(), + _onOpen(NULL), + _onFrame(NULL), + _onClose(NULL) + { + } + +PsychicWebSocketHandler::~PsychicWebSocketHandler() { +} + +PsychicWebSocketClient * PsychicWebSocketHandler::getClient(int socket) +{ + PsychicClient *client = PsychicHandler::getClient(socket); + if (client == NULL) + return NULL; + + if (client->_friend == NULL) + { + return NULL; + } + + return (PsychicWebSocketClient *)client->_friend; +} + +PsychicWebSocketClient * PsychicWebSocketHandler::getClient(PsychicClient *client) { + return getClient(client->socket()); +} + +void PsychicWebSocketHandler::addClient(PsychicClient *client) { + client->_friend = new PsychicWebSocketClient(client); + PsychicHandler::addClient(client); +} + +void PsychicWebSocketHandler::removeClient(PsychicClient *client) { + PsychicHandler::removeClient(client); + delete (PsychicWebSocketClient*)client->_friend; + client->_friend = NULL; +} + +void PsychicWebSocketHandler::openCallback(PsychicClient *client) { + PsychicWebSocketClient *buddy = getClient(client); + if (buddy == NULL) + { + return; + } + + if (_onOpen != NULL) + _onOpen(getClient(buddy)); +} + +void PsychicWebSocketHandler::closeCallback(PsychicClient *client) { + PsychicWebSocketClient *buddy = getClient(client); + if (buddy == NULL) + { + return; + } + + if (_onClose != NULL) + _onClose(getClient(buddy)); +} + +bool PsychicWebSocketHandler::isWebSocket() { return true; } + +esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest *request) +{ + //lookup our client + PsychicClient *client = checkForNewClient(request->client()); + + // beginning of the ws URI handler and our onConnect hook + if (request->method() == HTTP_GET) + { + if (client->isNew) + openCallback(client); + + return ESP_OK; + } + + //prep our request + PsychicWebSocketRequest wsRequest(request); + + //init our memory for storing the packet + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + ws_pkt.type = HTTPD_WS_TYPE_TEXT; + uint8_t *buf = NULL; + + /* Set max_len = 0 to get the frame len */ + esp_err_t ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, 0); + if (ret != ESP_OK) { + ESP_LOGE(PH_TAG, "httpd_ws_recv_frame failed to get frame len with %s", esp_err_to_name(ret)); + return ret; + } + + //okay, now try to load the packet + //ESP_LOGD(PH_TAG, "frame len is %d", ws_pkt.len); + if (ws_pkt.len) { + /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */ + buf = (uint8_t*) calloc(1, ws_pkt.len + 1); + if (buf == NULL) { + ESP_LOGE(PH_TAG, "Failed to calloc memory for buf"); + return ESP_ERR_NO_MEM; + } + ws_pkt.payload = buf; + /* Set max_len = ws_pkt.len to get the frame payload */ + ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, ws_pkt.len); + if (ret != ESP_OK) { + ESP_LOGE(PH_TAG, "httpd_ws_recv_frame failed with %s", esp_err_to_name(ret)); + free(buf); + return ret; + } + //ESP_LOGD(PH_TAG, "Got packet with message: %s", ws_pkt.payload); + } + + // Text messages are our payload. + if (ws_pkt.type == HTTPD_WS_TYPE_TEXT || ws_pkt.type == HTTPD_WS_TYPE_BINARY) + { + if (this->_onFrame != NULL) + ret = this->_onFrame(&wsRequest, &ws_pkt); + } + + //logging housekeeping + if (ret != ESP_OK) + ESP_LOGE(PH_TAG, "httpd_ws_send_frame failed with %s", esp_err_to_name(ret)); + // ESP_LOGD(PH_TAG, "ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d", + // request->server(), + // httpd_req_to_sockfd(request->request()), + // httpd_ws_get_fd_info(request->server()->server, httpd_req_to_sockfd(request->request()))); + + //dont forget to release our buffer memory + free(buf); + + return ret; +} + +PsychicWebSocketHandler * PsychicWebSocketHandler::onOpen(PsychicWebSocketClientCallback fn) { + _onOpen = fn; + return this; +} + +PsychicWebSocketHandler * PsychicWebSocketHandler::onFrame(PsychicWebSocketFrameCallback fn) { + _onFrame = fn; + return this; +} + +PsychicWebSocketHandler * PsychicWebSocketHandler::onClose(PsychicWebSocketClientCallback fn) { + _onClose = fn; + return this; +} + +void PsychicWebSocketHandler::sendAll(httpd_ws_frame_t * ws_pkt) +{ + for (PsychicClient *client : _clients) + { + //ESP_LOGD(PH_TAG, "Active client (fd=%d) -> sending async message", client->socket()); + + if (client->_friend == NULL) + { + return; + } + + if (((PsychicWebSocketClient*)client->_friend)->sendMessage(ws_pkt) != ESP_OK) + break; + } +} + +void PsychicWebSocketHandler::sendAll(httpd_ws_type_t op, const void *data, size_t len) +{ + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + + ws_pkt.payload = (uint8_t*)data; + ws_pkt.len = len; + ws_pkt.type = op; + + this->sendAll(&ws_pkt); +} + +void PsychicWebSocketHandler::sendAll(const char *buf) +{ + this->sendAll(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); +} diff --git a/lib/PsychicHttp/src/PsychicWebSocket.h b/lib/PsychicHttp/src/PsychicWebSocket.h new file mode 100644 index 0000000..01917de --- /dev/null +++ b/lib/PsychicHttp/src/PsychicWebSocket.h @@ -0,0 +1,70 @@ +#ifndef PsychicWebSocket_h +#define PsychicWebSocket_h + +#include "PsychicCore.h" +#include "PsychicRequest.h" + +class PsychicWebSocketRequest; +class PsychicWebSocketClient; + +//callback function definitions +typedef std::function PsychicWebSocketClientCallback; +typedef std::function PsychicWebSocketFrameCallback; + +class PsychicWebSocketClient : public PsychicClient +{ + public: + PsychicWebSocketClient(PsychicClient *client); + ~PsychicWebSocketClient(); + + esp_err_t sendMessage(httpd_ws_frame_t * ws_pkt); + esp_err_t sendMessage(httpd_ws_type_t op, const void *data, size_t len); + esp_err_t sendMessage(const char *buf); +}; + +class PsychicWebSocketRequest : public PsychicRequest +{ + private: + PsychicWebSocketClient _client; + + public: + PsychicWebSocketRequest(PsychicRequest *req); + virtual ~PsychicWebSocketRequest(); + + PsychicWebSocketClient * client() override; + + esp_err_t reply(httpd_ws_frame_t * ws_pkt); + esp_err_t reply(httpd_ws_type_t op, const void *data, size_t len); + esp_err_t reply(const char *buf); +}; + +class PsychicWebSocketHandler : public PsychicHandler { + protected: + PsychicWebSocketClientCallback _onOpen; + PsychicWebSocketFrameCallback _onFrame; + PsychicWebSocketClientCallback _onClose; + + public: + PsychicWebSocketHandler(); + ~PsychicWebSocketHandler(); + + PsychicWebSocketClient * getClient(int socket) override; + PsychicWebSocketClient * getClient(PsychicClient *client) override; + void addClient(PsychicClient *client) override; + void removeClient(PsychicClient *client) override; + void openCallback(PsychicClient *client) override; + void closeCallback(PsychicClient *client) override; + + bool isWebSocket() override final; + esp_err_t handleRequest(PsychicRequest *request) override; + + PsychicWebSocketHandler *onOpen(PsychicWebSocketClientCallback fn); + PsychicWebSocketHandler *onFrame(PsychicWebSocketFrameCallback fn); + PsychicWebSocketHandler *onClose(PsychicWebSocketClientCallback fn); + + void sendAll(httpd_ws_frame_t * ws_pkt); + void sendAll(httpd_ws_type_t op, const void *data, size_t len); + void sendAll(const char *buf); +}; + +#endif // PsychicWebSocket_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/TemplatePrinter.cpp b/lib/PsychicHttp/src/TemplatePrinter.cpp new file mode 100644 index 0000000..5c05904 --- /dev/null +++ b/lib/PsychicHttp/src/TemplatePrinter.cpp @@ -0,0 +1,90 @@ + /************************************************************ + + TemplatePrinter Class + + A basic templating engine for a stream of text. + This wraps the Arduino Print interface and writes to any + Print interface. + + Written by Christopher Andrews (https://github.com/Chris--A) + + ************************************************************/ + +#include "TemplatePrinter.h" + +void TemplatePrinter::resetParam(bool flush){ + if(flush && _inParam){ + _stream.write(_delimiter); + + if(_paramPos) + _stream.print(_paramBuffer); + } + + memset(_paramBuffer, 0, sizeof(_paramBuffer)); + _paramPos = 0; + _inParam = false; +} + + +void TemplatePrinter::flush(){ + resetParam(true); + _stream.flush(); +} + +size_t TemplatePrinter::write(uint8_t data){ + + if(data == _delimiter){ + + // End of parameter, send to callback + if(_inParam){ + + // On false, return the parameter place holder as is: not a parameter + // Bug fix: ignore parameters that are zero length. + if(!_paramPos || !_cb(_stream, _paramBuffer)){ + resetParam(true); + _stream.write(data); + }else{ + resetParam(false); + } + + // Start collecting parameter + }else{ + _inParam = true; + } + }else{ + + // Are we collecting + if(_inParam){ + + // Is param still valid + if(isalnum(data) || data == '_'){ + + // Total param len must be 63, 1 for null. + if(_paramPos < sizeof(_paramBuffer) - 1){ + _paramBuffer[_paramPos++] = data; + + // Not a valid param + }else{ + resetParam(true); + } + }else{ + resetParam(true); + _stream.write(data); + } + + // Just output + }else{ + _stream.write(data); + } + } + return 1; +} + +size_t TemplatePrinter::copyFrom(Stream &stream){ + size_t count = 0; + + while(stream.available()) + count += this->write(stream.read()); + + return count; +} diff --git a/lib/PsychicHttp/src/TemplatePrinter.h b/lib/PsychicHttp/src/TemplatePrinter.h new file mode 100644 index 0000000..6adf14a --- /dev/null +++ b/lib/PsychicHttp/src/TemplatePrinter.h @@ -0,0 +1,51 @@ +#ifndef TemplatePrinter_h + #define TemplatePrinter_h + + #include "PsychicCore.h" + #include + + /************************************************************ + + TemplatePrinter Class + + A basic templating engine for a stream of text. + This wraps the Arduino Print interface and writes to any + Print interface. + + Written by Christopher Andrews (https://github.com/Chris--A) + + ************************************************************/ + + class TemplatePrinter; + + typedef std::function TemplateCallback; + typedef std::function TemplateSourceCallback; + + class TemplatePrinter : public Print{ + private: + bool _inParam; + char _paramBuffer[64]; + uint8_t _paramPos; + Print &_stream; + TemplateCallback _cb; + char _delimiter; + + void resetParam(bool flush); + + public: + using Print::write; + + static void start(Print &stream, TemplateCallback cb, TemplateSourceCallback entry){ + TemplatePrinter printer(stream, cb); + entry(printer); + } + + TemplatePrinter(Print &stream, TemplateCallback cb, const char delimeter = '%') : _stream(stream), _cb(cb), _delimiter(delimeter) { resetParam(false); } + ~TemplatePrinter(){ flush(); } + + void flush() override; + size_t write(uint8_t data) override; + size_t copyFrom(Stream &stream); + }; + +#endif diff --git a/lib/PsychicHttp/src/async_worker.cpp b/lib/PsychicHttp/src/async_worker.cpp new file mode 100644 index 0000000..d7310cb --- /dev/null +++ b/lib/PsychicHttp/src/async_worker.cpp @@ -0,0 +1,203 @@ +#include "async_worker.h" + +bool is_on_async_worker_thread(void) +{ + // is our handle one of the known async handles? + TaskHandle_t handle = xTaskGetCurrentTaskHandle(); + for (int i = 0; i < ASYNC_WORKER_COUNT; i++) { + if (worker_handles[i] == handle) { + return true; + } + } + return false; +} + +// Submit an HTTP req to the async worker queue +esp_err_t submit_async_req(httpd_req_t *req, httpd_req_handler_t handler) +{ + // must create a copy of the request that we own + httpd_req_t* copy = NULL; + esp_err_t err = httpd_req_async_handler_begin(req, ©); + if (err != ESP_OK) { + return err; + } + + httpd_async_req_t async_req = { + .req = copy, + .handler = handler, + }; + + // How should we handle resource exhaustion? + // In this example, we immediately respond with an + // http error if no workers are available. + int ticks = 0; + + // counting semaphore: if success, we know 1 or + // more asyncReqTaskWorkers are available. + if (xSemaphoreTake(worker_ready_count, ticks) == false) { + ESP_LOGE(PH_TAG, "No workers are available"); + httpd_req_async_handler_complete(copy); // cleanup + return ESP_FAIL; + } + + // Since worker_ready_count > 0 the queue should already have space. + // But lets wait up to 100ms just to be safe. + if (xQueueSend(async_req_queue, &async_req, pdMS_TO_TICKS(100)) == false) { + ESP_LOGE(PH_TAG, "worker queue is full"); + httpd_req_async_handler_complete(copy); // cleanup + return ESP_FAIL; + } + + return ESP_OK; +} + +void async_req_worker_task(void *p) +{ + ESP_LOGI(PH_TAG, "starting async req task worker"); + + while (true) { + + // counting semaphore - this signals that a worker + // is ready to accept work + xSemaphoreGive(worker_ready_count); + + // wait for a request + httpd_async_req_t async_req; + if (xQueueReceive(async_req_queue, &async_req, portMAX_DELAY)) { + + ESP_LOGI(PH_TAG, "invoking %s", async_req.req->uri); + + // call the handler + async_req.handler(async_req.req); + + // Inform the server that it can purge the socket used for + // this request, if needed. + if (httpd_req_async_handler_complete(async_req.req) != ESP_OK) { + ESP_LOGE(PH_TAG, "failed to complete async req"); + } + } + } + + ESP_LOGW(PH_TAG, "worker stopped"); + vTaskDelete(NULL); +} + +void start_async_req_workers(void) +{ + + // counting semaphore keeps track of available workers + worker_ready_count = xSemaphoreCreateCounting( + ASYNC_WORKER_COUNT, // Max Count + 0); // Initial Count + if (worker_ready_count == NULL) { + ESP_LOGE(PH_TAG, "Failed to create workers counting Semaphore"); + return; + } + + // create queue + async_req_queue = xQueueCreate(1, sizeof(httpd_async_req_t)); + if (async_req_queue == NULL){ + ESP_LOGE(PH_TAG, "Failed to create async_req_queue"); + vSemaphoreDelete(worker_ready_count); + return; + } + + // start worker tasks + for (int i = 0; i < ASYNC_WORKER_COUNT; i++) { + + bool success = xTaskCreate(async_req_worker_task, "async_req_worker", + ASYNC_WORKER_TASK_STACK_SIZE, // stack size + (void *)0, // argument + ASYNC_WORKER_TASK_PRIORITY, // priority + &worker_handles[i]); + + if (!success) { + ESP_LOGE(PH_TAG, "Failed to start asyncReqWorker"); + continue; + } + } +} + +/**** + * + * This code is backported from the 5.1.x branch + * +****/ + +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +/* Calculate the maximum size needed for the scratch buffer */ +#define HTTPD_SCRATCH_BUF MAX(HTTPD_MAX_REQ_HDR_LEN, HTTPD_MAX_URI_LEN) + +/** + * @brief Auxiliary data structure for use during reception and processing + * of requests and temporarily keeping responses + */ +struct httpd_req_aux { + struct sock_db *sd; /*!< Pointer to socket database */ + char scratch[HTTPD_SCRATCH_BUF + 1]; /*!< Temporary buffer for our operations (1 byte extra for null termination) */ + size_t remaining_len; /*!< Amount of data remaining to be fetched */ + char *status; /*!< HTTP response's status code */ + char *content_type; /*!< HTTP response's content type */ + bool first_chunk_sent; /*!< Used to indicate if first chunk sent */ + unsigned req_hdrs_count; /*!< Count of total headers in request packet */ + unsigned resp_hdrs_count; /*!< Count of additional headers in response packet */ + struct resp_hdr { + const char *field; + const char *value; + } *resp_hdrs; /*!< Additional headers in response packet */ + struct http_parser_url url_parse_res; /*!< URL parsing result, used for retrieving URL elements */ +#ifdef CONFIG_HTTPD_WS_SUPPORT + bool ws_handshake_detect; /*!< WebSocket handshake detection flag */ + httpd_ws_type_t ws_type; /*!< WebSocket frame type */ + bool ws_final; /*!< WebSocket FIN bit (final frame or not) */ + uint8_t mask_key[4]; /*!< WebSocket mask key for this payload */ +#endif +}; + +esp_err_t httpd_req_async_handler_begin(httpd_req_t *r, httpd_req_t **out) +{ + if (r == NULL || out == NULL) { + return ESP_ERR_INVALID_ARG; + } + + // alloc async req + httpd_req_t *async = (httpd_req_t *)malloc(sizeof(httpd_req_t)); + if (async == NULL) { + return ESP_ERR_NO_MEM; + } + memcpy((void *)async, (void *)r, sizeof(httpd_req_t)); + + // alloc async aux + async->aux = (httpd_req_aux *)malloc(sizeof(struct httpd_req_aux)); + if (async->aux == NULL) { + free(async); + return ESP_ERR_NO_MEM; + } + memcpy(async->aux, r->aux, sizeof(struct httpd_req_aux)); + + // not available in 4.4.x + // mark socket as "in use" + // struct httpd_req_aux *ra = r->aux; + //ra->sd->for_async_req = true; + + *out = async; + + return ESP_OK; +} + +esp_err_t httpd_req_async_handler_complete(httpd_req_t *r) +{ + if (r == NULL) { + return ESP_ERR_INVALID_ARG; + } + + // not available in 4.4.x + // struct httpd_req_aux *ra = (httpd_req_aux *)r->aux; + // ra->sd->for_async_req = false; + + free(r->aux); + free(r); + + return ESP_OK; +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/async_worker.h b/lib/PsychicHttp/src/async_worker.h new file mode 100644 index 0000000..e77c4a6 --- /dev/null +++ b/lib/PsychicHttp/src/async_worker.h @@ -0,0 +1,36 @@ +#ifndef async_worker_h +#define async_worker_h + +#include "PsychicCore.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#define ASYNC_WORKER_TASK_PRIORITY 5 +#define ASYNC_WORKER_TASK_STACK_SIZE (4*1024) +#define ASYNC_WORKER_COUNT 8 + +// Async requests are queued here while they wait to be processed by the workers +static QueueHandle_t async_req_queue; + +// Track the number of free workers at any given time +static SemaphoreHandle_t worker_ready_count; + +// Each worker has its own thread +static TaskHandle_t worker_handles[ASYNC_WORKER_COUNT]; + +typedef esp_err_t (*httpd_req_handler_t)(httpd_req_t *req); + +typedef struct { + httpd_req_t* req; + httpd_req_handler_t handler; +} httpd_async_req_t; + +bool is_on_async_worker_thread(void); +esp_err_t submit_async_req(httpd_req_t *req, httpd_req_handler_t handler); +void async_req_worker_task(void *p); +void start_async_req_workers(void); + +esp_err_t httpd_req_async_handler_begin(httpd_req_t *r, httpd_req_t **out); +esp_err_t httpd_req_async_handler_complete(httpd_req_t *r); + +#endif //async_worker_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/http_status.cpp b/lib/PsychicHttp/src/http_status.cpp new file mode 100644 index 0000000..64e0c07 --- /dev/null +++ b/lib/PsychicHttp/src/http_status.cpp @@ -0,0 +1,194 @@ +#include "http_status.h" + +bool http_informational(int code) +{ + return code >= 100 && code < 200; +} + +bool http_success(int code) +{ + return code >= 200 && code < 300; +} + +bool http_redirection(int code) +{ + return code >= 300 && code < 400; +} + +bool http_client_error(int code) +{ + return code >= 400 && code < 500; +} + +bool http_server_error(int code) +{ + return code >= 500 && code < 600; +} + +bool http_failure(int code) +{ + return code >= 400 && code < 600; +} + +const char *http_status_group(int code) +{ + if (http_informational(code)) + return "Informational"; + + if (http_success(code)) + return "Success"; + + if (http_redirection(code)) + return "Redirection"; + + if (http_client_error(code)) + return "Client Error"; + + if (http_server_error(code)) + return "Server Error"; + + return "Unknown"; +} + +const char *http_status_reason(int code) +{ + switch (code) + { + /*####### 1xx - Informational #######*/ + case 100: + return "Continue"; + case 101: + return "Switching Protocols"; + case 102: + return "Processing"; + case 103: + return "Early Hints"; + + /*####### 2xx - Successful #######*/ + case 200: + return "OK"; + case 201: + return "Created"; + case 202: + return "Accepted"; + case 203: + return "Non-Authoritative Information"; + case 204: + return "No Content"; + case 205: + return "Reset Content"; + case 206: + return "Partial Content"; + case 207: + return "Multi-Status"; + case 208: + return "Already Reported"; + case 226: + return "IM Used"; + + /*####### 3xx - Redirection #######*/ + case 300: + return "Multiple Choices"; + case 301: + return "Moved Permanently"; + case 302: + return "Found"; + case 303: + return "See Other"; + case 304: + return "Not Modified"; + case 305: + return "Use Proxy"; + case 307: + return "Temporary Redirect"; + case 308: + return "Permanent Redirect"; + + /*####### 4xx - Client Error #######*/ + case 400: + return "Bad Request"; + case 401: + return "Unauthorized"; + case 402: + return "Payment Required"; + case 403: + return "Forbidden"; + case 404: + return "Not Found"; + case 405: + return "Method Not Allowed"; + case 406: + return "Not Acceptable"; + case 407: + return "Proxy Authentication Required"; + case 408: + return "Request Timeout"; + case 409: + return "Conflict"; + case 410: + return "Gone"; + case 411: + return "Length Required"; + case 412: + return "Precondition Failed"; + case 413: + return "Content Too Large"; + case 414: + return "URI Too Long"; + case 415: + return "Unsupported Media Type"; + case 416: + return "Range Not Satisfiable"; + case 417: + return "Expectation Failed"; + case 418: + return "I'm a teapot"; + case 421: + return "Misdirected Request"; + case 422: + return "Unprocessable Content"; + case 423: + return "Locked"; + case 424: + return "Failed Dependency"; + case 425: + return "Too Early"; + case 426: + return "Upgrade Required"; + case 428: + return "Precondition Required"; + case 429: + return "Too Many Requests"; + case 431: + return "Request Header Fields Too Large"; + case 451: + return "Unavailable For Legal Reasons"; + + /*####### 5xx - Server Error #######*/ + case 500: + return "Internal Server Error"; + case 501: + return "Not Implemented"; + case 502: + return "Bad Gateway"; + case 503: + return "Service Unavailable"; + case 504: + return "Gateway Timeout"; + case 505: + return "HTTP Version Not Supported"; + case 506: + return "Variant Also Negotiates"; + case 507: + return "Insufficient Storage"; + case 508: + return "Loop Detected"; + case 510: + return "Not Extended"; + case 511: + return "Network Authentication Required"; + + default: + return "Unknown"; + } +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/http_status.h b/lib/PsychicHttp/src/http_status.h new file mode 100644 index 0000000..e03b735 --- /dev/null +++ b/lib/PsychicHttp/src/http_status.h @@ -0,0 +1,15 @@ +#ifndef MICRO_HTTP_STATUS_H +#define MICRO_HTTP_STATUS_H + +#include + +bool http_informational(int code); +bool http_success(int code); +bool http_redirection(int code); +bool http_client_error(int code); +bool http_server_error(int code); +bool http_failure(int code); +const char *http_status_group(int code); +const char *http_status_reason(int code); + +#endif // MICRO_HTTP_STATUS_H \ No newline at end of file