Showing preview only (1,132K chars total). Download the full file or copy to clipboard to get everything.
Repository: hoeken/PsychicHttp
Branch: master
Commit: 1b63cf4db65f
Files: 158
Total size: 862.7 KB
Directory structure:
gitextract_tbu1jugx/
├── .clang-format
├── .github/
│ └── workflows/
│ ├── arduino.yml
│ ├── esp-idf.yml
│ ├── platformio.yml
│ └── release.yml
├── .gitignore
├── .gitmodules
├── CHANGELOG.md
├── CMakeLists.txt
├── LICENSE
├── README.md
├── RELEASE.md
├── benchmark/
│ ├── arduinomongoose/
│ │ ├── .gitignore
│ │ ├── include/
│ │ │ └── README
│ │ ├── lib/
│ │ │ └── README
│ │ ├── platformio.ini
│ │ ├── src/
│ │ │ └── main.cpp
│ │ └── test/
│ │ └── README
│ ├── comparison.ods
│ ├── espasyncwebserver/
│ │ ├── .gitignore
│ │ ├── include/
│ │ │ └── README
│ │ ├── lib/
│ │ │ └── README
│ │ ├── platformio.ini
│ │ ├── src/
│ │ │ ├── main.cpp
│ │ │ └── secret.h
│ │ └── test/
│ │ └── README
│ ├── eventsource-client-test.js
│ ├── http-client-test.js
│ ├── loadtest-http.sh
│ ├── loadtest-websocket.sh
│ ├── package.json
│ ├── parse-http-test.js
│ ├── parse-websocket-test.js
│ ├── psychichttp/
│ │ ├── .gitignore
│ │ ├── include/
│ │ │ └── README
│ │ ├── lib/
│ │ │ └── README
│ │ ├── platformio.ini
│ │ ├── src/
│ │ │ ├── main.cpp
│ │ │ └── secret.h
│ │ └── test/
│ │ └── README
│ ├── psychichttps/
│ │ ├── .gitignore
│ │ ├── data/
│ │ │ ├── server.crt
│ │ │ └── server.key
│ │ ├── include/
│ │ │ └── README
│ │ ├── lib/
│ │ │ └── README
│ │ ├── platformio.ini
│ │ ├── src/
│ │ │ ├── main.cpp
│ │ │ └── secret.h
│ │ └── test/
│ │ └── README
│ ├── results/
│ │ ├── arduinomongoose-http-loadtest.log
│ │ ├── arduinomongoose-websocket-loadtest.log
│ │ ├── espasync-http-loadtest.log
│ │ ├── espasync-websocket-loadtest.log
│ │ ├── psychic-http-loadtest.log
│ │ ├── psychic-ssl-http-loadtest.log
│ │ ├── psychic-ssl-websocket-loadtest.log
│ │ ├── psychic-v1.1-http-loadtest.log
│ │ ├── psychic-v1.1-websocket-loadtest.log
│ │ └── psychic-websocket-loadtest.log
│ └── websocket-client-test.js
├── component.mk
├── examples/
│ ├── esp-idf/
│ │ ├── .gitignore
│ │ ├── CMakeLists.txt
│ │ ├── README.md
│ │ ├── data/
│ │ │ ├── custom.txt
│ │ │ ├── server.crt
│ │ │ ├── server.key
│ │ │ ├── www/
│ │ │ │ ├── index.html
│ │ │ │ └── text.txt
│ │ │ └── www-ap/
│ │ │ └── index.html
│ │ ├── include/
│ │ │ └── README
│ │ ├── lib/
│ │ │ └── README
│ │ ├── main/
│ │ │ ├── CMakeLists.txt
│ │ │ ├── idf_component.yml
│ │ │ ├── main.cpp
│ │ │ └── secret.h
│ │ ├── partitions_custom.csv
│ │ └── sdkconfig.defaults
│ ├── platformio/
│ │ ├── .gitignore
│ │ ├── data/
│ │ │ ├── custom.txt
│ │ │ ├── server.crt
│ │ │ ├── server.key
│ │ │ ├── www/
│ │ │ │ ├── index.html
│ │ │ │ ├── text.txt
│ │ │ │ └── websocket-test.html
│ │ │ └── www-ap/
│ │ │ └── index.html
│ │ ├── platformio.ini
│ │ └── src/
│ │ ├── main.cpp
│ │ └── secret.h
│ └── websockets/
│ ├── .gitignore
│ ├── data/
│ │ └── www/
│ │ └── index.html
│ ├── include/
│ │ └── README
│ ├── lib/
│ │ └── README
│ ├── platformio.ini
│ ├── src/
│ │ ├── main.cpp
│ │ └── secret.h
│ └── test/
│ └── README
├── idf_component.yml
├── library.json
├── library.properties
├── middleware.md
├── partitions-4MB.csv
├── platformio.ini
├── request flow.drawio
└── src/
├── ChunkPrinter.cpp
├── ChunkPrinter.h
├── MultipartProcessor.cpp
├── MultipartProcessor.h
├── PsychicClient.cpp
├── PsychicClient.h
├── PsychicCore.h
├── PsychicEndpoint.cpp
├── PsychicEndpoint.h
├── PsychicEventSource.cpp
├── PsychicEventSource.h
├── PsychicFileResponse.cpp
├── PsychicFileResponse.h
├── PsychicHandler.cpp
├── PsychicHandler.h
├── PsychicHttp.h
├── PsychicHttpServer.cpp
├── PsychicHttpServer.h
├── PsychicHttpsServer.cpp
├── PsychicHttpsServer.h
├── PsychicJson.cpp
├── PsychicJson.h
├── PsychicMiddleware.cpp
├── PsychicMiddleware.h
├── PsychicMiddlewareChain.cpp
├── PsychicMiddlewareChain.h
├── PsychicMiddlewares.cpp
├── PsychicMiddlewares.h
├── PsychicRequest.cpp
├── PsychicRequest.h
├── PsychicResponse.cpp
├── PsychicResponse.h
├── PsychicRewrite.cpp
├── PsychicRewrite.h
├── PsychicStaticFileHander.cpp
├── PsychicStaticFileHandler.h
├── PsychicStreamResponse.cpp
├── PsychicStreamResponse.h
├── PsychicUploadHandler.cpp
├── PsychicUploadHandler.h
├── PsychicVersion.h
├── PsychicWebHandler.cpp
├── PsychicWebHandler.h
├── PsychicWebParameter.h
├── PsychicWebSocket.cpp
├── PsychicWebSocket.h
├── TemplatePrinter.cpp
├── TemplatePrinter.h
├── UrlEncode.cpp
├── UrlEncode.h
├── async_worker.cpp
├── async_worker.h
├── http_status.cpp
└── http_status.h
================================================
FILE CONTENTS
================================================
================================================
FILE: .clang-format
================================================
Language: Cpp
BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignConsecutiveMacros: true
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortIfStatementsOnASingleLine: false
BinPackArguments: false
ColumnLimit: 0
ContinuationIndentWidth: 2
FixNamespaceComments: false
IndentAccessModifiers: true
IndentCaseLabels: true
IndentPPDirectives: BeforeHash
IndentWidth: 2
NamespaceIndentation: All
PointerAlignment: Left
ReferenceAlignment: Left
TabWidth: 2
UseTab: Never
BreakBeforeBraces: Linux
AllowShortLambdasOnASingleLine: All
AlignAfterOpenBracket: DontAlign
================================================
FILE: .github/workflows/arduino.yml
================================================
name: Arduino Lint
on:
push:
branches: []
pull_request:
branches: []
schedule:
- cron: "0 1 * * 6" # Every Saturday at 1AM
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: arduino/arduino-lint-action@v1
with:
library-manager: update
project-type: library
compliance: strict
================================================
FILE: .github/workflows/esp-idf.yml
================================================
name: ESP-IDF
on:
push:
branches: []
pull_request:
branches: []
schedule:
- cron: "0 1 * * 6" # Every Saturday at 1AM
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build:
name: "ESP-IDF ${{ matrix.idf_ver }}"
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
idf_ver: ["v4.4.8", "release-v5.5"] # "release-v6.0" removed until arduino-esp32 v4.0 ships with ESP-IDF v6.0 support
idf_target: ["esp32", "esp32s3"]
steps:
- uses: actions/checkout@v4
with:
path: ${{ github.workspace }}/app
- name: Prepare Secret
run: cp ${{ github.workspace }}/app/examples/esp-idf/main/secret.h ${{ github.workspace }}/app/examples/esp-idf/main/_secret.h
- name: Compile
uses: espressif/esp-idf-ci-action@v1
with:
esp_idf_version: ${{ matrix.idf_ver }}
target: ${{ matrix.idf_target }}
path: app/examples/esp-idf
command: apt-get update && apt-get install -y python3-venv && idf.py build
================================================
FILE: .github/workflows/platformio.yml
================================================
name: Platform IO
on:
push:
branches: []
pull_request:
branches: []
schedule:
- cron: "0 1 * * 6" # Every Saturday at 1AM
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build:
name: "pio:${{ matrix.board }}:${{ matrix.platform }}:${{ matrix.flags }}"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
flags: ["-DPSY_ENABLE_SSL", "-DPSY_ENABLE_REGEX"]
platform: [
"stable"
]
board:
[
"esp32dev",
"esp32-s2-saola-1",
"esp32-s3-devkitc-1",
"esp32-c3-devkitc-02",
"esp32-c6-devkitc-1",
]
steps:
- uses: actions/checkout@v4
- name: Cache PlatformIO
uses: actions/cache@v4
with:
key: ${{ runner.os }}-pio
path: |
~/.cache/pip
~/.platformio
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Install Platform IO
run: |
python -m pip install --upgrade pip
pip install --upgrade platformio
- name: Install Checked out PsychicHttp
run: pio lib -g install -f $GITHUB_WORKSPACE
- name: Prepare Secret
run: |
cd examples/platformio
cp src/secret.h src/_secret.h
- run: PIO_BOARD=${{ matrix.board }} PIO_PLATFORM="https://github.com/pioarduino/platform-espressif32/releases/download/${{ matrix.platform }}/platform-espressif32.zip" PLATFORMIO_BUILD_FLAGS="${{ matrix.flags }}" pio run -e ci
================================================
FILE: .github/workflows/release.yml
================================================
---
name: Release to Platform IO and Arduino
on:
release:
types:
- released
jobs:
release_number:
name: Release Number Validation
runs-on: ubuntu-latest
steps:
### Check the version number in the code matches the tag number
- uses: actions/checkout@v4
- name: Retrieve the version number(s)
run: |
TAG_VERSION=$(sed "s/^v//" <<< $GITHUB_REF_NAME)
PLATFORMIO_VERSION=$(jq ".version" library.json -r)
ARDUINO_VERSION=$(awk -F= '/version/{gsub(/"/, "", $2); print $2}' library.properties)
echo TAG_VERSION=$TAG_VERSION >> $GITHUB_ENV
echo PLATFORMIO_VERSION=$PLATFORMIO_VERSION >> $GITHUB_ENV
echo ARDUINO_VERSION=$ARDUINO_VERSION >> $GITHUB_ENV
- name: Check the version number is semver compliant
run: |
if ! [[ $TAG_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-z]*[0-9]+)?$ ]]; then
echo "ERROR: The version number is not semver compliant"
exit 1
fi
- name: Check the Platformio version matches the tag number
run: |
if [ "$TAG_VERSION" != "$PLATFORMIO_VERSION" ]; then
echo "ERROR: The version number in library.json ($PLATFORMIO_VERSION) does not match the tag number ($TAG_VERSION)"
exit 1
fi
- name: Check the Arduino version matches the tag number
run: |
if [ "$TAG_VERSION" != "$ARDUINO_VERSION" ]; then
echo "ERROR: The version number in library.properties ($ARDUINO_VERSION) does not match the tag number ($TAG_VERSION)"
exit 1
fi
release_to_platformio:
name: Release to PlatformIO Registry
runs-on: ubuntu-latest
needs: release_number
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
- name: Install Platform IO
run: |
pip install -U platformio
platformio update
- name: Register new library version
run: |
PLATFORMIO_AUTH_TOKEN=${{ secrets.PLATFORMIO_AUTH_TOKEN }} pio pkg publish --type library --no-interactive --notify
================================================
FILE: .gitignore
================================================
_secret.h
.$request flow.drawio.bkp
.$request flow.drawio.dtmp
.clang_complete
.gcc-flags.json
.pio
.pioenvs
.pioenvs
.piolibdeps
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/ipch
.vscode/launch.json
.vscode/settings.json
*.a
*.app
*.dll
*.dylib
*.exe
*.gch
*.la
*.lai
*.lib
*.lo
*.mod
*.o
*.obj
*.opensdf
*.out
*.pch
*.sdf
*.slo
*.so
*.suo
**.DS_Store
**.pio
**.vscode
/build
/psychic-http-loadtest.log
/psychic-websocket-loadtest.log
benchmark/_psychic-http-loadtest.json
benchmark/.~lock.comparison.ods#
benchmark/http-loadtest-results.csv
benchmark/node_modules
benchmark/package-lock.json
benchmark/psychic-http-loadtest.log
benchmark/psychic-websocket-loadtest.json
benchmark/psychic-websocket-loadtest.log
benchmark/websocket-loadtest-results.csv
examples/arduino/
examples/platformio/lib/PsychicHttp
examples/websockets/lib/PsychicHttp
src/cookie.txt
src/secret.h
Visual\ Micro
================================================
FILE: .gitmodules
================================================
[submodule "examples/esp-idf/components/ArduinoJson"]
path = examples/esp-idf/components/ArduinoJson
url = https://github.com/bblanchon/ArduinoJson
branch = 7.x
[submodule "examples/esp-idf/components/arduino-esp32"]
path = examples/esp-idf/components/arduino-esp32
url = https://github.com/espressif/arduino-esp32
branch = idf-release/v4.4
[submodule "examples/esp-idf/components/esp_littlefs"]
path = examples/esp-idf/components/esp_littlefs
url = https://github.com/joltwallet/esp_littlefs.git
================================================
FILE: CHANGELOG.md
================================================
## 2.2.0
- fix: memory leaks — add PsychicEndpoint destructor to delete handler; removeEndpoint/removeHandler/removeRewrite now delete removed objects; reset() deletes middleware chain
- fix: stale endpoint state — removeEndpoint now cleans up _esp_idf_endpoints so WebSocket entries are properly unregistered across server restarts
- fix: HTTPS server now syncs max_uri_handlers and stack_size from config before starting (both were silently ignored before)
- fix: path traversal attack blocked in static file handler (#security)
- fix: redirect response code was initialized wrong, breaking redirects (#239)
- fix: correct integer comparisons in indexOf() checks, char overflow in auth buffer, and format specifiers for size_t
- fix: urlDecode bounds-checks before reading past a trailing %
- fix: regex URI matching now catches invalid patterns instead of crashing
- fix: closeCallback guards against null handler before calling checkForClosedClient
- fix: improper delegation call in PsychicJSONResponse
- feat: removed urlencode external dependency — pulled into repository as src/UrlEncode.cpp
- feat: replaced WiFi.h/ETH.h dependencies with generic esp_netif API; isConnected(), ON_STA_FILTER, and ON_AP_FILTER now work on all interface types including ESP32-P4
- feat: esp_netif compatibility — use esp_netif_next loop for older ESP-IDF versions (#235)
- feat: added warning when registering WebSocket handler after start() call (#233)
- feat: ESP-IDF v5.5 support added; v6.0 removed pending Arduino support
- fix: NTP/DHCP handling for ESP-IDF
- examples: increased stack size to fix multipart file upload issues; added start() call to HTTPS redirect server example
## 2.1.3
- fix: Added getParams() to access all parameters from issue #236
## 2.1.2
- fix: close _file before PsychicFileResponse to prevent LittleFS remove() failure
## 2.1.1
- Re-added deleted MAX function per #230
## 2.1.0 (since 2.0.0)
- send to all clients, not bail on the first one.
- Fix issue whereby H2 encoding ignores method and defaults to HTTP_GET. (#202)
- now using the stable version of pioarduino.
- V2 dev rollup: update PsychicFileResponse (set status and content type before chunked responses), fix getCookie, and add pong reply to ping. (#228, #207, #209, #222)
- Update async_worker.cpp to fix compatibility with Arduino ESP32 3.3.0. (#225)
- fixed a mistake from the pull merge.
- Moved setting content type and response code into sendHeaders(). (PR #220)
- Check if content size is 0 before sending a response. (#218)
- Fix crash with Event Source and update CI / IDF examples. (#221)
- fixed EventSource error with missing headers (content type, cache-control, keep-alive).
- fixed the CI to use the latest stable versions.
- ugh. CI so annoying.
- bump to v2.1.0.
# v2.0
I apologize for sitting on this release for so long. Its been almost a year and life just sort of got away from me. I'd like to get this release out and then start working through the backlog of issues. v2.0 has been very stable for me, so it's more than time to release it.
* Huge amount of work was done to add MiddleWare and some more under the hood updates
* Modified the request handling to bring initial url matching and filtering into PsychicHttpServer itself.
* Fixed a bug with filter() where endpoint is matched, but filter fails and it doesn't continue matching further endpoints on same uri (checks were in different codebases)
* HTTP_ANY support
* unlimited endpoints (no more need to manually set config.max_uri_handlers)
* much more flexibility for future
* Endpoint Matching Updates
* Endpoint matching functions can be set on server level (```server.setURIMatchFunction()```) or endpoint level (```endpoint.setURIMatchFunction()```)
* Added convenience macros MATCH_SIMPLE, MATCH_WILDCARD, and MATCH_REGEX
* Added regex matching of URIs, enable it with define PSY_ENABLE_REGEX
* On regex matched requests, you can get match data with request->getRegexMatches()
* Ported URL rewrite functionality from ESPAsyncWS
## Changes required from v1.x to v2.0:
* add a ```server.begin()``` or ```server.start()``` after all your ```server.on()``` calls
* remove any calls to ```config.max_uri_handlers```
* if you are using a custom ```server.config.uri_match_fn``` to match uris, change it to ```server.setURIMatchFunction()```
# 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
================================================
FILE: CMakeLists.txt
================================================
set(COMPONENT_SRCDIRS
"src"
)
set(COMPONENT_ADD_INCLUDEDIRS
"src"
)
set(COMPONENT_REQUIRES
"arduino-esp32"
"esp_https_server"
"arduinojson"
)
register_component()
target_compile_definitions(${COMPONENT_TARGET} PUBLIC -DESP32)
target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti)
================================================
FILE: LICENSE
================================================
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.
================================================
FILE: README.md
================================================
# 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.
**Discord**: [https://discord.gg/TAQrTR3f9C](https://discord.gg/TAQrTR3f9C)
# 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)
* Middleware system (logging, authentication, CORS, and custom)
* URL rewriting
* Regex URI matching
* JSON request/response support (via ArduinoJson)
* TemplatePrinter class for dynamic variables at runtime
## Differences from ESPAsyncWebserver
* No templating system (anyone actually use this?)
# 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.
* Any middleware attached to the server or endpoint is run in order.
* ```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 middleware.
* If no global handlers are called, the server.onNotFound 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.

### 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.

### 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 an issue.
## Globals Stuff
* Change your #include to ```#include <PsychicHttp.h>```
* 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
* add your handlers and call `server.start()` at the end
* server now supports unlimited endpoints — no need to set `config.max_uri_handlers`
* check your callback function definitions:
* All request callbacks now take two parameters: `PsychicRequest *request` and `PsychicResponse *response`
* `AsyncWebServerRequest` -> `PsychicRequest`
* no more onBody() event
* for small bodies (up to `MAX_REQUEST_BODY_SIZE`, 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
## Requests / Responses
* `request->send` is now `response->send()`
* 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 `request->beginResponse()`. Instantiate a `PsychicResponse` instead: ```PsychicResponse response(request);```
* No PROGMEM support (it's 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 <PsychicHttp.h>
PsychicHttpServer server;
void setup()
{
//connect to wifi
//optional: set default headers on every response
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
//call server methods to attach endpoints and handlers
server.on(...);
server.serveStatic(...);
//call start at the end. you can add/remove handlers after, except for websockets.
server.start();
}
```
## 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);
//handle any HTTP method on a URL
server.on("/any", HTTP_ANY, request_callback);
```
The ```server.on(...)``` returns a pointer to the endpoint, which can be used to call various functions like ```setHandler()```, ```addFilter()```, ```addMiddleware()```, and ```setURIMatchFunction()```.
```cpp
//respond to /url only from requests to the AP
server.on("/url", HTTP_GET, request_callback)->addFilter(ON_AP_FILTER);
//add middleware to a specific endpoint
server.on("/secure", HTTP_GET, request_callback)->addMiddleware(&authMiddleware);
//set the URI matching function on a specific endpoint
server.on("/simple", HTTP_GET, request_callback)->setURIMatchFunction(MATCH_SIMPLE);
//attach websocket handler to /ws
PsychicWebSocketHandler websocketHandler;
server.on("/ws")->setHandler(&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->send()``` function 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, PsychicResponse *response);
```
Here is a simple example that sends back the client's IP on the URL /ip
```cpp
server.on("/ip", [](PsychicRequest *request, PsychicResponse *response)
{
String output = "Your IP is: " + request->client()->remoteIP().toString();
return response->send(output.c_str());
});
```
`PsychicWebHandler` also has `onOpen()` and `onClose()` callbacks to track connections to a specific endpoint:
```cpp
PsychicWebHandler *handler = new PsychicWebHandler();
handler->onRequest([](PsychicRequest *request, PsychicResponse *response) {
return response->send("OK");
});
handler->onOpen([](PsychicClient *client) {
Serial.printf("[handler] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str());
});
handler->onClose([](PsychicClient *client) {
Serial.printf("[handler] connection #%u closed\n", client->socket());
});
server.on("/handler", handler);
```
### 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: %llu\n", path.c_str(), (uint64_t)index+(uint64_t)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, PsychicResponse *response)
{
String url = "/" + request->getFilename();
String output = "<a href=\"" + url + "\">" + url + "</a>";
return response->send(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 info (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: %llu\n", path.c_str(), (uint64_t)index+(uint64_t)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, PsychicResponse *response)
{
PsychicWebParameter *file = request->getParam("file_upload");
String url = "/" + file->value();
String output;
output += "<a href=\"" + url + "\">" + url + "</a><br/>\n";
output += "Bytes: " + String(file->size()) + "<br/>\n";
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
return response->send(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/")->addFilter(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/")->addFilter(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");
//set cache control headers
server.serveStatic("/", LittleFS, "/www/")->setCacheControl("max-age=60");
```
You could also theoretically use the file response directly:
```cpp
server.on("/ip", [](PsychicRequest *request, PsychicResponse *response)
{
String filename = "/path/to/file";
PsychicFileResponse fileResponse(request, LittleFS, filename);
return fileResponse.send();
});
```
### 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().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->remoteIP().toString().c_str());
});
//attach the handler to /ws. You can then connect to ws://ip.address/ws
//NOTE: the handler must be registered before server.start() is called, otherwise it will not work.
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->sendMessage()``` - can be used anywhere* to send a websocket message to a specific client
All of the above functions either accept a simple ```char *``` string or you can pass a constructed ```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->sendMessage("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().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->remoteIP().toString().c_str());
});
//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");
```
### JSON
PsychicHttp has built-in support for JSON via ArduinoJson. There are two ways to use it:
#### JSON Request Callback
The simplest approach is to use the `PsychicJsonRequestCallback` signature directly with `server.on()`. The JSON body is parsed automatically and passed as a `JsonVariant`:
```cpp
server.on("/api", HTTP_POST, [](PsychicRequest *request, PsychicResponse *resp, JsonVariant &json) {
JsonObject input = json.as<JsonObject>();
PsychicJsonResponse response(resp);
JsonObject output = response.getRoot();
output["status"] = "success";
output["millis"] = millis();
if (input.containsKey("foo"))
output["foo"] = input["foo"];
return response.send();
});
```
#### JSON Response
Use `PsychicJsonResponse` to build and send a JSON response from any handler:
```cpp
server.on("/json", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response) {
PsychicJsonResponse jsonResponse(response);
JsonObject root = jsonResponse.getRoot();
root["status"] = "ok";
root["millis"] = millis();
return jsonResponse.send();
});
```
### Middleware
PsychicHttp has a middleware system that allows you to run code before and after a request is handled. Middleware can be attached to the server (runs on every request) or to a specific endpoint.
The `PsychicMiddlewareNext` function must be called to pass the request to the next middleware or handler:
```cpp
esp_err_t run(PsychicRequest *request, PsychicResponse *response, PsychicMiddlewareNext next);
```
#### Built-in Middleware
Three built-in middleware classes are included:
**LoggingMiddleware** - logs requests in a curl-like format:
```cpp
LoggingMiddleware loggingMiddleware;
loggingMiddleware.setOutput(Serial);
server.addMiddleware(&loggingMiddleware);
```
**AuthenticationMiddleware** - handles HTTP Basic or Digest authentication:
```cpp
AuthenticationMiddleware auth;
auth.setUsername("admin");
auth.setPassword("admin");
auth.setRealm("My App");
auth.setAuthMethod(HTTPAuthMethod::BASIC_AUTH); // or DIGEST_AUTH
auth.setAuthFailureMessage("You must log in.");
//attach to the whole server
server.addMiddleware(&auth);
//or attach to a specific endpoint
server.on("/secure", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response) {
return response->send("Authenticated!");
})->addMiddleware(&auth);
```
**CorsMiddleware** - adds CORS headers for cross-origin requests:
```cpp
CorsMiddleware corsMiddleware;
//optional customization (defaults shown)
corsMiddleware.setOrigin("*");
corsMiddleware.setMethods("*");
corsMiddleware.setHeaders("*");
corsMiddleware.setAllowCredentials(true);
corsMiddleware.setMaxAge(86400);
server.addMiddleware(&corsMiddleware);
```
#### Custom Middleware
You can write custom middleware by either subclassing `PsychicMiddleware` or by using a lambda:
```cpp
server.addMiddleware([](PsychicRequest *request, PsychicResponse *response, PsychicMiddlewareNext next) {
Serial.printf("Before: %s\n", request->path().c_str());
esp_err_t ret = next(request, response);
Serial.printf("After: %d\n", response->getCode());
return ret;
});
```
### URL Rewriting
PsychicHttp supports URL rewriting, which allows you to map one URL to another before the request is matched to a handler:
```cpp
//rewrite /rewrite to /api?foo=rewrite
server.rewrite("/rewrite", "/api?foo=rewrite");
//rewrites can also have filters
server.rewrite("/mobile", "/mobile-index.html")->setFilter(ON_AP_FILTER);
```
### URI Matching
PsychicHttp supports three URI matching modes that can be set at the server level or per-endpoint:
* `MATCH_SIMPLE` - exact string matching
* `MATCH_WILDCARD` - ESP-IDF wildcard matching (e.g., `/files/*`)
* `MATCH_REGEX` - regular expression matching (requires `PSY_ENABLE_REGEX` define)
```cpp
//set default for all endpoints
server.setURIMatchFunction(MATCH_WILDCARD);
//or set per-endpoint
server.on("/files/*", HTTP_GET, handler)->setURIMatchFunction(MATCH_WILDCARD);
```
#### Regex URI Matching
To use regex matching, add `#define PSY_ENABLE_REGEX` before including PsychicHttp, or define it in your build flags. Then use `MATCH_REGEX` as the URI match function and capture groups via `request->getRegexMatches()`:
```cpp
server.on("^/user/([\\w]+)/?$", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response) {
std::smatch matches;
if (request->getRegexMatches(matches)) {
String username = matches.str(1).c_str();
return response->send(("Hello " + username).c_str());
}
return response->send(404, "text/plain", "Not found");
})->setURIMatchFunction(MATCH_REGEX);
```
### Default Headers
You can set headers that will be automatically added to every response:
```cpp
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
DefaultHeaders::Instance().addHeader("X-Custom-Header", "value");
```
### 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.
SSL connections require significant RAM — each TLS session consumes roughly 40–100KB of internal RAM plus two 16KB record buffers. **A board with PSRAM is strongly recommended**, and PSRAM must be enabled in your project configuration (see below). Without PSRAM, you will likely be limited to a single simultaneous SSL connection before the heap is exhausted.
To use HTTPS, you need to modify your setup like so:
```cpp
#include <PsychicHttp.h>
#include <PsychicHttpsServer.h>
PsychicHttpsServer server;
server.setCertificate(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
```
You can also generate a self-signed certificate directly on the ESP32 at runtime using mbedTLS, which is already bundled with ESP-IDF. This is useful for devices that need a unique certificate without any external tooling — generate once on first boot, store the PEM strings to NVS or LittleFS, and load them on every subsequent boot. An example implementation can be found [here](https://github.com/hoeken/YarrboardFramework/blob/34be5c9457355a5e0dcdc0afacca9e2907cfab03/src/controllers/HTTPController.cpp#L328).
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->config.stack_size = 4096; // we dont need a large stack size for this.
redirectServer->onNotFound([](PsychicRequest *request, PsychicResponse *response) {
String url = "https://" + request->host() + request->url();
return response->redirect(url.c_str());
});
redirectServer->start();
```
#### Enabling PSRAM for SSL
To offload mbedTLS allocations to PSRAM and avoid exhausting internal heap under multiple SSL connections, add the following to your project. The example below targets an **ESP32-S3-WROOM-1-N16R8** (8 MB OPI PSRAM) but the mbedTLS options apply to any ESP32 variant with PSRAM.
`sdkconfig.defaults`:
```
# Enable OPI PSRAM (8MB on ESP32-S3-WROOM-1-N16R8).
# board_build.arduino.memory_type = qio_opi selects the right bootloader/linker,
# but IDF still needs these Kconfig options to actually map PSRAM into the heap.
CONFIG_SPIRAM=y
CONFIG_SPIRAM_MODE_OCT=y
CONFIG_SPIRAM_SPEED_80M=y
CONFIG_SPIRAM_USE_MALLOC=y
# Move mbedTLS dynamic allocations to PSRAM.
# Without this, each TLS session chews ~40-100KB of internal RAM plus two 16KB
# record buffers, exhausting internal heap when multiple clients connect.
# Has no effect on targets without SPIRAM (CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC
# depends on SPIRAM in Kconfig, so it is silently ignored on C3/C5/C6/esp32).
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
# Use asymmetric record buffer sizes: full 16KB in (needed for receiving TLS
# records from browsers) but only 4KB out (sufficient for JSON API responses).
# Saves ~24KB of PSRAM per session vs the default symmetric 32KB allocation.
CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y
CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384
CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096
```
`platformio.ini`:
```ini
build_flags = -D BOARD_HAS_PSRAM
```
# 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 <TemplatePrinter.h>
```
## 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, PsychicResponse *response) {
PsychicStreamResponse streamResponse(request, "text/plain");
streamResponse.beginSend();
TemplatePrinter printer(streamResponse, 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 streamResponse.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, PsychicResponse *response) {
PsychicStreamResponse streamResponse(request, "text/html");
File file = SD.open("/www/index.html");
streamResponse.beginSend();
TemplatePrinter printer(streamResponse, templateHandler);
printer.copyFrom(file);
printer.flush();
file.close();
return streamResponse.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, PsychicResponse *response) {
PsychicStreamResponse streamResponse(request, "text/html");
File file = SD.open("/www/index.html");
streamResponse.beginSend();
TemplatePrinter::start(streamResponse, templateHandler, [&file](TemplatePrinter &printer){
printer.copyFrom(file);
});
file.close();
return streamResponse.endSend();
});
```
### Template a string like example 1
```C++
server.on("/template2", [](PsychicRequest *request, PsychicResponse *response) {
PsychicStreamResponse streamResponse(request, "text/plain");
streamResponse.beginSend();
TemplatePrinter::start(streamResponse, 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 streamResponse.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).


## HTTPS / SSL
Yes, PsychicHttp supports SSL out of the box, but there are a few caveats:
* SSL connections are memory-intensive — each TLS session consumes roughly 40–100KB of internal RAM plus two 16KB record buffers. On a board without PSRAM, a blank PsychicHttp sketch has around 150KB free, which limits you to 1–2 simultaneous SSL connections. **A board with PSRAM is strongly recommended** for any production HTTPS use. See the [Enabling PSRAM for SSL](#enabling-psram-for-ssl) section above for the required `sdkconfig.defaults` and `platformio.ini` settings to route mbedTLS allocations to PSRAM.
* 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.
# Community / Support
The best way to get support is probably with Github issues. There is also a [Discord chat](https://discord.gg/CM5abjGG) that is pretty active.
# Roadmap
## Longterm Wants
* investigate websocket performance gap
* 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.
================================================
FILE: RELEASE.md
================================================
* Update CHANGELOG
* Bump version in src/PsychicVersion.h
* Bump version in library.json
* Bump version in library.properties
* Bump version in idf_component.yml
* Make new release + tag
* this will get pulled in automatically by Arduino Library Indexer
* Platformio automatically publishes on release via .github hook
================================================
FILE: benchmark/arduinomongoose/.gitignore
================================================
.pio
.vscode/
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
================================================
FILE: benchmark/arduinomongoose/include/README
================================================
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
================================================
FILE: benchmark/arduinomongoose/lib/README
================================================
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 <Foo.h>
#include <Bar.h>
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
================================================
FILE: benchmark/arduinomongoose/platformio.ini
================================================
; 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]
================================================
FILE: benchmark/arduinomongoose/src/main.cpp
================================================
/* 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 <Arduino.h>
#include <ArduinoJSON.h>
#include <LittleFS.h>
#include <MongooseCore.h>
#include <MongooseHttpServer.h>
#include <WiFi.h>
const char* ssid = "";
const char* password = "";
MongooseHttpServer server;
const char* htmlContent = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</body>
</html>
)";
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);
}
================================================
FILE: benchmark/arduinomongoose/test/README
================================================
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
================================================
FILE: benchmark/espasyncwebserver/.gitignore
================================================
.pio
.vscode/
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
================================================
FILE: benchmark/espasyncwebserver/include/README
================================================
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
================================================
FILE: benchmark/espasyncwebserver/lib/README
================================================
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 <Foo.h>
#include <Bar.h>
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
================================================
FILE: benchmark/espasyncwebserver/platformio.ini
================================================
; 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
board = esp32-s3-devkitc-1
upload_port = /dev/ttyACM0
monitor_port = /dev/ttyACM1
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
lib_compat_mode = strict
lib_ldf_mode = chain
lib_deps =
; mathieucarbou/AsyncTCP @ 3.2.10
https://github.com/mathieucarbou/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip
mathieucarbou/ESPAsyncWebServer @ 3.3.16
bblanchon/ArduinoJson
lib_ignore =
AsyncTCP
mathieucarbou/AsyncTCP
board_build.filesystem = littlefs
build_flags =
-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=128
[env:default]
================================================
FILE: benchmark/espasyncwebserver/src/main.cpp
================================================
/* 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 "_secret.h"
#include <Arduino.h>
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <ESPmDNS.h>
#include <LittleFS.h>
#include <WiFi.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;
// hostname for mdns (psychic.local)
const char* local_hostname = "psychic";
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
const char* htmlContent = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</body>
</html>
)";
const size_t htmlContentLen = strlen(htmlContent);
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()) {
// 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;
}
// api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
// ESPAsyncWebServer, sending a char* does a buffer copy, unlike Psychic.
// Sending flash data is done with the uint8_t* overload.
request->send(200, "text/html", (uint8_t*)htmlContent, htmlContentLen);
});
// api - parameters passed in via query eg. /api/endpoint?foo=bar
server.on("/api", HTTP_GET, [](AsyncWebServerRequest* request) {
// create a response object
JsonDocument output;
output["msg"] = "status";
output["status"] = "success";
output["millis"] = millis();
// work with some params
if (request->hasParam("foo")) {
const 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);
// put this last, otherwise it clogs the other requests
// serve static files from LittleFS/www on /
server.serveStatic("/", LittleFS, "/www/");
server.begin();
}
}
void loop()
{
ws.cleanupClients();
Serial.printf("Free Heap: %d\n", esp_get_free_heap_size());
delay(1000);
}
================================================
FILE: benchmark/espasyncwebserver/src/secret.h
================================================
#define WIFI_SSID "Your_SSID"
#define WIFI_PASS "Your_PASS"
================================================
FILE: benchmark/espasyncwebserver/test/README
================================================
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
================================================
FILE: benchmark/eventsource-client-test.js
================================================
#!/usr/bin/env node
//stress test the client opening/closing code
const EventSource = require('eventsource');
const url = 'http://psychic.local/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();
================================================
FILE: benchmark/http-client-test.js
================================================
#!/usr/bin/env node
//stress test the http request code
const axios = require('axios');
const url = 'http://psychic.local/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();
================================================
FILE: benchmark/loadtest-http.sh
================================================
#!/usr/bin/env bash
#Command to install the testers:
# npm install
TEST_IP="psychic.local"
TEST_TIME=10
#LOG_FILE=psychic-http-loadtest.log
LOG_FILE=_psychic-http-loadtest.json
RESULTS_FILE=http-loadtest-results.csv
TIMEOUT=10000
WORKERS=1
PROTOCOL=http
#PROTOCOL=https
echo "url,connections,rps,latency,errors" > $RESULTS_FILE
for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20
do
printf "\n\nCLIENTS: *** $CONCURRENCY ***\n\n" >> $LOG_FILE
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/"
autocannon -c $CONCURRENCY -w $WORKERS -d $TEST_TIME -j "$PROTOCOL://$TEST_IP/" > $LOG_FILE
node parse-http-test.js $LOG_FILE $RESULTS_FILE
sleep 5
done
for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20
do
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/api"
autocannon -c $CONCURRENCY -w $WORKERS -d $TEST_TIME -j "$PROTOCOL://$TEST_IP/api?foo=bar" > $LOG_FILE
node parse-http-test.js $LOG_FILE $RESULTS_FILE
sleep 5
done
for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20
do
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/alien.png"
autocannon -c $CONCURRENCY -w $WORKERS -d $TEST_TIME -j "$PROTOCOL://$TEST_IP/alien.png" > $LOG_FILE
node parse-http-test.js $LOG_FILE $RESULTS_FILE
sleep 5
done
rm $LOG_FILE
================================================
FILE: benchmark/loadtest-websocket.sh
================================================
#!/usr/bin/env bash
#Command to install the testers:
# npm install
TEST_IP="psychic.local"
TEST_TIME=60
LOG_FILE=psychic-websocket-loadtest.json
RESULTS_FILE=websocket-loadtest-results.csv
PROTOCOL=ws
#PROTOCOL=wss
if test -f "$LOG_FILE"; then
rm $LOG_FILE
fi
echo "url,clients,rps,latency,errors" > $RESULTS_FILE
CORES=1
for CONCURRENCY in 1 2 3 4 5
do
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/ws"
loadtest -c $CONCURRENCY --cores $CORES -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
node parse-websocket-test.js $LOG_FILE $RESULTS_FILE
sleep 2
done
CORES=2
for CONNECTIONS in 6 8 10 12 14
do
CONCURRENCY=$((CONNECTIONS / 2))
echo "Testing $CONNECTIONS clients on $PROTOCOL://$TEST_IP/ws"
loadtest -c $CONCURRENCY --cores $CORES -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
node parse-websocket-test.js $LOG_FILE $RESULTS_FILE
sleep 2
done
CORES=4
for CONNECTIONS in 16 20 24 28 32
do
CONCURRENCY=$((CONNECTIONS / CORES))
echo "Testing $CONNECTIONS clients on $PROTOCOL://$TEST_IP/ws"
loadtest -c $CONCURRENCY --cores $CORES -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
node parse-websocket-test.js $LOG_FILE $RESULTS_FILE
sleep 2
done
================================================
FILE: benchmark/package.json
================================================
{
"dependencies": {
"autocannon": "^7.15.0",
"axios": "^1.6.2",
"csv-writer": "^1.6.0",
"eventsource": "^2.0.2",
"loadtest": "^8.0.9",
"ws": "^8.14.2"
}
}
================================================
FILE: benchmark/parse-http-test.js
================================================
const fs = require('fs');
const createCsvWriter = require('csv-writer').createObjectCsvWriter;
// Get the input and output file paths from the command line arguments
const inputFilePath = process.argv[2];
const outputFilePath = process.argv[3];
if (!inputFilePath || !outputFilePath) {
console.error('Usage: node script.js <inputFilePath> <outputFilePath>');
process.exit(1);
}
// Read and parse the JSON file
fs.readFile(inputFilePath, 'utf8', (err, data) => {
if (err) {
console.error('Error reading the input file:', err);
return;
}
// Parse the JSON data
const jsonData = JSON.parse(data);
// Extract the desired fields
const { url, connections, latency, requests, errors } = jsonData;
const latencyMean = latency.mean;
const requestsMean = requests.mean;
// Set up the CSV writer
const csvWriter = createCsvWriter({
path: outputFilePath,
header: [
{id: 'url', title: 'URL'},
{id: 'connections', title: 'Connections'},
{id: 'requestsMean', title: 'Requests Mean'},
{id: 'latencyMean', title: 'Latency Mean'},
{id: 'errors', title: 'Errors'},
],
append: true // this will append to the existing file
});
// Prepare the data to be written
const records = [
{ url: url, connections: connections, latencyMean: latencyMean, requestsMean: requestsMean, errors: errors }
];
// Write the data to the CSV file
csvWriter.writeRecords(records)
.then(() => {
console.log('Data successfully appended to CSV file.');
})
.catch(err => {
console.error('Error writing to the CSV file:', err);
});
});
================================================
FILE: benchmark/parse-websocket-test.js
================================================
const fs = require('fs');
const readline = require('readline');
if (process.argv.length !== 4) {
console.error('Usage: node parse-websocket-test.js <input_file> <output_file>');
process.exit(1);
}
const inputFile = process.argv[2];
const outputFile = process.argv[3];
async function parseFile() {
const fileStream = fs.createReadStream(inputFile);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
let targetUrl = null;
let totalErrors = null;
let meanLatency = null;
let effectiveRps = null;
let concurrentClients = null;
for await (const line of rl) {
if (line.startsWith('Target URL:')) {
targetUrl = line.split(':').slice(1).join(':').trim();
}
if (line.startsWith('Total errors:')) {
totalErrors = parseInt(line.split(':')[1].trim(), 10);
}
if (line.startsWith('Mean latency:')) {
meanLatency = parseFloat(line.split(':')[1].trim());
}
if (line.startsWith('Effective rps:')) {
effectiveRps = parseInt(line.split(':')[1].trim(), 10);
}
if (line.startsWith('Concurrent clients:')) {
concurrentClients = parseInt(line.split(':')[1].trim(), 10);
}
}
if (targetUrl === null || totalErrors === null || meanLatency === null || effectiveRps === null || concurrentClients === null) {
console.error('Failed to extract necessary data from the input file');
process.exit(1);
}
const csvLine = `${targetUrl},${concurrentClients},${effectiveRps},${meanLatency},${totalErrors}\n`;
fs.appendFile(outputFile, csvLine, (err) => {
if (err) {
console.error('Failed to append to CSV file:', err);
process.exit(1);
}
console.log('Data successfully appended to CSV file.');
});
}
parseFile().catch(err => {
console.error('Error reading file:', err);
process.exit(1);
});
================================================
FILE: benchmark/psychichttp/.gitignore
================================================
.pio
.vscode/
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
================================================
FILE: benchmark/psychichttp/include/README
================================================
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
================================================
FILE: benchmark/psychichttp/lib/README
================================================
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 <Foo.h>
#include <Bar.h>
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
================================================
FILE: benchmark/psychichttp/platformio.ini
================================================
; 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 =
bblanchon/ArduinoJson
board_build.filesystem = littlefs
[env:default]
lib_deps = https://github.com/hoeken/PsychicHttp
[env:v2-dev]
lib_deps = https://github.com/hoeken/PsychicHttp#v2-dev
board = esp32-s3-devkitc-1
upload_port = /dev/ttyACM0
monitor_port = /dev/ttyACM1
================================================
FILE: benchmark/psychichttp/src/main.cpp
================================================
/* 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 "_secret.h"
#include <Arduino.h>
#include <ArduinoJson.h>
#include <ESPmDNS.h>
#include <LittleFS.h>
#include <PsychicHttp.h>
#include <WiFi.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;
// hostname for mdns (psychic.local)
const char* local_hostname = "psychic";
PsychicHttpServer server;
PsychicWebSocketHandler websocketHandler;
PsychicEventSource eventSource;
const char* htmlContent = R"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</body>
</html>
)";
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()) {
// 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;
}
// our index
server.on("/", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->send(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) {
response->send(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, PsychicResponse* response) {
//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")->value();
output["foo"] = foo;
}
//serialize and return
String jsonBuffer;
serializeJson(output, jsonBuffer);
return response->send(200, "application/json", jsonBuffer.c_str()); });
server.begin();
}
}
unsigned long last;
void loop()
{
if (millis() - last > 1000) {
Serial.printf("Free Heap: %d\n", esp_get_free_heap_size());
last = millis();
}
}
================================================
FILE: benchmark/psychichttp/src/secret.h
================================================
#define WIFI_SSID "Your_SSID"
#define WIFI_PASS "Your_PASS"
================================================
FILE: benchmark/psychichttp/test/README
================================================
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
================================================
FILE: benchmark/psychichttps/.gitignore
================================================
.pio
.vscode/
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
================================================
FILE: benchmark/psychichttps/data/server.crt
================================================
-----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-----
================================================
FILE: benchmark/psychichttps/data/server.key
================================================
-----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-----
================================================
FILE: benchmark/psychichttps/include/README
================================================
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
================================================
FILE: benchmark/psychichttps/lib/README
================================================
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 <Foo.h>
#include <Bar.h>
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
================================================
FILE: benchmark/psychichttps/platformio.ini
================================================
; 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]
================================================
FILE: benchmark/psychichttps/src/main.cpp
================================================
/* 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 "_secret.h"
#include <Arduino.h>
#include <ArduinoJSON.h>
#include <LittleFS.h>
#include <PsychicHttp.h>
#include <PsychicHttpsServer.h>
#include <WiFi.h>
#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"(
<!DOCTYPE html>
<html>
<head>
<title>Sample HTML</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
</body>
</html>
)";
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.setCertificate(server_cert.c_str(), server_key.c_str());
// our index
server.on("/", HTTP_GET, [](PsychicRequest* request)
{ return response->send(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)
{
response->send(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 response->send(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();
}
}
================================================
FILE: benchmark/psychichttps/src/secret.h
================================================
#define WIFI_SSID "Your_SSID"
#define WIFI_PASS "Your_PASS"
================================================
FILE: benchmark/psychichttps/test/README
================================================
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
================================================
FILE: benchmark/results/arduinomongoose-http-loadtest.log
================================================
CLIENTS: *** 1 ***
Running 60s test @ http://192.168.2.131/
1 connections
1 workers
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 720 ms [90m│[39m 14880 ms [90m│[39m 29087 ms [90m│[39m 29519 ms [90m│[39m 15024.03 ms [90m│[39m 8572.14 ms [90m│[39m 29737 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────┤[39m
[90m│[39m Req/Sec [90m│[39m 11 [90m│[39m 11 [90m│[39m 12 [90m│[39m 14 [90m│[39m 12.47 [90m│[39m 0.87 [90m│[39m 11 [90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 48.2 kB [90m│[39m 48.2 kB [90m│[39m 52.6 kB [90m│[39m 61.3 kB [90m│[39m 54.6 kB [90m│[39m 3.79 kB [90m│[39m 48.2 kB [90m│[39m
[90m└───────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 748 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 781 ms [90m│[39m 15061 ms [90m│[39m 29571 ms [90m│[39m 30046 ms [90m│[39m 15137.29 ms [90m│[39m 8833.98 ms [90m│[39m 30393 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬───────[39m[90m┬─────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Req/Sec [90m│[39m 17 [90m│[39m 18 [90m│[39m 20 [90m│[39m 23 [90m│[39m 20.34 [90m│[39m 1.27 [90m│[39m 17 [90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 2.99 kB [90m│[39m 3.15 kB [90m│[39m 3.52 kB [90m│[39m 4.03 kB [90m│[39m 3.57 kB [90m│[39m 220 B [90m│[39m 2.99 kB [90m│[39m
[90m└───────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴───────[39m[90m┴─────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 1220 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 969 ms [90m│[39m 15300 ms [90m│[39m 29831 ms [90m│[39m 30132 ms [90m│[39m 15143.83 ms [90m│[39m 8807.54 ms [90m│[39m 30385 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────┤[39m
[90m│[39m Req/Sec [90m│[39m 3 [90m│[39m 3 [90m│[39m 3 [90m│[39m 4 [90m│[39m 3.15 [90m│[39m 0.36 [90m│[39m 3 [90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 86.1 kB [90m│[39m 86.1 kB [90m│[39m 86.1 kB [90m│[39m 115 kB [90m│[39m 90.4 kB [90m│[39m 10.3 kB [90m│[39m 86.1 kB [90m│[39m
[90m└───────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 189 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 803 ms [90m│[39m 14773 ms [90m│[39m 29269 ms [90m│[39m 29642 ms [90m│[39m 14925.25 ms [90m│[39m 8556.82 ms [90m│[39m 29974 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬───────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────┤[39m
[90m│[39m Req/Sec [90m│[39m 17 [90m│[39m 17 [90m│[39m 19 [90m│[39m 21 [90m│[39m 18.9 [90m│[39m 0.98 [90m│[39m 17 [90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 74.5 kB [90m│[39m 74.5 kB [90m│[39m 83.3 kB [90m│[39m 92 kB [90m│[39m 82.8 kB [90m│[39m 4.29 kB [90m│[39m 74.5 kB [90m│[39m
[90m└───────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴───────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 1134 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 772 ms [90m│[39m 15252 ms [90m│[39m 29307 ms [90m│[39m 29709 ms [90m│[39m 15094.92 ms [90m│[39m 8732.51 ms [90m│[39m 30070 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬───────[39m[90m┬─────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Req/Sec [90m│[39m 26 [90m│[39m 27 [90m│[39m 29 [90m│[39m 32 [90m│[39m 29.72 [90m│[39m 1.43 [90m│[39m 26 [90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 4.58 kB [90m│[39m 4.75 kB [90m│[39m 5.11 kB [90m│[39m 5.63 kB [90m│[39m 5.23 kB [90m│[39m 251 B [90m│[39m 4.58 kB [90m│[39m
[90m└───────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴───────[39m[90m┴─────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 1783 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 856 ms [90m│[39m 15635 ms [90m│[39m 28880 ms [90m│[39m 29310 ms [90m│[39m 15261.99 ms [90m│[39m 8577.65 ms [90m│[39m 29700 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬─────────[39m[90m┬────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼────────┤[39m
[90m│[39m Req/Sec [90m│[39m 4 [90m│[39m 4 [90m│[39m 5 [90m│[39m 6 [90m│[39m 4.64 [90m│[39m 0.64 [90m│[39m 4 [90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 115 kB [90m│[39m 115 kB [90m│[39m 144 kB [90m│[39m 172 kB [90m│[39m 133 kB [90m│[39m 18.1 kB [90m│[39m 115 kB [90m│[39m
[90m└───────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴─────────[39m[90m┴────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 278 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 794 ms [90m│[39m 15166 ms [90m│[39m 29323 ms [90m│[39m 29697 ms [90m│[39m 15066.78 ms [90m│[39m 8676.25 ms [90m│[39m 30114 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬─────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬─────────[39m[90m┬─────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼─────────┤[39m
[90m│[39m Req/Sec [90m│[39m 22 [90m│[39m 23 [90m│[39m 26 [90m│[39m 29 [90m│[39m 25.99 [90m│[39m 1.42 [90m│[39m 22 [90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼─────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 96.4 kB [90m│[39m 101 kB [90m│[39m 114 kB [90m│[39m 127 kB [90m│[39m 114 kB [90m│[39m 6.22 kB [90m│[39m 96.4 kB [90m│[39m
[90m└───────────[39m[90m┴─────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴─────────[39m[90m┴─────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 1559 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 826 ms [90m│[39m 14949 ms [90m│[39m 29377 ms [90m│[39m 29790 ms [90m│[39m 15049.95 ms [90m│[39m 8720.15 ms [90m│[39m 30168 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬───────[39m[90m┬─────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Req/Sec [90m│[39m 32 [90m│[39m 32 [90m│[39m 36 [90m│[39m 39 [90m│[39m 36.1 [90m│[39m 1.82 [90m│[39m 32 [90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 5.63 kB [90m│[39m 5.63 kB [90m│[39m 6.34 kB [90m│[39m 6.87 kB [90m│[39m 6.36 kB [90m│[39m 319 B [90m│[39m 5.63 kB [90m│[39m
[90m└───────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴───────[39m[90m┴─────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 2166 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬─────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼─────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 995 ms [90m│[39m 15127 ms [90m│[39m 29464 ms [90m│[39m 29993 ms [90m│[39m 15219.94 ms [90m│[39m 8704 ms [90m│[39m 30258 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴─────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬─────────[39m[90m┬─────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼─────────┤[39m
[90m│[39m Req/Sec [90m│[39m 3 [90m│[39m 3 [90m│[39m 6 [90m│[39m 6 [90m│[39m 5.62 [90m│[39m 0.99 [90m│[39m 3 [90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼─────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 86.1 kB [90m│[39m 86.1 kB [90m│[39m 172 kB [90m│[39m 172 kB [90m│[39m 161 kB [90m│[39m 28.3 kB [90m│[39m 86.1 kB [90m│[39m
[90m└───────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴─────────[39m[90m┴─────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 337 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 702 ms [90m│[39m 15255 ms [90m│[39m 29811 ms [90m│[39m 30226 ms [90m│[39m 15144.93 ms [90m│[39m 8830.16 ms [90m│[39m 30616 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────┤[39m
[90m│[39m Req/Sec [90m│[39m 26 [90m│[39m 27 [90m│[39m 29 [90m│[39m 32 [90m│[39m 29.05 [90m│[39m 1.58 [90m│[39m 26 [90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 114 kB [90m│[39m 118 kB [90m│[39m 127 kB [90m│[39m 140 kB [90m│[39m 127 kB [90m│[39m 6.9 kB [90m│[39m 114 kB [90m│[39m
[90m└───────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 1743 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬───────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼───────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 762 ms [90m│[39m 15231 ms [90m│[39m 29096 ms [90m│[39m 29534 ms [90m│[39m 15112.66 ms [90m│[39m 8660.6 ms [90m│[39m 29997 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴───────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬────────[39m[90m┬───────[39m[90m┬─────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Req/Sec [90m│[39m 35 [90m│[39m 35 [90m│[39m 41 [90m│[39m 44 [90m│[39m 40.89 [90m│[39m 1.9 [90m│[39m 35 [90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 6.16 kB [90m│[39m 6.16 kB [90m│[39m 7.22 kB [90m│[39m 7.75 kB [90m│[39m 7.2 kB [90m│[39m 334 B [90m│[39m 6.16 kB [90m│[39m
[90m└───────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴────────[39m[90m┴───────[39m[90m┴─────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 2453 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬─────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼─────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 1175 ms [90m│[39m 15413 ms [90m│[39m 29485 ms [90m│[39m 30066 ms [90m│[39m 15217.07 ms [90m│[39m 8604.35 ms [90m│[39m 30126 ms [90m│[39m
[90m└─────────[39m[90m┴─────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬─────────[39m[90m┬────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼────────┤[39m
[90m│[39m Req/Sec [90m│[39m 4 [90m│[39m 4 [90m│[39m 8 [90m│[39m 8 [90m│[39m 6.47 [90m│[39m 1.87 [90m│[39m 4 [90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 115 kB [90m│[39m 115 kB [90m│[39m 230 kB [90m│[39m 230 kB [90m│[39m 186 kB [90m│[39m 53.6 kB [90m│[39m 115 kB [90m│[39m
[90m└───────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴─────────[39m[90m┴────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 388 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 802 ms [90m│[39m 14978 ms [90m│[39m 29391 ms [90m│[39m 29863 ms [90m│[39m 15042.57 ms [90m│[39m 8642.88 ms [90m│[39m 30280 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────┤[39m
[90m│[39m Req/Sec [90m│[39m 26 [90m│[39m 29 [90m│[39m 33 [90m│[39m 37 [90m│[39m 33.19 [90m│[39m 2.13 [90m│[39m 26 [90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 114 kB [90m│[39m 127 kB [90m│[39m 145 kB [90m│[39m 162 kB [90m│[39m 145 kB [90m│[39m 9.3 kB [90m│[39m 114 kB [90m│[39m
[90m└───────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 1991 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 724 ms [90m│[39m 15356 ms [90m│[39m 29304 ms [90m│[39m 29791 ms [90m│[39m 15198.71 ms [90m│[39m 8761.23 ms [90m│[39m 30174 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬───────[39m[90m┬─────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Req/Sec [90m│[39m 40 [90m│[39m 40 [90m│[39m 45 [90m│[39m 50 [90m│[39m 44.84 [90m│[39m 2.78 [90m│[39m 40 [90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 7.04 kB [90m│[39m 7.04 kB [90m│[39m 7.92 kB [90m│[39m 8.81 kB [90m│[39m 7.89 kB [90m│[39m 489 B [90m│[39m 7.04 kB [90m│[39m
[90m└───────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴───────[39m[90m┴─────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 2690 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬─────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼─────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 1250 ms [90m│[39m 15289 ms [90m│[39m 29607 ms [90m│[39m 30193 ms [90m│[39m 15345.82 ms [90m│[39m 8719.55 ms [90m│[39m 30354 ms [90m│[39m
[90m└─────────[39m[90m┴─────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬─────────[39m[90m┬────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼────────┤[39m
[90m│[39m Req/Sec [90m│[39m 5 [90m│[39m 5 [90m│[39m 6 [90m│[39m 10 [90m│[39m 7.25 [90m│[39m 2.25 [90m│[39m 5 [90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 144 kB [90m│[39m 144 kB [90m│[39m 172 kB [90m│[39m 287 kB [90m│[39m 208 kB [90m│[39m 64.5 kB [90m│[39m 144 kB [90m│[39m
[90m└───────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴─────────[39m[90m┴────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 435 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 847 ms [90m│[39m 14920 ms [90m│[39m 28899 ms [90m│[39m 29392 ms [90m│[39m 15008.54 ms [90m│[39m 8456.33 ms [90m│[39m 29870 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬─────────[39m[90m┬────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼────────┤[39m
[90m│[39m Req/Sec [90m│[39m 33 [90m│[39m 33 [90m│[39m 36 [90m│[39m 39 [90m│[39m 35.75 [90m│[39m 1.75 [90m│[39m 33 [90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 145 kB [90m│[39m 145 kB [90m│[39m 158 kB [90m│[39m 171 kB [90m│[39m 157 kB [90m│[39m 7.65 kB [90m│[39m 145 kB [90m│[39m
[90m└───────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴─────────[39m[90m┴────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 2145 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 890 ms [90m│[39m 14807 ms [90m│[39m 29142 ms [90m│[39m 29732 ms [90m│[39m 14934.33 ms [90m│[39m 8513.39 ms [90m│[39m 30168 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬───────[39m[90m┬─────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Req/Sec [90m│[39m 44 [90m│[39m 44 [90m│[39m 48 [90m│[39m 53 [90m│[39m 48.3 [90m│[39m 2.22 [90m│[39m 44 [90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 7.75 kB [90m│[39m 7.75 kB [90m│[39m 8.5 kB [90m│[39m 9.38 kB [90m│[39m 8.54 kB [90m│[39m 395 B [90m│[39m 7.74 kB [90m│[39m
[90m└───────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴───────[39m[90m┴─────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 2898 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 853 ms [90m│[39m 15325 ms [90m│[39m 29742 ms [90m│[39m 30578 ms [90m│[39m 15287.1 ms [90m│[39m 8714.92 ms [90m│[39m 30650 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬───────[39m[90m┬────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼───────[39m[90m┼────────┤[39m
[90m│[39m Req/Sec [90m│[39m 6 [90m│[39m 6 [90m│[39m 6 [90m│[39m 12 [90m│[39m 7.7 [90m│[39m 2.44 [90m│[39m 6 [90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼───────[39m[90m┼────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 172 kB [90m│[39m 172 kB [90m│[39m 172 kB [90m│[39m 345 kB [90m│[39m 221 kB [90m│[39m 70 kB [90m│[39m 172 kB [90m│[39m
[90m└───────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴───────[39m[90m┴────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 462 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 785 ms [90m│[39m 15141 ms [90m│[39m 29506 ms [90m│[39m 29981 ms [90m│[39m 15124.91 ms [90m│[39m 8769.84 ms [90m│[39m 30379 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────┤[39m
[90m│[39m Req/Sec [90m│[39m 32 [90m│[39m 33 [90m│[39m 37 [90m│[39m 40 [90m│[39m 36.64 [90m│[39m 2.04 [90m│[39m 32 [90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 140 kB [90m│[39m 145 kB [90m│[39m 162 kB [90m│[39m 175 kB [90m│[39m 160 kB [90m│[39m 8.9 kB [90m│[39m 140 kB [90m│[39m
[90m└───────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 2198 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 773 ms [90m│[39m 15322 ms [90m│[39m 29472 ms [90m│[39m 29911 ms [90m│[39m 15145.75 ms [90m│[39m 8792.14 ms [90m│[39m 30238 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬───────[39m[90m┬─────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Req/Sec [90m│[39m 45 [90m│[39m 45 [90m│[39m 51 [90m│[39m 56 [90m│[39m 50.62 [90m│[39m 3.07 [90m│[39m 45 [90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 7.97 kB [90m│[39m 7.97 kB [90m│[39m 9.03 kB [90m│[39m 9.92 kB [90m│[39m 8.96 kB [90m│[39m 541 B [90m│[39m 7.96 kB [90m│[39m
[90m└───────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴───────[39m[90m┴─────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 3037 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 903 ms [90m│[39m 15426 ms [90m│[39m 29337 ms [90m│[39m 30187 ms [90m│[39m 15395.14 ms [90m│[39m 8592.24 ms [90m│[39m 30239 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬───────[39m[90m┬────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼───────[39m[90m┼────────┤[39m
[90m│[39m Req/Sec [90m│[39m 7 [90m│[39m 7 [90m│[39m 7 [90m│[39m 14 [90m│[39m 8.06 [90m│[39m 2.03 [90m│[39m 7 [90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼───────[39m[90m┼────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 201 kB [90m│[39m 201 kB [90m│[39m 201 kB [90m│[39m 402 kB [90m│[39m 231 kB [90m│[39m 58 kB [90m│[39m 201 kB [90m│[39m
[90m└───────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴───────[39m[90m┴────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 483 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 947 ms [90m│[39m 15230 ms [90m│[39m 29194 ms [90m│[39m 29710 ms [90m│[39m 15139.81 ms [90m│[39m 8698.37 ms [90m│[39m 30418 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬─────────[39m[90m┬────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼────────┤[39m
[90m│[39m Req/Sec [90m│[39m 31 [90m│[39m 33 [90m│[39m 38 [90m│[39m 43 [90m│[39m 37.92 [90m│[39m 2.7 [90m│[39m 31 [90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 136 kB [90m│[39m 145 kB [90m│[39m 167 kB [90m│[39m 188 kB [90m│[39m 166 kB [90m│[39m 11.8 kB [90m│[39m 136 kB [90m│[39m
[90m└───────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴─────────[39m[90m┴────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 2275 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 818 ms [90m│[39m 15102 ms [90m│[39m 29043 ms [90m│[39m 29502 ms [90m│[39m 14999.69 ms [90m│[39m 8454.75 ms [90m│[39m 30295 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬───────[39m[90m┬─────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Req/Sec [90m│[39m 44 [90m│[39m 45 [90m│[39m 51 [90m│[39m 60 [90m│[39m 51.37 [90m│[39m 3.35 [90m│[39m 44 [90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 7.79 kB [90m│[39m 7.97 kB [90m│[39m 9.03 kB [90m│[39m 10.6 kB [90m│[39m 9.09 kB [90m│[39m 591 B [90m│[39m 7.79 kB [90m│[39m
[90m└───────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴───────[39m[90m┴─────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 3082 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 951 ms [90m│[39m 15565 ms [90m│[39m 30182 ms [90m│[39m 30252 ms [90m│[39m 15396.59 ms [90m│[39m 8860.08 ms [90m│[39m 31138 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬─────────[39m[90m┬────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼────────┤[39m
[90m│[39m Req/Sec [90m│[39m 7 [90m│[39m 8 [90m│[39m 8 [90m│[39m 14 [90m│[39m 8.56 [90m│[39m 1.59 [90m│[39m 7 [90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 201 kB [90m│[39m 230 kB [90m│[39m 230 kB [90m│[39m 402 kB [90m│[39m 245 kB [90m│[39m 45.5 kB [90m│[39m 201 kB [90m│[39m
[90m└───────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴─────────[39m[90m┴────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 513 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬───────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼───────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 951 ms [90m│[39m 15246 ms [90m│[39m 28914 ms [90m│[39m 29402 ms [90m│[39m 15108.58 ms [90m│[39m 8543.6 ms [90m│[39m 30524 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴───────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬─────────[39m[90m┬────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼────────┤[39m
[90m│[39m Req/Sec [90m│[39m 31 [90m│[39m 32 [90m│[39m 37 [90m│[39m 43 [90m│[39m 37.42 [90m│[39m 2.9 [90m│[39m 31 [90m│[39m
[90m├───────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼────────[39m[90m┼─────────[39m[90m┼────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 136 kB [90m│[39m 140 kB [90m│[39m 162 kB [90m│[39m 188 kB [90m│[39m 164 kB [90m│[39m 12.7 kB [90m│[39m 136 kB [90m│[39m
[90m└───────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴────────[39m[90m┴─────────[39m[90m┴────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 2245 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 885 ms [90m│[39m 15043 ms [90m│[39m 29217 ms [90m│[39m 29730 ms [90m│[39m 14953.4 ms [90m│[39m 8530.16 ms [90m│[39m 30399 ms [90m│[39m
[90m└─────────[39m[90m┴────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬─────────[39m[90m┬───────[39m[90m┬─────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [39m[90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Req/Sec [90m│[39m 44 [90m│[39m 46 [90m│[39m 52 [90m│[39m 58 [90m│[39m 52.1 [90m│[39m 3.22 [90m│[39m 44 [90m│[39m
[90m├───────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼─────────[39m[90m┼───────[39m[90m┼─────────┤[39m
[90m│[39m Bytes/Sec [90m│[39m 7.79 kB [90m│[39m 8.14 kB [90m│[39m 9.21 kB [90m│[39m 10.3 kB [90m│[39m 9.22 kB [90m│[39m 570 B [90m│[39m 7.79 kB [90m│[39m
[90m└───────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴─────────[39m[90m┴───────[39m[90m┴─────────┘[39m
[90m┌──────[39m[90m┬───────┐[39m
[90m│[39m[31m Code [39m[90m│[39m[31m Count [39m[90m│[39m
[90m├──────[39m[90m┼───────┤[39m
[90m│[39m 200 [90m│[39m 3126 [90m│[39m
[90m└──────[39m[90m┴───────┘[39m
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
[90m┌─────────[39m[90m┬─────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬──────────[39m[90m┬─────────────[39m[90m┬────────────[39m[90m┬──────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m 99% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Max [39m[90m│[39m
[90m├─────────[39m[90m┼─────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼──────────[39m[90m┼─────────────[39m[90m┼────────────[39m[90m┼──────────┤[39m
[90m│[39m Latency [90m│[39m 1079 ms [90m│[39m 15444 ms [90m│[39m 28978 ms [90m│[39m 29935 ms [90m│[39m 15389.63 ms [90m│[39m 8469.15 ms [90m│[39m 30003 ms [90m│[39m
[90m└─────────[39m[90m┴─────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴──────────[39m[90m┴─────────────[39m[90m┴────────────[39m[90m┴──────────┘[39m
[90m┌───────────[39m[90m┬─────[39m[90m┬──────[39m[90m┬────────[39m[90m┬────────[39m[90m┬────────[39m[90m┬─────────[39m[90m┬─────────┐[39m
[90m│[39m[31m Stat [39m[90m│[39m[31m 1% [39m[90m│[39m[31m 2.5% [39m[90m│[39m[31m 50% [39m[90m│[39m[31m 97.5% [39m[90m│[39m[31m Avg [39m[90m│[39m[31m Stdev [39m[90m│[39m[31m Min [
gitextract_tbu1jugx/
├── .clang-format
├── .github/
│ └── workflows/
│ ├── arduino.yml
│ ├── esp-idf.yml
│ ├── platformio.yml
│ └── release.yml
├── .gitignore
├── .gitmodules
├── CHANGELOG.md
├── CMakeLists.txt
├── LICENSE
├── README.md
├── RELEASE.md
├── benchmark/
│ ├── arduinomongoose/
│ │ ├── .gitignore
│ │ ├── include/
│ │ │ └── README
│ │ ├── lib/
│ │ │ └── README
│ │ ├── platformio.ini
│ │ ├── src/
│ │ │ └── main.cpp
│ │ └── test/
│ │ └── README
│ ├── comparison.ods
│ ├── espasyncwebserver/
│ │ ├── .gitignore
│ │ ├── include/
│ │ │ └── README
│ │ ├── lib/
│ │ │ └── README
│ │ ├── platformio.ini
│ │ ├── src/
│ │ │ ├── main.cpp
│ │ │ └── secret.h
│ │ └── test/
│ │ └── README
│ ├── eventsource-client-test.js
│ ├── http-client-test.js
│ ├── loadtest-http.sh
│ ├── loadtest-websocket.sh
│ ├── package.json
│ ├── parse-http-test.js
│ ├── parse-websocket-test.js
│ ├── psychichttp/
│ │ ├── .gitignore
│ │ ├── include/
│ │ │ └── README
│ │ ├── lib/
│ │ │ └── README
│ │ ├── platformio.ini
│ │ ├── src/
│ │ │ ├── main.cpp
│ │ │ └── secret.h
│ │ └── test/
│ │ └── README
│ ├── psychichttps/
│ │ ├── .gitignore
│ │ ├── data/
│ │ │ ├── server.crt
│ │ │ └── server.key
│ │ ├── include/
│ │ │ └── README
│ │ ├── lib/
│ │ │ └── README
│ │ ├── platformio.ini
│ │ ├── src/
│ │ │ ├── main.cpp
│ │ │ └── secret.h
│ │ └── test/
│ │ └── README
│ ├── results/
│ │ ├── arduinomongoose-http-loadtest.log
│ │ ├── arduinomongoose-websocket-loadtest.log
│ │ ├── espasync-http-loadtest.log
│ │ ├── espasync-websocket-loadtest.log
│ │ ├── psychic-http-loadtest.log
│ │ ├── psychic-ssl-http-loadtest.log
│ │ ├── psychic-ssl-websocket-loadtest.log
│ │ ├── psychic-v1.1-http-loadtest.log
│ │ ├── psychic-v1.1-websocket-loadtest.log
│ │ └── psychic-websocket-loadtest.log
│ └── websocket-client-test.js
├── component.mk
├── examples/
│ ├── esp-idf/
│ │ ├── .gitignore
│ │ ├── CMakeLists.txt
│ │ ├── README.md
│ │ ├── data/
│ │ │ ├── custom.txt
│ │ │ ├── server.crt
│ │ │ ├── server.key
│ │ │ ├── www/
│ │ │ │ ├── index.html
│ │ │ │ └── text.txt
│ │ │ └── www-ap/
│ │ │ └── index.html
│ │ ├── include/
│ │ │ └── README
│ │ ├── lib/
│ │ │ └── README
│ │ ├── main/
│ │ │ ├── CMakeLists.txt
│ │ │ ├── idf_component.yml
│ │ │ ├── main.cpp
│ │ │ └── secret.h
│ │ ├── partitions_custom.csv
│ │ └── sdkconfig.defaults
│ ├── platformio/
│ │ ├── .gitignore
│ │ ├── data/
│ │ │ ├── custom.txt
│ │ │ ├── server.crt
│ │ │ ├── server.key
│ │ │ ├── www/
│ │ │ │ ├── index.html
│ │ │ │ ├── text.txt
│ │ │ │ └── websocket-test.html
│ │ │ └── www-ap/
│ │ │ └── index.html
│ │ ├── platformio.ini
│ │ └── src/
│ │ ├── main.cpp
│ │ └── secret.h
│ └── websockets/
│ ├── .gitignore
│ ├── data/
│ │ └── www/
│ │ └── index.html
│ ├── include/
│ │ └── README
│ ├── lib/
│ │ └── README
│ ├── platformio.ini
│ ├── src/
│ │ ├── main.cpp
│ │ └── secret.h
│ └── test/
│ └── README
├── idf_component.yml
├── library.json
├── library.properties
├── middleware.md
├── partitions-4MB.csv
├── platformio.ini
├── request flow.drawio
└── src/
├── ChunkPrinter.cpp
├── ChunkPrinter.h
├── MultipartProcessor.cpp
├── MultipartProcessor.h
├── PsychicClient.cpp
├── PsychicClient.h
├── PsychicCore.h
├── PsychicEndpoint.cpp
├── PsychicEndpoint.h
├── PsychicEventSource.cpp
├── PsychicEventSource.h
├── PsychicFileResponse.cpp
├── PsychicFileResponse.h
├── PsychicHandler.cpp
├── PsychicHandler.h
├── PsychicHttp.h
├── PsychicHttpServer.cpp
├── PsychicHttpServer.h
├── PsychicHttpsServer.cpp
├── PsychicHttpsServer.h
├── PsychicJson.cpp
├── PsychicJson.h
├── PsychicMiddleware.cpp
├── PsychicMiddleware.h
├── PsychicMiddlewareChain.cpp
├── PsychicMiddlewareChain.h
├── PsychicMiddlewares.cpp
├── PsychicMiddlewares.h
├── PsychicRequest.cpp
├── PsychicRequest.h
├── PsychicResponse.cpp
├── PsychicResponse.h
├── PsychicRewrite.cpp
├── PsychicRewrite.h
├── PsychicStaticFileHander.cpp
├── PsychicStaticFileHandler.h
├── PsychicStreamResponse.cpp
├── PsychicStreamResponse.h
├── PsychicUploadHandler.cpp
├── PsychicUploadHandler.h
├── PsychicVersion.h
├── PsychicWebHandler.cpp
├── PsychicWebHandler.h
├── PsychicWebParameter.h
├── PsychicWebSocket.cpp
├── PsychicWebSocket.h
├── TemplatePrinter.cpp
├── TemplatePrinter.h
├── UrlEncode.cpp
├── UrlEncode.h
├── async_worker.cpp
├── async_worker.h
├── http_status.cpp
└── http_status.h
SYMBOL INDEX (286 symbols across 58 files)
FILE: benchmark/arduinomongoose/src/main.cpp
function connectToWifi (line 82) | bool connectToWifi()
function setup (line 147) | void setup()
function loop (line 227) | void loop()
FILE: benchmark/espasyncwebserver/src/main.cpp
function connectToWifi (line 94) | bool connectToWifi()
function onEvent (line 154) | void onEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEv...
function setup (line 224) | void setup()
function loop (line 283) | void loop()
FILE: benchmark/eventsource-client-test.js
function eventSourceClient (line 7) | async function eventSourceClient() {
FILE: benchmark/http-client-test.js
function fetchData (line 22) | function fetchData() {
FILE: benchmark/parse-websocket-test.js
function parseFile (line 12) | async function parseFile() {
FILE: benchmark/psychichttp/src/main.cpp
function connectToWifi (line 93) | bool connectToWifi()
function setup (line 153) | void setup()
function loop (line 216) | void loop()
FILE: benchmark/psychichttps/src/main.cpp
function connectToWifi (line 92) | bool connectToWifi()
function setup (line 156) | void setup()
function loop (line 235) | void loop()
FILE: benchmark/websocket-client-test.js
function websocketClient (line 8) | async function websocketClient() {
FILE: examples/esp-idf/main/main.cpp
type tm (line 77) | struct tm
function timeAvailable (line 80) | void timeAvailable(struct timeval* t)
function connectToWifi (line 93) | bool connectToWifi()
function setup (line 165) | void setup()
function loop (line 620) | void loop()
FILE: examples/platformio/src/main.cpp
type tm (line 115) | struct tm
function timeAvailable (line 118) | void timeAvailable(struct timeval* t)
function connectToWifi (line 131) | bool connectToWifi()
function setupSDCard (line 204) | bool setupSDCard()
function setup (line 250) | void setup()
function loop (line 712) | void loop()
FILE: examples/websockets/src/main.cpp
function connectToWifi (line 49) | bool connectToWifi()
function setup (line 112) | void setup()
function loop (line 194) | void loop()
FILE: src/ChunkPrinter.h
function class (line 7) | class ChunkPrinter : public Print
FILE: src/MultipartProcessor.cpp
function esp_err_t (line 38) | esp_err_t MultipartProcessor::process()
function esp_err_t (line 103) | esp_err_t MultipartProcessor::process(const char* body)
FILE: src/MultipartProcessor.h
function class (line 10) | class MultipartProcessor
FILE: src/PsychicClient.cpp
function httpd_handle_t (line 16) | httpd_handle_t PsychicClient::server()
function esp_err_t (line 27) | esp_err_t PsychicClient::close()
function IPAddress (line 35) | IPAddress PsychicClient::localIP()
type sockaddr_storage (line 58) | struct sockaddr_storage
type sockaddr (line 60) | struct sockaddr
type sockaddr_in (line 61) | struct sockaddr_in
type sockaddr_in (line 61) | struct sockaddr_in
function IPAddress (line 65) | IPAddress PsychicClient::remoteIP()
type sockaddr_storage (line 88) | struct sockaddr_storage
type sockaddr (line 90) | struct sockaddr
type sockaddr_in (line 91) | struct sockaddr_in
type sockaddr_in (line 91) | struct sockaddr_in
FILE: src/PsychicClient.h
function class (line 10) | class PsychicClient
FILE: src/PsychicCore.h
type HTTPAuthMethod (line 40) | enum HTTPAuthMethod {
type std (line 54) | typedef std::function<bool(PsychicRequest* request)> PsychicRequestFilte...
type std (line 57) | typedef std::function<esp_err_t()> PsychicMiddlewareNext;
type std (line 58) | typedef std::function<esp_err_t(PsychicRequest* request, PsychicResponse...
type std (line 61) | typedef std::function<void(PsychicClient* client)> PsychicClientCallback;
type std (line 64) | typedef std::function<esp_err_t(PsychicRequest* request, PsychicResponse...
type std (line 65) | typedef std::function<esp_err_t(PsychicRequest* request, PsychicResponse...
type std (line 66) | typedef std::function<esp_err_t(PsychicRequest* request, const String& f...
type HTTPHeader (line 68) | struct HTTPHeader {
function class (line 73) | class DefaultHeaders
FILE: src/PsychicEndpoint.cpp
function PsychicEndpoint (line 23) | PsychicEndpoint* PsychicEndpoint::setHandler(PsychicHandler* handler)
function PsychicHandler (line 38) | PsychicHandler* PsychicEndpoint::handler()
function String (line 43) | String PsychicEndpoint::uri()
function esp_err_t (line 48) | esp_err_t PsychicEndpoint::requestCallback(httpd_req_t* req)
function httpd_uri_match_func_t (line 105) | httpd_uri_match_func_t PsychicEndpoint::getURIMatchFunction()
function PsychicEndpoint (line 115) | PsychicEndpoint* PsychicEndpoint::addFilter(PsychicRequestFilterFunction...
function PsychicEndpoint (line 121) | PsychicEndpoint* PsychicEndpoint::addMiddleware(PsychicMiddleware* middl...
function PsychicEndpoint (line 127) | PsychicEndpoint* PsychicEndpoint::addMiddleware(PsychicMiddlewareCallbac...
function esp_err_t (line 138) | esp_err_t PsychicEndpoint::process(PsychicRequest* request)
FILE: src/PsychicEndpoint.h
function class (line 13) | class PsychicEndpoint
FILE: src/PsychicEventSource.cpp
function PsychicEventSourceClient (line 39) | PsychicEventSourceClient* PsychicEventSource::getClient(int socket)
function PsychicEventSourceClient (line 49) | PsychicEventSourceClient* PsychicEventSource::getClient(PsychicClient* c...
function esp_err_t (line 54) | esp_err_t PsychicEventSource::handleRequest(PsychicRequest* request, Psy...
function PsychicEventSource (line 79) | PsychicEventSource* PsychicEventSource::onOpen(PsychicEventSourceClientC...
function PsychicEventSource (line 85) | PsychicEventSource* PsychicEventSource::onClose(PsychicEventSourceClient...
function esp_err_t (line 203) | esp_err_t PsychicEventSourceResponse::send()
function String (line 234) | String generateEventMessage(const char* message, const char* event, uint...
FILE: src/PsychicEventSource.h
type std (line 34) | typedef std::function<void(PsychicEventSourceClient* client)> PsychicEve...
type async_event_transfer_t (line 36) | typedef struct {
function class (line 45) | class PsychicEventSourceClient : public PsychicClient
function class (line 64) | class PsychicEventSource : public PsychicHandler
function class (line 89) | class PsychicEventSourceResponse : public PsychicResponseDelegate
FILE: src/PsychicFileResponse.cpp
function esp_err_t (line 118) | esp_err_t PsychicFileResponse::send()
FILE: src/PsychicFileResponse.h
function class (line 9) | class PsychicFileResponse : public PsychicResponseDelegate
FILE: src/PsychicHandler.cpp
function PsychicHandler (line 16) | PsychicHandler* PsychicHandler::addFilter(PsychicRequestFilterFunction fn)
function PsychicClient (line 43) | PsychicClient* PsychicHandler::checkForNewClient(PsychicClient* client)
function PsychicClient (line 74) | PsychicClient* PsychicHandler::getClient(int socket)
function PsychicClient (line 89) | PsychicClient* PsychicHandler::getClient(PsychicClient* client)
function PsychicHandler (line 104) | PsychicHandler* PsychicHandler::addMiddleware(PsychicMiddleware* middlew...
function PsychicHandler (line 113) | PsychicHandler* PsychicHandler::addMiddleware(PsychicMiddlewareCallback fn)
function esp_err_t (line 129) | esp_err_t PsychicHandler::process(PsychicRequest* request)
FILE: src/PsychicHandler.h
function class (line 16) | class PsychicHandler
FILE: src/PsychicHttpServer.cpp
function _netif_is_connected (line 89) | static bool _netif_is_connected(esp_netif_t* netif)
function esp_err_t (line 107) | esp_err_t PsychicHttpServer::start()
function esp_err_t (line 173) | esp_err_t PsychicHttpServer::_startServer()
function esp_err_t (line 178) | esp_err_t PsychicHttpServer::stop()
function esp_err_t (line 214) | esp_err_t PsychicHttpServer::_stopServer()
function esp_err_t (line 250) | esp_err_t PsychicHttpServer::restart()
function httpd_uri_match_func_t (line 263) | httpd_uri_match_func_t PsychicHttpServer::getURIMatchFunction()
function PsychicHandler (line 273) | PsychicHandler* PsychicHttpServer::addHandler(PsychicHandler* handler)
function PsychicRewrite (line 285) | PsychicRewrite* PsychicHttpServer::addRewrite(PsychicRewrite* rewrite)
function PsychicRewrite (line 297) | PsychicRewrite* PsychicHttpServer::rewrite(const char* from, const char*...
function PsychicEndpoint (line 302) | PsychicEndpoint* PsychicHttpServer::on(const char* uri)
function PsychicEndpoint (line 307) | PsychicEndpoint* PsychicHttpServer::on(const char* uri, int method)
function PsychicEndpoint (line 314) | PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHandler* ...
function PsychicEndpoint (line 319) | PsychicEndpoint* PsychicHttpServer::on(const char* uri, int method, Psyc...
function PsychicEndpoint (line 358) | PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHttpReque...
function PsychicEndpoint (line 363) | PsychicEndpoint* PsychicHttpServer::on(const char* uri, int method, Psyc...
function PsychicEndpoint (line 372) | PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicJsonReque...
function PsychicEndpoint (line 377) | PsychicEndpoint* PsychicHttpServer::on(const char* uri, int method, Psyc...
function PsychicHttpServer (line 416) | PsychicHttpServer* PsychicHttpServer::addFilter(PsychicRequestFilterFunc...
function PsychicHttpServer (line 434) | PsychicHttpServer* PsychicHttpServer::addMiddleware(PsychicMiddleware* m...
function PsychicHttpServer (line 443) | PsychicHttpServer* PsychicHttpServer::addMiddleware(PsychicMiddlewareCal...
function esp_err_t (line 479) | esp_err_t PsychicHttpServer::requestHandler(httpd_req_t* req)
function esp_err_t (line 511) | esp_err_t PsychicHttpServer::_process(PsychicRequest* request)
function esp_err_t (line 533) | esp_err_t PsychicHttpServer::notFoundHandler(httpd_req_t* req, httpd_err...
function esp_err_t (line 551) | esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest* requ...
function esp_err_t (line 561) | esp_err_t PsychicHttpServer::openCallback(httpd_handle_t hd, int sockfd)
function PsychicStaticFileHandler (line 616) | PsychicStaticFileHandler* PsychicHttpServer::serveStatic(const char* uri...
function PsychicClient (line 635) | PsychicClient* PsychicHttpServer::getClient(int socket)
function PsychicClient (line 644) | PsychicClient* PsychicHttpServer::getClient(httpd_req_t* req)
function esp_netif_t (line 659) | static esp_netif_t* _find_netif_by_ip(const IPAddress& addr)
function ON_STA_FILTER (line 671) | bool ON_STA_FILTER(PsychicRequest* request)
function ON_AP_FILTER (line 679) | bool ON_AP_FILTER(PsychicRequest* request)
function String (line 687) | String urlDecode(const char* encoded)
function psychic_uri_match_simple (line 720) | bool psychic_uri_match_simple(const char* uri1, const char* uri2, size_t...
function psychic_uri_match_regex (line 727) | bool psychic_uri_match_regex(const char* uri1, const char* uri2, size_t ...
FILE: src/PsychicHttpServer.h
function virtual (line 77) | virtual void setCertificate(const char* cert, const char* private_key) {}
function virtual (line 78) | virtual void setCertificate(const uint8_t* cert, size_t cert_size, const...
function isRunning (line 81) | bool isRunning() { return _running; }
function esp_err_t (line 82) | esp_err_t begin() { return start(); }
function esp_err_t (line 83) | esp_err_t end() { return stop(); }
function count (line 104) | int count() { return _clients.size(); }
FILE: src/PsychicHttpsServer.cpp
function esp_err_t (line 55) | esp_err_t PsychicHttpsServer::_startServer()
function esp_err_t (line 63) | esp_err_t PsychicHttpsServer::_stopServer()
FILE: src/PsychicHttpsServer.h
function class (line 19) | class PsychicHttpsServer : public PsychicHttpServer
FILE: src/PsychicJson.cpp
function JsonVariant (line 24) | JsonVariant& PsychicJsonResponse::getRoot()
function esp_err_t (line 34) | esp_err_t PsychicJsonResponse::send()
function esp_err_t (line 103) | esp_err_t PsychicJsonHandler::handleRequest(PsychicRequest* request, Psy...
FILE: src/PsychicJson.h
function class (line 33) | class PsychicJsonResponse : public PsychicResponseDelegate
function class (line 66) | class PsychicJsonHandler : public PsychicWebHandler
FILE: src/PsychicMiddleware.cpp
function esp_err_t (line 3) | esp_err_t PsychicMiddlewareFunction::run(PsychicRequest* request, Psychi...
FILE: src/PsychicMiddleware.h
function class (line 13) | class PsychicMiddleware
function class (line 27) | class PsychicMiddlewareFunction : public PsychicMiddleware
FILE: src/PsychicMiddlewareChain.cpp
function esp_err_t (line 30) | esp_err_t PsychicMiddlewareChain::runChain(PsychicRequest* request, Psyc...
FILE: src/PsychicMiddlewareChain.h
function class (line 13) | class PsychicMiddlewareChain
FILE: src/PsychicMiddlewares.cpp
function esp_err_t (line 7) | esp_err_t LoggingMiddleware::run(PsychicRequest* request, PsychicRespons...
function AuthenticationMiddleware (line 68) | AuthenticationMiddleware& AuthenticationMiddleware::setUsername(const ch...
function AuthenticationMiddleware (line 74) | AuthenticationMiddleware& AuthenticationMiddleware::setPassword(const ch...
function AuthenticationMiddleware (line 80) | AuthenticationMiddleware& AuthenticationMiddleware::setRealm(const char*...
function AuthenticationMiddleware (line 86) | AuthenticationMiddleware& AuthenticationMiddleware::setAuthMethod(HTTPAu...
function AuthenticationMiddleware (line 92) | AuthenticationMiddleware& AuthenticationMiddleware::setAuthFailureMessag...
function esp_err_t (line 107) | esp_err_t AuthenticationMiddleware::run(PsychicRequest* request, Psychic...
function CorsMiddleware (line 122) | CorsMiddleware& CorsMiddleware::setOrigin(const char* origin)
function CorsMiddleware (line 128) | CorsMiddleware& CorsMiddleware::setMethods(const char* methods)
function CorsMiddleware (line 134) | CorsMiddleware& CorsMiddleware::setHeaders(const char* headers)
function CorsMiddleware (line 140) | CorsMiddleware& CorsMiddleware::setAllowCredentials(bool credentials)
function CorsMiddleware (line 146) | CorsMiddleware& CorsMiddleware::setMaxAge(uint32_t seconds)
function esp_err_t (line 161) | esp_err_t CorsMiddleware::run(PsychicRequest* request, PsychicResponse* ...
FILE: src/PsychicMiddlewares.h
function class (line 10) | class LoggingMiddleware : public PsychicMiddleware
function class (line 21) | class AuthenticationMiddleware : public PsychicMiddleware
function class (line 51) | class CorsMiddleware : public PsychicMiddleware
FILE: src/PsychicRequest.cpp
function PsychicHttpServer (line 57) | PsychicHttpServer* PsychicRequest::server()
function httpd_req_t (line 62) | httpd_req_t* PsychicRequest::request()
function PsychicClient (line 67) | PsychicClient* PsychicRequest::client()
function PsychicEndpoint (line 72) | PsychicEndpoint* PsychicRequest::endpoint()
function String (line 98) | const String PsychicRequest::getFilename()
function ContentDisposition (line 124) | const ContentDisposition PsychicRequest::getContentDisposition()
function esp_err_t (line 155) | esp_err_t PsychicRequest::loadBody()
function http_method (line 200) | http_method PsychicRequest::method()
function String (line 205) | const String PsychicRequest::methodStr()
function String (line 210) | const String PsychicRequest::path()
function String (line 219) | const String& PsychicRequest::uri()
function String (line 224) | const String& PsychicRequest::query()
function String (line 234) | const String PsychicRequest::header(const char* name)
function String (line 252) | const String PsychicRequest::host()
function String (line 257) | const String PsychicRequest::contentType()
function String (line 267) | const String& PsychicRequest::body()
function esp_err_t (line 295) | esp_err_t PsychicRequest::getCookie(const char* key, char* buffer, size_...
function String (line 300) | String PsychicRequest::getCookie(const char* key)
function PsychicWebParameter (line 391) | PsychicWebParameter* PsychicRequest::addParam(const String& name, const ...
function PsychicWebParameter (line 399) | PsychicWebParameter* PsychicRequest::addParam(PsychicWebParameter* param)
function PsychicWebParameter (line 416) | PsychicWebParameter* PsychicRequest::getParam(const char* key)
function PsychicWebParameter (line 425) | PsychicWebParameter* PsychicRequest::getParam(const char* key, bool isPo...
function String (line 438) | const String PsychicRequest::getSessionKey(const String& key)
function String (line 452) | static const String md5str(const String& in)
function String (line 561) | const String PsychicRequest::_extractParam(const String& authReq, const ...
function String (line 569) | const String PsychicRequest::_getRandomHexString()
function esp_err_t (line 579) | esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, con...
FILE: src/PsychicRequest.h
type std (line 14) | typedef std::map<String, String> SessionData;
type Disposition (line 16) | enum Disposition {
type ContentDisposition (line 23) | struct ContentDisposition {
function class (line 29) | class PsychicRequest
FILE: src/PsychicResponse.cpp
type tm (line 51) | struct tm
function esp_err_t (line 97) | esp_err_t PsychicResponse::send()
function esp_err_t (line 128) | esp_err_t PsychicResponse::sendChunk(uint8_t* chunk, size_t chunksize)
function esp_err_t (line 143) | esp_err_t PsychicResponse::finishChunking()
function esp_err_t (line 149) | esp_err_t PsychicResponse::redirect(const char* url)
function esp_err_t (line 157) | esp_err_t PsychicResponse::send(int code)
function esp_err_t (line 163) | esp_err_t PsychicResponse::send(const char* content)
function esp_err_t (line 173) | esp_err_t PsychicResponse::send(const char* contentType, const char* con...
function esp_err_t (line 182) | esp_err_t PsychicResponse::send(int code, const char* contentType, const...
function esp_err_t (line 190) | esp_err_t PsychicResponse::send(int code, const char* contentType, const...
function esp_err_t (line 198) | esp_err_t PsychicResponse::error(httpd_err_code_t code, const char* mess...
function httpd_req_t (line 203) | httpd_req_t* PsychicResponse::request()
FILE: src/PsychicResponse.h
function class (line 9) | class PsychicResponse
function class (line 63) | class PsychicResponseDelegate
function setContent (line 86) | void setContent(const char* content) { _response->setContent(content); }
function setContent (line 87) | void setContent(const uint8_t* content, size_t len) { _response->setCont...
function getContentLength (line 90) | size_t getContentLength() { return _response->getContentLength(); }
function esp_err_t (line 92) | esp_err_t send() { return _response->send(); }
function sendHeaders (line 93) | void sendHeaders() { _response->sendHeaders(); }
function esp_err_t (line 95) | esp_err_t sendChunk(uint8_t* chunk, size_t chunksize) { return _response...
function esp_err_t (line 96) | esp_err_t finishChunking() { return _response->finishChunking(); }
function esp_err_t (line 98) | esp_err_t redirect(const char* url) { return _response->redirect(url); }
function esp_err_t (line 99) | esp_err_t send(int code) { return _response->send(code); }
function esp_err_t (line 100) | esp_err_t send(const char* content) { return _response->send(content); }
function esp_err_t (line 101) | esp_err_t send(const char* contentType, const char* content) { return _r...
function esp_err_t (line 102) | esp_err_t send(int code, const char* contentType, const char* content) {...
function esp_err_t (line 103) | esp_err_t send(int code, const char* contentType, const uint8_t* content...
function esp_err_t (line 104) | esp_err_t error(httpd_err_code_t code, const char* message) { return _re...
function httpd_req_t (line 106) | httpd_req_t* request() { return _response->request(); }
FILE: src/PsychicRewrite.cpp
function PsychicRewrite (line 24) | PsychicRewrite* PsychicRewrite::setFilter(PsychicRequestFilterFunction fn)
function String (line 34) | const String& PsychicRewrite::from(void) const
function String (line 38) | const String& PsychicRewrite::toUrl(void) const
function String (line 43) | const String& PsychicRewrite::params(void) const
FILE: src/PsychicRewrite.h
function class (line 10) | class PsychicRewrite {
FILE: src/PsychicStaticFileHander.cpp
function PsychicStaticFileHandler (line 32) | PsychicStaticFileHandler* PsychicStaticFileHandler::setIsDir(bool isDir)
function PsychicStaticFileHandler (line 38) | PsychicStaticFileHandler* PsychicStaticFileHandler::setDefaultFile(const...
function PsychicStaticFileHandler (line 44) | PsychicStaticFileHandler* PsychicStaticFileHandler::setCacheControl(cons...
function PsychicStaticFileHandler (line 50) | PsychicStaticFileHandler* PsychicStaticFileHandler::setLastModified(cons...
function PsychicStaticFileHandler (line 56) | PsychicStaticFileHandler* PsychicStaticFileHandler::setLastModified(stru...
function esp_err_t (line 167) | esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest* reques...
FILE: src/PsychicStaticFileHandler.h
function class (line 10) | class PsychicStaticFileHandler : public PsychicWebHandler
FILE: src/PsychicStreamResponse.cpp
function esp_err_t (line 29) | esp_err_t PsychicStreamResponse::beginSend()
function esp_err_t (line 50) | esp_err_t PsychicStreamResponse::endSend()
FILE: src/PsychicUploadHandler.cpp
function esp_err_t (line 13) | esp_err_t PsychicUploadHandler::handleRequest(PsychicRequest* request, P...
function esp_err_t (line 57) | esp_err_t PsychicUploadHandler::_basicUploadHandler(PsychicRequest* requ...
function esp_err_t (line 122) | esp_err_t PsychicUploadHandler::_multipartUploadHandler(PsychicRequest* ...
function PsychicUploadHandler (line 128) | PsychicUploadHandler* PsychicUploadHandler::onUpload(PsychicUploadCallba...
FILE: src/PsychicUploadHandler.h
function class (line 14) | class PsychicUploadHandler : public PsychicWebHandler
FILE: src/PsychicWebHandler.cpp
function esp_err_t (line 16) | esp_err_t PsychicWebHandler::handleRequest(PsychicRequest* request, Psyc...
function PsychicWebHandler (line 52) | PsychicWebHandler* PsychicWebHandler::onRequest(PsychicHttpRequestCallba...
function PsychicWebHandler (line 70) | PsychicWebHandler* PsychicWebHandler::onOpen(PsychicClientCallback fn)
function PsychicWebHandler (line 76) | PsychicWebHandler* PsychicWebHandler::onClose(PsychicClientCallback fn)
FILE: src/PsychicWebHandler.h
function class (line 13) | class PsychicWebHandler : public PsychicHandler
FILE: src/PsychicWebParameter.h
function class (line 8) | class PsychicWebParameter
FILE: src/PsychicWebSocket.cpp
function PsychicWebSocketClient (line 16) | PsychicWebSocketClient* PsychicWebSocketRequest::client()
function esp_err_t (line 21) | esp_err_t PsychicWebSocketRequest::reply(httpd_ws_frame_t* ws_pkt)
function esp_err_t (line 26) | esp_err_t PsychicWebSocketRequest::reply(httpd_ws_type_t op, const void*...
function esp_err_t (line 38) | esp_err_t PsychicWebSocketRequest::reply(const char* buf)
function esp_err_t (line 75) | esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_frame_t* ws_pkt)
function esp_err_t (line 80) | esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_type_t op, const ...
function esp_err_t (line 113) | esp_err_t PsychicWebSocketClient::sendMessage(const char* buf)
function PsychicWebSocketClient (line 129) | PsychicWebSocketClient* PsychicWebSocketHandler::getClient(int socket)
function PsychicWebSocketClient (line 142) | PsychicWebSocketClient* PsychicWebSocketHandler::getClient(PsychicClient...
function esp_err_t (line 184) | esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest* request...
function PsychicWebSocketHandler (line 259) | PsychicWebSocketHandler* PsychicWebSocketHandler::onOpen(PsychicWebSocke...
function PsychicWebSocketHandler (line 265) | PsychicWebSocketHandler* PsychicWebSocketHandler::onFrame(PsychicWebSock...
function PsychicWebSocketHandler (line 271) | PsychicWebSocketHandler* PsychicWebSocketHandler::onClose(PsychicWebSock...
FILE: src/PsychicWebSocket.h
type std (line 11) | typedef std::function<void(PsychicWebSocketClient* client)> PsychicWebSo...
type std (line 12) | typedef std::function<esp_err_t(PsychicWebSocketRequest* request, httpd_...
function class (line 14) | class PsychicWebSocketClient : public PsychicClient
function class (line 28) | class PsychicWebSocketRequest : public PsychicRequest
function class (line 44) | class PsychicWebSocketHandler : public PsychicHandler
FILE: src/TemplatePrinter.h
type std (line 21) | typedef std::function<bool(Print& output, const char* parameter)> Templa...
type std (line 22) | typedef std::function<void(TemplatePrinter& printer)> TemplateSourceCall...
function class (line 24) | class TemplatePrinter : public Print
FILE: src/UrlEncode.cpp
function String (line 7) | String urlEncode(const char *msg) {
function String (line 25) | String urlEncode(String msg) {
FILE: src/async_worker.cpp
function is_on_async_worker_thread (line 3) | bool is_on_async_worker_thread(void)
function esp_err_t (line 16) | esp_err_t submit_async_req(httpd_req_t* req, httpd_req_handler_t handler)
function async_req_worker_task (line 54) | void async_req_worker_task(void* p)
function start_async_req_workers (line 85) | void start_async_req_workers(void)
type httpd_req_aux (line 145) | struct httpd_req_aux {
type sock_db (line 146) | struct sock_db
type resp_hdr (line 154) | struct resp_hdr {
type http_parser_url (line 158) | struct http_parser_url
function esp_err_t (line 168) | esp_err_t httpd_req_async_handler_begin(httpd_req_t *r, httpd_req_t **out)
function esp_err_t (line 199) | esp_err_t httpd_req_async_handler_complete(httpd_req_t* r)
FILE: src/async_worker.h
type esp_err_t (line 28) | typedef esp_err_t (*httpd_req_handler_t)(httpd_req_t* req);
type httpd_async_req_t (line 30) | typedef struct
FILE: src/http_status.cpp
function http_informational (line 3) | bool http_informational(int code)
function http_success (line 8) | bool http_success(int code)
function http_redirection (line 13) | bool http_redirection(int code)
function http_client_error (line 18) | bool http_client_error(int code)
function http_server_error (line 23) | bool http_server_error(int code)
function http_failure (line 28) | bool http_failure(int code)
Condensed preview — 158 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,360K chars).
[
{
"path": ".clang-format",
"chars": 601,
"preview": "Language: Cpp\nBasedOnStyle: LLVM\n\nAccessModifierOffset: -2\nAlignConsecutiveMacros: true\nAllowAllArgumentsOnNextLine: fal"
},
{
"path": ".github/workflows/arduino.yml",
"chars": 515,
"preview": "name: Arduino Lint\n\non:\n push:\n branches: []\n pull_request:\n branches: []\n schedule:\n - cron: \"0 1 * * 6\" # "
},
{
"path": ".github/workflows/esp-idf.yml",
"chars": 1158,
"preview": "name: ESP-IDF\n\non:\n push:\n branches: []\n pull_request:\n branches: []\n schedule:\n - cron: \"0 1 * * 6\" # Every"
},
{
"path": ".github/workflows/platformio.yml",
"chars": 1681,
"preview": "name: Platform IO\n\non:\n push:\n branches: []\n pull_request:\n branches: []\n schedule:\n - cron: \"0 1 * * 6\" # E"
},
{
"path": ".github/workflows/release.yml",
"chars": 2156,
"preview": "---\n\nname: Release to Platform IO and Arduino\n\non:\n release:\n types:\n - released\n\njobs:\n release_number:\n n"
},
{
"path": ".gitignore",
"chars": 904,
"preview": "_secret.h\n.$request flow.drawio.bkp\n.$request flow.drawio.dtmp\n.clang_complete\n.gcc-flags.json\n.pio\n.pioenvs\n.pioenvs\n.p"
},
{
"path": ".gitmodules",
"chars": 505,
"preview": "[submodule \"examples/esp-idf/components/ArduinoJson\"]\n\tpath = examples/esp-idf/components/ArduinoJson\n\turl = https://git"
},
{
"path": "CHANGELOG.md",
"chars": 5909,
"preview": "## 2.2.0\n\n- fix: memory leaks — add PsychicEndpoint destructor to delete handler; removeEndpoint/removeHandler/removeRew"
},
{
"path": "CMakeLists.txt",
"chars": 312,
"preview": "set(COMPONENT_SRCDIRS\n \"src\"\n)\n\nset(COMPONENT_ADD_INCLUDEDIRS\n \"src\"\n)\n\nset(COMPONENT_REQUIRES\n \"arduino-esp32\""
},
{
"path": "LICENSE",
"chars": 1092,
"preview": "Copyright (c) 2024 Jeremy Poulter, Zachary Smith, and Mathieu Carbou\n\nPermission is hereby granted, free of charge, to a"
},
{
"path": "README.md",
"chars": 43122,
"preview": "# PsychicHttp - HTTP on your ESP 🧙🔮\n\nPsychicHttp is a webserver library for ESP32 + Arduino framework which uses the [ES"
},
{
"path": "RELEASE.md",
"chars": 320,
"preview": "* Update CHANGELOG\n* Bump version in src/PsychicVersion.h\n* Bump version in library.json\n* Bump version in library.prope"
},
{
"path": "benchmark/arduinomongoose/.gitignore",
"chars": 103,
"preview": ".pio\n.vscode/\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "benchmark/arduinomongoose/include/README",
"chars": 1386,
"preview": "\nThis directory is intended for project header files.\n\nA header file is a file containing C declarations and macro defin"
},
{
"path": "benchmark/arduinomongoose/lib/README",
"chars": 1037,
"preview": "\nThis directory is intended for project specific (private) libraries.\nPlatformIO will compile them to static libraries a"
},
{
"path": "benchmark/arduinomongoose/platformio.ini",
"chars": 616,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "benchmark/arduinomongoose/src/main.cpp",
"chars": 8804,
"preview": "/* Wi-Fi STA Connect and Disconnect Example\n\n This example code is in the Public Domain (or CC0 licensed, at your opti"
},
{
"path": "benchmark/arduinomongoose/test/README",
"chars": 518,
"preview": "\nThis directory is intended for PlatformIO Test Runner and project tests.\n\nUnit Testing is a software testing method by "
},
{
"path": "benchmark/espasyncwebserver/.gitignore",
"chars": 103,
"preview": ".pio\n.vscode/\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "benchmark/espasyncwebserver/include/README",
"chars": 1386,
"preview": "\nThis directory is intended for project header files.\n\nA header file is a file containing C declarations and macro defin"
},
{
"path": "benchmark/espasyncwebserver/lib/README",
"chars": 1037,
"preview": "\nThis directory is intended for project specific (private) libraries.\nPlatformIO will compile them to static libraries a"
},
{
"path": "benchmark/espasyncwebserver/platformio.ini",
"chars": 1152,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "benchmark/espasyncwebserver/src/main.cpp",
"chars": 11878,
"preview": "/* Wi-Fi STA Connect and Disconnect Example\n\n This example code is in the Public Domain (or CC0 licensed, at your opti"
},
{
"path": "benchmark/espasyncwebserver/src/secret.h",
"chars": 59,
"preview": "#define WIFI_SSID \"Your_SSID\"\n#define WIFI_PASS \"Your_PASS\""
},
{
"path": "benchmark/espasyncwebserver/test/README",
"chars": 518,
"preview": "\nThis directory is intended for PlatformIO Test Runner and project tests.\n\nUnit Testing is a software testing method by "
},
{
"path": "benchmark/eventsource-client-test.js",
"chars": 975,
"preview": "#!/usr/bin/env node\n//stress test the client opening/closing code\n\nconst EventSource = require('eventsource');\nconst url"
},
{
"path": "benchmark/http-client-test.js",
"chars": 879,
"preview": "#!/usr/bin/env node\n//stress test the http request code\n\nconst axios = require('axios');\n\nconst url = 'http://psychic.lo"
},
{
"path": "benchmark/loadtest-http.sh",
"chars": 1254,
"preview": "#!/usr/bin/env bash\n#Command to install the testers:\n# npm install\n\nTEST_IP=\"psychic.local\"\nTEST_TIME=10\n#LOG_FILE=psych"
},
{
"path": "benchmark/loadtest-websocket.sh",
"chars": 1301,
"preview": "#!/usr/bin/env bash\n#Command to install the testers:\n# npm install\n\nTEST_IP=\"psychic.local\"\nTEST_TIME=60\nLOG_FILE=psychi"
},
{
"path": "benchmark/package.json",
"chars": 183,
"preview": "{\n \"dependencies\": {\n \"autocannon\": \"^7.15.0\",\n \"axios\": \"^1.6.2\",\n \"csv-writer\": \"^1.6.0\",\n \"eventsource\":"
},
{
"path": "benchmark/parse-http-test.js",
"chars": 1741,
"preview": "const fs = require('fs');\nconst createCsvWriter = require('csv-writer').createObjectCsvWriter;\n\n// Get the input and out"
},
{
"path": "benchmark/parse-websocket-test.js",
"chars": 1991,
"preview": "const fs = require('fs');\nconst readline = require('readline');\n\nif (process.argv.length !== 4) {\n console.error('Usa"
},
{
"path": "benchmark/psychichttp/.gitignore",
"chars": 103,
"preview": ".pio\n.vscode/\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "benchmark/psychichttp/include/README",
"chars": 1386,
"preview": "\nThis directory is intended for project header files.\n\nA header file is a file containing C declarations and macro defin"
},
{
"path": "benchmark/psychichttp/lib/README",
"chars": 1037,
"preview": "\nThis directory is intended for project specific (private) libraries.\nPlatformIO will compile them to static libraries a"
},
{
"path": "benchmark/psychichttp/platformio.ini",
"chars": 783,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "benchmark/psychichttp/src/main.cpp",
"chars": 8851,
"preview": "/* Wi-Fi STA Connect and Disconnect Example\n\n This example code is in the Public Domain (or CC0 licensed, at your opti"
},
{
"path": "benchmark/psychichttp/src/secret.h",
"chars": 59,
"preview": "#define WIFI_SSID \"Your_SSID\"\n#define WIFI_PASS \"Your_PASS\""
},
{
"path": "benchmark/psychichttp/test/README",
"chars": 518,
"preview": "\nThis directory is intended for PlatformIO Test Runner and project tests.\n\nUnit Testing is a software testing method by "
},
{
"path": "benchmark/psychichttps/.gitignore",
"chars": 103,
"preview": ".pio\n.vscode/\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
},
{
"path": "benchmark/psychichttps/data/server.crt",
"chars": 1158,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL\nBQAwJTEjMCEGA1UEAwwaRVNQMzI"
},
{
"path": "benchmark/psychichttps/data/server.key",
"chars": 1703,
"preview": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH\nJioMD7U7BitLgpcYPi8Cid1l7sn"
},
{
"path": "benchmark/psychichttps/include/README",
"chars": 1386,
"preview": "\nThis directory is intended for project header files.\n\nA header file is a file containing C declarations and macro defin"
},
{
"path": "benchmark/psychichttps/lib/README",
"chars": 1037,
"preview": "\nThis directory is intended for project specific (private) libraries.\nPlatformIO will compile them to static libraries a"
},
{
"path": "benchmark/psychichttps/platformio.ini",
"chars": 625,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "benchmark/psychichttps/src/main.cpp",
"chars": 8811,
"preview": "/* Wi-Fi STA Connect and Disconnect Example\n\n This example code is in the Public Domain (or CC0 licensed, at your opti"
},
{
"path": "benchmark/psychichttps/src/secret.h",
"chars": 59,
"preview": "#define WIFI_SSID \"Your_SSID\"\n#define WIFI_PASS \"Your_PASS\""
},
{
"path": "benchmark/psychichttps/test/README",
"chars": 518,
"preview": "\nThis directory is intended for PlatformIO Test Runner and project tests.\n\nUnit Testing is a software testing method by "
},
{
"path": "benchmark/results/arduinomongoose-http-loadtest.log",
"chars": 88724,
"preview": "\n\nCLIENTS: *** 1 ***\n\nRunning 60s test @ http://192.168.2.131/\n1 connections\n1 workers\n\n\n\u001b[90m┌─────────\u001b[39m\u001b[90m┬─────"
},
{
"path": "benchmark/results/arduinomongoose-websocket-loadtest.log",
"chars": 4840,
"preview": "\n\nCLIENTS: *** 1 ***\n\n\nTarget URL: ws://192.168.2.131/ws\nMax time (s): 60\nConcurrent clients: 1\nAgent: "
},
{
"path": "benchmark/results/espasync-http-loadtest.log",
"chars": 83151,
"preview": "\n\nCLIENTS: *** 1 ***\n\nRunning 60s test @ http://192.168.2.131/\n1 connections\n1 workers\n\n\n\u001b[90m┌─────────\u001b[39m\u001b[90m┬─────"
},
{
"path": "benchmark/results/espasync-websocket-loadtest.log",
"chars": 4897,
"preview": "\n\nCLIENTS: *** 1 ***\n\n\nTarget URL: ws://192.168.2.131/ws\nMax time (s): 60\nConcurrent clients: 1\nAgent: "
},
{
"path": "benchmark/results/psychic-http-loadtest.log",
"chars": 90414,
"preview": "\n\nCLIENTS: *** 1 ***\n\nRunning 60s test @ http://192.168.2.131/\n1 connections\n1 workers\n\n\n\u001b[90m┌─────────\u001b[39m\u001b[90m┬─────"
},
{
"path": "benchmark/results/psychic-ssl-http-loadtest.log",
"chars": 92061,
"preview": "\n\nCLIENTS: *** 1 ***\n\nRunning 60s test @ https://192.168.2.131/\n1 connections\n1 workers\n\n\n\u001b[90m┌─────────\u001b[39m\u001b[90m┬────"
},
{
"path": "benchmark/results/psychic-ssl-websocket-loadtest.log",
"chars": 4847,
"preview": "\n\nCLIENTS: *** 1 ***\n\n\nTarget URL: wss://192.168.2.131/ws\nMax time (s): 60\nConcurrent clients: 1\nAgent:"
},
{
"path": "benchmark/results/psychic-v1.1-http-loadtest.log",
"chars": 92370,
"preview": "\n\nCLIENTS: *** 1 ***\n\nRunning 60s test @ http://192.168.2.131/\n1 connections\n1 workers\n\n\n\u001b[90m┌─────────\u001b[39m\u001b[90m┬─────"
},
{
"path": "benchmark/results/psychic-v1.1-websocket-loadtest.log",
"chars": 4840,
"preview": "\n\nCLIENTS: *** 1 ***\n\n\nTarget URL: ws://192.168.2.131/ws\nMax time (s): 60\nConcurrent clients: 1\nAgent: "
},
{
"path": "benchmark/results/psychic-websocket-loadtest.log",
"chars": 4835,
"preview": "\n\nCLIENTS: *** 1 ***\n\n\nTarget URL: ws://192.168.2.131/ws\nMax time (s): 60\nConcurrent clients: 1\nAgent: "
},
{
"path": "benchmark/websocket-client-test.js",
"chars": 735,
"preview": "#!/usr/bin/env node\n//stress test the client open/close for websockets\n\nconst WebSocket = require('ws');\n\nconst uri = 'w"
},
{
"path": "component.mk",
"chars": 80,
"preview": "COMPONENT_ADD_INCLUDEDIRS := src\nCOMPONENT_SRCDIRS := src\nCXXFLAGS += -fno-rtti\n"
},
{
"path": "examples/esp-idf/.gitignore",
"chars": 96,
"preview": "build/\nsdkconfig\nsdkconfig.old\ncomponents/\nmanaged_components/\ndependencies.lock\nmain/_secret.h\n"
},
{
"path": "examples/esp-idf/CMakeLists.txt",
"chars": 689,
"preview": "# The following lines of boilerplate have to be in your project's\n# CMakeLists in this exact order for cmake to work cor"
},
{
"path": "examples/esp-idf/README.md",
"chars": 722,
"preview": "# PsychicHttp - ESP IDF Example\n* Download and install [ESP IDF 4.4.7](https://github.com/espressif/esp-idf/releases/ta"
},
{
"path": "examples/esp-idf/data/custom.txt",
"chars": 17,
"preview": "Custom text file."
},
{
"path": "examples/esp-idf/data/server.crt",
"chars": 1158,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL\nBQAwJTEjMCEGA1UEAwwaRVNQMzI"
},
{
"path": "examples/esp-idf/data/server.key",
"chars": 1703,
"preview": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH\nJioMD7U7BitLgpcYPi8Cid1l7sn"
},
{
"path": "examples/esp-idf/data/www/index.html",
"chars": 10565,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width="
},
{
"path": "examples/esp-idf/data/www/text.txt",
"chars": 11,
"preview": "Test File.\n"
},
{
"path": "examples/esp-idf/data/www-ap/index.html",
"chars": 438,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width="
},
{
"path": "examples/esp-idf/include/README",
"chars": 1386,
"preview": "\nThis directory is intended for project header files.\n\nA header file is a file containing C declarations and macro defin"
},
{
"path": "examples/esp-idf/lib/README",
"chars": 1037,
"preview": "\nThis directory is intended for project specific (private) libraries.\nPlatformIO will compile them to static libraries a"
},
{
"path": "examples/esp-idf/main/CMakeLists.txt",
"chars": 241,
"preview": "# This file was automatically generated for projects\n# without default 'CMakeLists.txt' file.\n\nidf_component_register(\n "
},
{
"path": "examples/esp-idf/main/idf_component.yml",
"chars": 45,
"preview": "dependencies:\n joltwallet/littlefs: ^1.16.4\n"
},
{
"path": "examples/esp-idf/main/main.cpp",
"chars": 22869,
"preview": "/*\n PsychicHTTP Server Example\n\n This example code is in the Public Domain (or CC0 licensed, at your option.)\n\n Unles"
},
{
"path": "examples/esp-idf/main/secret.h",
"chars": 59,
"preview": "#define WIFI_SSID \"Your_SSID\"\n#define WIFI_PASS \"Your_PASS\""
},
{
"path": "examples/esp-idf/partitions_custom.csv",
"chars": 268,
"preview": "# Name, Type, SubType, Offset, Size, Flags\nnvs, data, nvs, 0x11000, 0xC000\notadata, data, ota, 0x1D000, 0x2000\nphy"
},
{
"path": "examples/esp-idf/sdkconfig.defaults",
"chars": 1279,
"preview": "CONFIG_AUTOSTART_ARDUINO=y\n# CONFIG_WS2812_LED_ENABLE is not set\nCONFIG_FREERTOS_HZ=1000\n\nCONFIG_BOOTLOADER_COMPILER_OPT"
},
{
"path": "examples/platformio/.gitignore",
"chars": 117,
"preview": ".pio\n.vscode/\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\nsrc/_secret.h\n"
},
{
"path": "examples/platformio/data/custom.txt",
"chars": 17,
"preview": "Custom text file."
},
{
"path": "examples/platformio/data/server.crt",
"chars": 1158,
"preview": "-----BEGIN CERTIFICATE-----\nMIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL\nBQAwJTEjMCEGA1UEAwwaRVNQMzI"
},
{
"path": "examples/platformio/data/server.key",
"chars": 1703,
"preview": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH\nJioMD7U7BitLgpcYPi8Cid1l7sn"
},
{
"path": "examples/platformio/data/www/index.html",
"chars": 9923,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-widt"
},
{
"path": "examples/platformio/data/www/text.txt",
"chars": 11,
"preview": "Test File.\n"
},
{
"path": "examples/platformio/data/www/websocket-test.html",
"chars": 7041,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n\t<meta charset=\"UTF-8\">\n\t<meta name=\"viewport\" content=\"width=device-width, ini"
},
{
"path": "examples/platformio/data/www-ap/index.html",
"chars": 438,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width="
},
{
"path": "examples/platformio/platformio.ini",
"chars": 1601,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "examples/platformio/src/main.cpp",
"chars": 25274,
"preview": "/*\n PsychicHTTP Server Example\n\n This example code is in the Public Domain (or CC0 licensed, at your option.)\n\n Unles"
},
{
"path": "examples/platformio/src/secret.h",
"chars": 59,
"preview": "#define WIFI_SSID \"Your_SSID\"\n#define WIFI_PASS \"Your_PASS\""
},
{
"path": "examples/websockets/.gitignore",
"chars": 133,
"preview": ".pio\n.vscode/\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\nsrc/_secret.h\nlib"
},
{
"path": "examples/websockets/data/www/index.html",
"chars": 3493,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width="
},
{
"path": "examples/websockets/include/README",
"chars": 1386,
"preview": "\nThis directory is intended for project header files.\n\nA header file is a file containing C declarations and macro defin"
},
{
"path": "examples/websockets/lib/README",
"chars": 1037,
"preview": "\nThis directory is intended for project specific (private) libraries.\nPlatformIO will compile them to static libraries a"
},
{
"path": "examples/websockets/platformio.ini",
"chars": 765,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "examples/websockets/src/main.cpp",
"chars": 6480,
"preview": "/*\n PsychicHTTP Server Example\n\n This example code is in the Public Domain (or CC0 licensed, at your option.)\n\n Unles"
},
{
"path": "examples/websockets/src/secret.h",
"chars": 59,
"preview": "#define WIFI_SSID \"Your_SSID\"\n#define WIFI_PASS \"Your_PASS\""
},
{
"path": "examples/websockets/test/README",
"chars": 518,
"preview": "\nThis directory is intended for PlatformIO Test Runner and project tests.\n\nUnit Testing is a software testing method by "
},
{
"path": "idf_component.yml",
"chars": 600,
"preview": "## IDF Component Manager Manifest File\nversion: \"2.2.0\"\nlicense: \"MIT\"\ndescription: \"Asyncronous Webserver library for E"
},
{
"path": "library.json",
"chars": 993,
"preview": "{\n \"name\": \"PsychicHttp\",\n \"version\": \"2.2.0\",\n \"description\": \"Arduino style wrapper around ESP-IDF HTTP library. HT"
},
{
"path": "library.properties",
"chars": 457,
"preview": "name=PsychicHttp\nversion=2.2.0\nauthor=Zach Hoeken <hoeken@gmail.com>\nmaintainer=Zach Hoeken <hoeken@gmail.com>\nsentence="
},
{
"path": "middleware.md",
"chars": 861,
"preview": "# PsychicHandler\n\n- [x] create addMiddleware()\n- [x] create runMiddleware()\n- [ ] move all the handler::canHandle() stuf"
},
{
"path": "partitions-4MB.csv",
"chars": 301,
"preview": "# Name ,Type ,SubType ,Offset ,Size ,Flags\nnvs ,data ,nvs ,36K ,20K ,\notadata ,data ,ota ,56K "
},
{
"path": "platformio.ini",
"chars": 2711,
"preview": "; PlatformIO Project Configuration File\n;\n; Build options: build flags, source filter\n; Upload options: custom uploa"
},
{
"path": "request flow.drawio",
"chars": 4513,
"preview": "<mxfile host=\"Electron\" modified=\"2023-12-09T15:02:09.427Z\" agent=\"5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/5"
},
{
"path": "src/ChunkPrinter.cpp",
"chars": 1711,
"preview": "\n#include \"ChunkPrinter.h\"\n\nChunkPrinter::ChunkPrinter(PsychicResponse* response, uint8_t* buffer, size_t len) : _respon"
},
{
"path": "src/ChunkPrinter.h",
"chars": 518,
"preview": "#ifndef ChunkPrinter_h\n#define ChunkPrinter_h\n\n#include \"PsychicResponse.h\"\n#include <Print.h>\n\nclass ChunkPrinter : pub"
},
{
"path": "src/MultipartProcessor.cpp",
"chars": 12146,
"preview": "#include \"MultipartProcessor.h\"\n#include \"PsychicRequest.h\"\n\nenum\n{\n EXPECT_BOUNDARY,\n PARSE_HEADERS,\n WAIT_FOR_RETUR"
},
{
"path": "src/MultipartProcessor.h",
"chars": 955,
"preview": "#ifndef MULTIPART_PROCESSOR_H\n#define MULTIPART_PROCESSOR_H\n\n#include \"PsychicCore.h\"\n\n/*\n * MultipartProcessor - handle"
},
{
"path": "src/PsychicClient.cpp",
"chars": 2548,
"preview": "#include \"PsychicClient.h\"\n#include \"PsychicHttpServer.h\"\n#include <lwip/sockets.h>\n\nPsychicClient::PsychicClient(httpd_"
},
{
"path": "src/PsychicClient.h",
"chars": 779,
"preview": "#ifndef PsychicClient_h\n#define PsychicClient_h\n\n#include \"PsychicCore.h\"\n\n/*\n * PsychicClient :: Generic wrapper around"
},
{
"path": "src/PsychicCore.h",
"chars": 2565,
"preview": "#ifndef PsychicCore_h\n#define PsychicCore_h\n\n#define PH_TAG \"psychic\"\n\n#ifndef FILE_CHUNK_SIZE\n #define FILE_CHUNK_SIZE"
},
{
"path": "src/PsychicEndpoint.cpp",
"chars": 3896,
"preview": "#include \"PsychicEndpoint.h\"\n#include \"PsychicHttpServer.h\"\n\nPsychicEndpoint::PsychicEndpoint() : _server(NULL),\n "
},
{
"path": "src/PsychicEndpoint.h",
"chars": 1302,
"preview": "#ifndef PsychicEndpoint_h\n#define PsychicEndpoint_h\n\n#include \"PsychicCore.h\"\n\nclass PsychicHandler;\nclass PsychicMiddle"
},
{
"path": "src/PsychicEventSource.cpp",
"chars": 7216,
"preview": "/*\n Asynchronous WebServer library for Espressif MCUs\n\n Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n\n Thi"
},
{
"path": "src/PsychicEventSource.h",
"chars": 3269,
"preview": "/*\n Asynchronous WebServer library for Espressif MCUs\n\n Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n\n Thi"
},
{
"path": "src/PsychicFileResponse.cpp",
"chars": 5060,
"preview": "#include \"PsychicFileResponse.h\"\n#include \"PsychicRequest.h\"\n#include \"PsychicResponse.h\"\n#include <http_status.h>\n\nPsyc"
},
{
"path": "src/PsychicFileResponse.h",
"chars": 721,
"preview": "#ifndef PsychicFileResponse_h\n#define PsychicFileResponse_h\n\n#include \"PsychicCore.h\"\n#include \"PsychicResponse.h\"\n\nclas"
},
{
"path": "src/PsychicHandler.cpp",
"chars": 3087,
"preview": "#include \"PsychicHandler.h\"\n\nPsychicHandler::PsychicHandler()\n{\n}\n\nPsychicHandler::~PsychicHandler()\n{\n delete _chain;\n"
},
{
"path": "src/PsychicHandler.h",
"chars": 2090,
"preview": "#ifndef PsychicHandler_h\n#define PsychicHandler_h\n\n#include \"PsychicCore.h\"\n#include \"PsychicRequest.h\"\n\nclass PsychicEn"
},
{
"path": "src/PsychicHttp.h",
"chars": 859,
"preview": "#ifndef PsychicHttp_h\n#define PsychicHttp_h\n\n// #define ENABLE_ASYNC // This is something added in ESP-IDF 5.1.x where e"
},
{
"path": "src/PsychicHttpServer.cpp",
"chars": 20042,
"preview": "#include \"PsychicHttpServer.h\"\n#include \"PsychicEndpoint.h\"\n#include \"PsychicHandler.h\"\n#include \"PsychicJson.h\"\n#includ"
},
{
"path": "src/PsychicHttpServer.h",
"chars": 5180,
"preview": "#ifndef PsychicHttpServer_h\n#define PsychicHttpServer_h\n\n#include \"PsychicClient.h\"\n#include \"PsychicCore.h\"\n#include \"P"
},
{
"path": "src/PsychicHttpsServer.cpp",
"chars": 2195,
"preview": "#include \"PsychicHttpsServer.h\"\n\n#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE\n\nPsychicHttpsServer::PsychicHttpsServer(uint16_t "
},
{
"path": "src/PsychicHttpsServer.h",
"chars": 1684,
"preview": "#ifndef PsychicHttpsServer_h\n#define PsychicHttpsServer_h\n\n#include <sdkconfig.h>\n\n#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE"
},
{
"path": "src/PsychicJson.cpp",
"chars": 3546,
"preview": "#include \"PsychicJson.h\"\n\n#ifdef ARDUINOJSON_6_COMPATIBILITY\nPsychicJsonResponse::PsychicJsonResponse(PsychicResponse* r"
},
{
"path": "src/PsychicJson.h",
"chars": 2340,
"preview": "// PsychicJson.h\n/*\n Async Response to use with ArduinoJson and AsyncWebServer\n Written by Andrew Melvin (SticilFace) "
},
{
"path": "src/PsychicMiddleware.cpp",
"chars": 207,
"preview": "#include \"PsychicMiddleware.h\"\n\nesp_err_t PsychicMiddlewareFunction::run(PsychicRequest* request, PsychicResponse* respo"
},
{
"path": "src/PsychicMiddleware.h",
"chars": 885,
"preview": "#ifndef PsychicMiddleware_h\n#define PsychicMiddleware_h\n\n#include \"PsychicCore.h\"\n#include \"PsychicRequest.h\"\n#include \""
},
{
"path": "src/PsychicMiddlewareChain.cpp",
"chars": 1213,
"preview": "#include \"PsychicMiddlewareChain.h\"\n\nPsychicMiddlewareChain::~PsychicMiddlewareChain()\n{\n for (auto middleware : _middl"
},
{
"path": "src/PsychicMiddlewareChain.h",
"chars": 683,
"preview": "#ifndef PsychicMiddlewareChain_h\n#define PsychicMiddlewareChain_h\n\n#include \"PsychicCore.h\"\n#include \"PsychicMiddleware."
},
{
"path": "src/PsychicMiddlewares.cpp",
"chars": 4356,
"preview": "#include \"PsychicMiddlewares.h\"\n\nvoid LoggingMiddleware::setOutput(Print &output) {\n _out = &output;\n}\n\nesp_err_t Loggi"
},
{
"path": "src/PsychicMiddlewares.h",
"chars": 2364,
"preview": "#ifndef PsychicMiddlewares_h\n#define PsychicMiddlewares_h\n\n#include \"PsychicMiddleware.h\"\n\n#include <Stream.h>\n#include "
},
{
"path": "src/PsychicRequest.cpp",
"chars": 16146,
"preview": "#include \"PsychicRequest.h\"\n#include \"MultipartProcessor.h\"\n#include \"PsychicHttpServer.h\"\n#include \"http_status.h\"\n\nPsy"
},
{
"path": "src/PsychicRequest.h",
"chars": 5301,
"preview": "#ifndef PsychicRequest_h\n#define PsychicRequest_h\n\n#include \"PsychicClient.h\"\n#include \"PsychicCore.h\"\n#include \"Psychic"
},
{
"path": "src/PsychicResponse.cpp",
"chars": 5217,
"preview": "#include \"PsychicResponse.h\"\n#include \"PsychicRequest.h\"\n#include <http_status.h>\n\nPsychicResponse::PsychicResponse(Psyc"
},
{
"path": "src/PsychicResponse.h",
"chars": 4183,
"preview": "#ifndef PsychicResponse_h\n#define PsychicResponse_h\n\n#include \"PsychicCore.h\"\n#include \"time.h\"\n\nclass PsychicRequest;\n\n"
},
{
"path": "src/PsychicRewrite.cpp",
"chars": 1087,
"preview": "#include \"PsychicRewrite.h\"\n#include \"PsychicRequest.h\"\n\n PsychicRewrite::PsychicRewrite(const char* from, const char"
},
{
"path": "src/PsychicRewrite.h",
"chars": 701,
"preview": "#ifndef PsychicRewrite_h\n#define PsychicRewrite_h\n\n#include \"PsychicCore.h\"\n\n/*\n * REWRITE :: One instance can be handle"
},
{
"path": "src/PsychicStaticFileHander.cpp",
"chars": 5971,
"preview": "#include \"PsychicStaticFileHandler.h\"\n\n/*************************************/\n/* PsychicStaticFileHandler */\n/"
},
{
"path": "src/PsychicStaticFileHandler.h",
"chars": 1473,
"preview": "#ifndef PsychicStaticFileHandler_h\n#define PsychicStaticFileHandler_h\n\n#include \"PsychicCore.h\"\n#include \"PsychicFileRes"
},
{
"path": "src/PsychicStreamResponse.cpp",
"chars": 2175,
"preview": "#include \"PsychicStreamResponse.h\"\n#include \"PsychicRequest.h\"\n#include \"PsychicResponse.h\"\n\nPsychicStreamResponse::Psyc"
},
{
"path": "src/PsychicStreamResponse.h",
"chars": 830,
"preview": "#ifndef PsychicStreamResponse_h\n#define PsychicStreamResponse_h\n\n#include \"ChunkPrinter.h\"\n#include \"PsychicCore.h\"\n#inc"
},
{
"path": "src/PsychicUploadHandler.cpp",
"chars": 3662,
"preview": "#include \"PsychicUploadHandler.h\"\n\nPsychicUploadHandler::PsychicUploadHandler() : PsychicWebHandler(), _uploadCallback(n"
},
{
"path": "src/PsychicUploadHandler.h",
"chars": 847,
"preview": "#ifndef PsychicUploadHandler_h\n#define PsychicUploadHandler_h\n\n#include \"MultipartProcessor.h\"\n#include \"PsychicCore.h\"\n"
},
{
"path": "src/PsychicVersion.h",
"chars": 1681,
"preview": "// Copyright 2019 Espressif Systems (Shanghai) PTE LTD\n//\n// Licensed under the Apache License, Version 2.0 (the \"Licens"
},
{
"path": "src/PsychicWebHandler.cpp",
"chars": 2121,
"preview": "#include \"PsychicWebHandler.h\"\n\nPsychicWebHandler::PsychicWebHandler() : PsychicHandler(),\n "
},
{
"path": "src/PsychicWebHandler.h",
"chars": 973,
"preview": "#ifndef PsychicWebHandler_h\n#define PsychicWebHandler_h\n\n// #include \"PsychicCore.h\"\n// #include \"PsychicHttpServer.h\"\n/"
},
{
"path": "src/PsychicWebParameter.h",
"chars": 742,
"preview": "#ifndef PsychicWebParameter_h\n#define PsychicWebParameter_h\n\n/*\n * PARAMETER :: Chainable object to hold GET/POST and FI"
},
{
"path": "src/PsychicWebSocket.cpp",
"chars": 8543,
"preview": "#include \"PsychicWebSocket.h\"\n\n/*************************************/\n/* PsychicWebSocketRequest */\n/************"
},
{
"path": "src/PsychicWebSocket.h",
"chars": 2400,
"preview": "#ifndef PsychicWebSocket_h\n#define PsychicWebSocket_h\n\n#include \"PsychicCore.h\"\n#include \"PsychicRequest.h\"\n\nclass Psych"
},
{
"path": "src/TemplatePrinter.cpp",
"chars": 1987,
"preview": "/************************************************************\n\nTemplatePrinter Class\n\nA basic templating engine for a st"
},
{
"path": "src/TemplatePrinter.h",
"chars": 1351,
"preview": "#ifndef TemplatePrinter_h\n#define TemplatePrinter_h\n\n#include \"PsychicCore.h\"\n#include <Print.h>\n\n/*********************"
},
{
"path": "src/UrlEncode.cpp",
"chars": 678,
"preview": "// MIT License\n// Copyright (c) Masayuki Sugahara\n// https://github.com/plageoj/urlencode\n\n#include \"UrlEncode.h\"\n\nStrin"
},
{
"path": "src/UrlEncode.h",
"chars": 467,
"preview": "// MIT License\n// Copyright (c) Masayuki Sugahara\n// https://github.com/plageoj/urlencode\n\n#ifndef _PLAGEOJ_URLENCODE_H\n"
},
{
"path": "src/async_worker.cpp",
"chars": 6289,
"preview": "#include \"async_worker.h\"\n\nbool is_on_async_worker_thread(void)\n{\n // is our handle one of the known async handles?\n T"
},
{
"path": "src/async_worker.h",
"chars": 1415,
"preview": "#ifndef async_worker_h\n#define async_worker_h\n\n#include \"PsychicCore.h\"\n#include \"freertos/FreeRTOS.h\"\n#include \"freerto"
},
{
"path": "src/http_status.cpp",
"chars": 3999,
"preview": "#include \"http_status.h\"\n\nbool http_informational(int code)\n{\n return code >= 100 && code < 200;\n}\n\nbool http_success(i"
},
{
"path": "src/http_status.h",
"chars": 386,
"preview": "#ifndef MICRO_HTTP_STATUS_H\n#define MICRO_HTTP_STATUS_H\n\n#include <stdbool.h>\n\nbool http_informational(int code);\nbool h"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the hoeken/PsychicHttp GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 158 files (862.7 KB), approximately 404.0k tokens, and a symbol index with 286 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.