Merge pull request #585 from iranl/psychichttp2
PsychicHTTP v2-dev branch, add HTTPS server (PSRAM only), add HTTP Digest Authentication
This commit is contained in:
44
README.md
44
README.md
@@ -52,20 +52,16 @@ See the "[Connecting via Ethernet](#connecting-via-ethernet-optional)" section f
|
||||
|
||||
## Recommended ESP32 devices
|
||||
|
||||
- If WIFI6 is absolutely required: ESP32-C6
|
||||
- If PoE is required: Any of the above mentioned devices with PoE or any other ESP device in combination with a SPI Ethernet module ([W5500](https://www.aliexpress.com/w/wholesale-w5500.html)) and [PoE to Ethernet and USB type B/C splitter](https://aliexpress.com/w/wholesale-poe-splitter-usb-c.html)
|
||||
- If you want maximum performance and intend to run any or multiple of the following:
|
||||
- a Nuki Lock and Nuki Opener and/or
|
||||
- MQTT SSL and/or
|
||||
- HTTP SSL and/or
|
||||
- large amounts of keypad codes, timecontrol or authorization entries
|
||||
- Developing/debugging Nuki devices and/or Nuki Hub
|
||||
We don't recommend using single-core ESP32 devices (ESP32-C3, ESP32-C6, ESP32-H2, ESP32-Solo1).<br>
|
||||
Although NukiHub supports single-core devices, NukiHub uses both CPU cores (if available) to process tasks (e.g. HTTP server/MQTT client/BLE scanner/BLE client) and thus runs much better on dual-core devices.<br>
|
||||
|
||||
An ESP32-S3 with 2MB of PSRAM or more (look for an ESP32-S3 with the designation N>=4 and R>=2 such as an ESP32-S3 N16R8)
|
||||
|
||||
- In general when buying a new device when size and a couple of dollars more or less are not an issue: An ESP32-S3 with 2MB of PSRAM or more.<br>
|
||||
When buying a new device in 2025 we can only recommend the ESP32-S3 with PSRAM (look for an ESP32-S3 with the designation N>=4 and R>=2 such as an ESP32-S3 N16R8).<br>
|
||||
The ESP32-S3 is a dual-core CPU with many GPIO's, ability to enlarge RAM using PSRAM, ability to connect Ethernet modules over SPI and optionally power the device with a PoE splitter.<br>
|
||||
The only functions missing from the ESP32-S3 as compared to other ESP devices is the ability to use some Ethernet modules only supported by the original ESP32 (and ESP32-P4) and the ability to connect over WIFI6 (C6 or ESP32-P4 with C6 module)
|
||||
|
||||
The ESP32-S3 is a dual-core CPU with many GPIO's, ability to enlarge RAM using PSRAM, ability to connect Ethernet modules over SPI and optionally power the device with a PoE splitter. The only functions missing from the ESP32-S3 as compared to other ESP devices is the ability to use some Ethernet modules only supported by the original ESP32 and the ability to connect over WIFI6 (C6)
|
||||
Other considerations:
|
||||
- If PoE is required: An ESP32-S3 with PSRAM in combination with a SPI Ethernet module ([W5500](https://www.aliexpress.com/w/wholesale-w5500.html)) and [PoE to Ethernet and USB type B/C splitter](https://aliexpress.com/w/wholesale-poe-splitter-usb-c.html) or the M5stack Atom S3R with the M5stack AtomPoe W5500 module
|
||||
- If WIFI6 is absolutely required (it probably isn't): ESP32-C6
|
||||
|
||||
## Feature comparison
|
||||
|
||||
@@ -194,6 +190,8 @@ In a browser navigate to the IP address assigned to the ESP32.
|
||||
- RSSI Publish interval: Set to a positive integer to set the amount of seconds between updates to the maintenance/wifiRssi MQTT topic with the current Wi-Fi RSSI, set to -1 to disable, default 60.
|
||||
- Restart on disconnect: Enable to restart the Nuki Hub when disconnected from the network.
|
||||
- Check for Firmware Updates every 24h: Enable to allow the Nuki Hub to check the latest release of the Nuki Hub firmware on boot and every 24 hours. Requires the Nuki Hub to be able to connect to github.com. The latest version will be published to MQTT and will be visible on the main page of the Web Configurator.
|
||||
- HTTP SSL Certificate (PSRAM enabled devices only): Optionally set to the SSL certificate of the HTTPS server, see the "[HTTPS Server](#https-server-optional-psram-enabled-devices-only)" section of this README.
|
||||
- HTTP SSL Key (PSRAM enabled devices only): Optionally set to the SSL key of the HTTPS server, see the "[HTTPS Server](#https-server-optional-psram-enabled-devices-only)" section of this README.
|
||||
|
||||
#### IP Address assignment
|
||||
|
||||
@@ -274,8 +272,9 @@ In a browser navigate to the IP address assigned to the ESP32.
|
||||
|
||||
#### Credentials
|
||||
|
||||
- User: Pick a username to enable HTTP Basic authentication for the Web Configuration, Set to "#" to disable authentication.
|
||||
- Password/Retype password: Pick a password to enable HTTP Basic authentication for the Web Configuration.
|
||||
- User: Pick a username to enable HTTP authentication for the Web Configuration, Set to "#" to disable authentication.
|
||||
- Password/Retype password: Pick a password to enable HTTP authentication for the Web Configuration.
|
||||
- Use Digest Authentication (more secure): Enable to use HTTP Digest Authentication instead of HTTP Basic Authentication. Digest authentication is more secure, especially over unencrypted (HTTP) connections.
|
||||
|
||||
#### Nuki Lock PIN / Nuki Opener PIN
|
||||
|
||||
@@ -574,6 +573,23 @@ openssl req -new -key server.key -out server.csr -subj "/C=US/ST=YourState/L=You
|
||||
# sign it
|
||||
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650
|
||||
```
|
||||
|
||||
## HTTPS Server (optional, PSRAM enabled devices only)
|
||||
|
||||
The Webconfigurator can use/force HTTPS on PSRAM enabled devices.<br>
|
||||
To enable SSL encryption, supply the certificate and key in the Network configuration page and reboot NukiHub.<br>
|
||||
|
||||
Example self-signed certificate creation for your HTTPS server:
|
||||
```console
|
||||
|
||||
# make a Certificate and key pair, MAKE SURE THE CN MATCHES THE DOMAIN AT WHICH NUKIHUB IS AVAILABLE
|
||||
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout nukihub.key -out nukihub.crt -subj "/C=US/ST=YourState/L=YourCity/O=YourOrganization/OU=YourUnit/CN=YourCAName"
|
||||
|
||||
```
|
||||
|
||||
Although you can use the HTTPS server in this way your client device will not trust the certificate by default.
|
||||
An option would be to configure a proxy SSL server (such as Caddy, Traefik, nginx) with a non-self signed (e.g. let's encrypt) SSL certificate and have this proxy server connect to NukiHub over SSL and trust the self-signed NukiHub certificate for this connection.
|
||||
|
||||
## Home Assistant Discovery (optional)
|
||||
|
||||
This software supports [MQTT Discovery](https://www.home-assistant.io/docs/mqtt/discovery/) for integrating Nuki Hub with Home Assistant.<br>
|
||||
|
||||
25
lib/PsychicHttp/.clang-format
Normal file
25
lib/PsychicHttp/.clang-format
Normal file
@@ -0,0 +1,25 @@
|
||||
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
|
||||
|
||||
55
lib/PsychicHttp/.gitignore
vendored
Normal file
55
lib/PsychicHttp/.gitignore
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
_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
|
||||
14
lib/PsychicHttp/.gitmodules
vendored
Normal file
14
lib/PsychicHttp/.gitmodules
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
[submodule "examples/esp-idf/components/UrlEncode"]
|
||||
path = examples/esp-idf/components/UrlEncode
|
||||
url = https://github.com/dzungpv/UrlEncode
|
||||
[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
|
||||
@@ -1,3 +1,23 @@
|
||||
# v2.0
|
||||
|
||||
* 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.
|
||||
|
||||
@@ -23,7 +23,7 @@ PsychicHttp is a webserver library for ESP32 + Arduino framework which uses the
|
||||
## Differences from ESPAsyncWebserver
|
||||
|
||||
* No templating system (anyone actually use this?)
|
||||
* No url rewriting (but you can use request->redirect)
|
||||
* No url rewriting (but you can use response->redirect)
|
||||
|
||||
# Usage
|
||||
|
||||
@@ -120,7 +120,7 @@ If you have existing code using ESPAsyncWebserver, you will feel right at home w
|
||||
|
||||
## setup() Stuff
|
||||
|
||||
* no more server.begin(), call server.listen(80), before you add your handlers
|
||||
* add your handlers and call server.begin()
|
||||
* server has a configurable limit on .on() endpoints. change it with ```server.config.max_uri_handlers = 20;``` as needed.
|
||||
* check your callback function definitions:
|
||||
* AsyncWebServerRequest -> PsychicRequest
|
||||
@@ -136,7 +136,7 @@ If you have existing code using ESPAsyncWebserver, you will feel right at home w
|
||||
|
||||
## Requests / Responses
|
||||
|
||||
* request->send is now request->reply()
|
||||
* 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 AsyncCallbackJsonWebHandler (for now... can add if needed)
|
||||
@@ -164,9 +164,6 @@ void setup()
|
||||
|
||||
//connect to wifi
|
||||
|
||||
//start the server listening on port 80 (standard HTTP port)
|
||||
server.listen(80);
|
||||
|
||||
//call server methods to attach endpoints and handlers
|
||||
server.on(...);
|
||||
server.serveStatic(...);
|
||||
@@ -198,7 +195,7 @@ The ```server.on(...)``` returns a pointer to the endpoint, which can be used to
|
||||
|
||||
```cpp
|
||||
//respond to /url only from requests to the AP
|
||||
server.on("/url", HTTP_GET, request_callback)->setFilter(ON_AP_FILTER);
|
||||
server.on("/url", HTTP_GET, request_callback)->addFilter(ON_AP_FILTER);
|
||||
|
||||
//require authentication on /url
|
||||
server.on("/url", HTTP_GET, request_callback)->setAuthentication("user", "pass");
|
||||
@@ -212,7 +209,7 @@ server.on("/ws")->attachHandler(&websocketHandler);
|
||||
|
||||
The ```PsychicWebHandler``` class is for handling standard web requests. It provides a single callback: ```onRequest()```. This callback is called when the handler receives a valid HTTP request.
|
||||
|
||||
One major difference from ESPAsyncWebserver is that this callback needs to return an esp_err_t variable to let the server know the result of processing the request. The ```response->reply()``` and ```request->send()``` functions will return this. It is a good habit to return the result of these functions as sending the response will close the connection.
|
||||
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()``` and ```request->send()``` functions will return this. It is a good habit to return the result of these functions as sending the response will close the connection.
|
||||
|
||||
The function definition for the onRequest callback is:
|
||||
|
||||
@@ -226,7 +223,7 @@ Here is a simple example that sends back the client's IP on the URL /ip
|
||||
server.on("/ip", [](PsychicRequest *request)
|
||||
{
|
||||
String output = "Your IP is: " + request->client()->remoteIP().toString();
|
||||
return request->reply(output.c_str());
|
||||
return response->send(output.c_str());
|
||||
});
|
||||
```
|
||||
|
||||
@@ -294,7 +291,7 @@ It's worth noting that there is no standard way of passing in a filename for thi
|
||||
String url = "/" + request->getFilename();
|
||||
String output = "<a href=\"" + url + "\">" + url + "</a>";
|
||||
|
||||
return request->reply(output.c_str());
|
||||
return response->send(output.c_str());
|
||||
});
|
||||
|
||||
//wildcard basic file upload - POST to /upload/filename.ext
|
||||
@@ -352,7 +349,7 @@ Very similar to the basic upload, with 2 key differences:
|
||||
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
||||
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
|
||||
|
||||
return request->reply(output.c_str());
|
||||
return response->send(output.c_str());
|
||||
});
|
||||
|
||||
//upload to /multipart url
|
||||
@@ -374,11 +371,11 @@ The ```server.serveStatic()``` function handles creating the handler and assigni
|
||||
```cpp
|
||||
//serve static files from LittleFS/www on / only to clients on same wifi network
|
||||
//this is where our /index.html file lives
|
||||
server.serveStatic("/", LittleFS, "/www/")->setFilter(ON_STA_FILTER);
|
||||
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/")->setFilter(ON_AP_FILTER);
|
||||
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.
|
||||
@@ -426,17 +423,17 @@ Here is a basic example of using WebSockets:
|
||||
PsychicWebSocketHandler websocketHandler();
|
||||
|
||||
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
|
||||
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
|
||||
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);
|
||||
return response->send(frame);
|
||||
});
|
||||
|
||||
websocketHandler.onClose([](PsychicWebSocketClient *client) {
|
||||
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
|
||||
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
|
||||
@@ -452,7 +449,7 @@ The onFrame() callback has 2 parameters:
|
||||
|
||||
For sending data on the websocket connection, there are 3 methods:
|
||||
|
||||
* ```request->reply()``` - only available in the onFrame() callback context.
|
||||
* ```response->send()``` - only available in the onFrame() callback context.
|
||||
* ```webSocketHandler.sendAll()``` - can be used anywhere to send websocket messages to all connected clients.
|
||||
* ```client->send()``` - can be used anywhere* to send a websocket message to a specific client
|
||||
|
||||
@@ -488,12 +485,12 @@ Here is a basic example of using PsychicEventSource:
|
||||
PsychicEventSource eventSource;
|
||||
|
||||
eventSource.onOpen([](PsychicEventSourceClient *client) {
|
||||
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
|
||||
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());
|
||||
Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString().c_str());
|
||||
});
|
||||
|
||||
//attach the handler to /events
|
||||
@@ -524,7 +521,7 @@ PsychicHttp supports HTTPS / SSL out of the box, however there are some limitati
|
||||
#include <PsychicHttp.h>
|
||||
#include <PsychicHttpsServer.h>
|
||||
PsychicHttpsServer server;
|
||||
server.listen(443, server_cert, server_key);
|
||||
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.
|
||||
@@ -552,10 +549,9 @@ Last, but not least, you can create a separate HTTP server on port 80 that redir
|
||||
//this creates a 2nd server listening on port 80 and redirects all requests HTTPS
|
||||
PsychicHttpServer *redirectServer = new PsychicHttpServer();
|
||||
redirectServer->config.ctrl_port = 20420; // just a random port different from the default one
|
||||
redirectServer->listen(80);
|
||||
redirectServer->onNotFound([](PsychicRequest *request) {
|
||||
String url = "https://" + request->host() + request->url();
|
||||
return request->redirect(url.c_str());
|
||||
return response->redirect(url.c_str());
|
||||
});
|
||||
```
|
||||
|
||||
@@ -775,51 +771,22 @@ With all due respect to @me-no-dev who has done some amazing work in the open so
|
||||
|
||||
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
|
||||
|
||||
## v1.2: ESPAsyncWebserver Parity
|
||||
|
||||
|
||||
Change:
|
||||
Modify the request handling to bring initail url matching and filtering into PsychicHttpServer itself.
|
||||
|
||||
Benefits:
|
||||
* Fix a bug with filter() where endpoint is matched, but filter fails and it doesn't continue matching further endpoints (checks are in different codebases)
|
||||
* HTTP_ANY support
|
||||
* unlimited endpoints
|
||||
* we would use a List to store endpoints
|
||||
* dont have to pre-declare config.max_uri_handlers;
|
||||
* much more flexibility for future
|
||||
|
||||
Issues
|
||||
* it would log a warning on every request as if its a 404. (httpd_uri.c:298)
|
||||
* req->user_ctx is not passed in. (httpd_uri.c:309)
|
||||
* but... user_ctx is something we could store in the psychicendpoint data
|
||||
* Websocket support assumes an endpoint with matching url / method (httpd_uri.c:312)
|
||||
* we could copy and bring this code into our own internal request processor
|
||||
* would need to manually maintain more code (~100 lines?) and be more prone to esp-idf http_server updates causing problems.
|
||||
|
||||
How to implement
|
||||
* set config.max_uri_handlers = 1;
|
||||
* possibly do not register any uri_handlers (looks like it would be fastest way to exit httpd_find_uri_handler (httpd_uri.c:94))
|
||||
* looks like 404 is set by default, so should work.
|
||||
* modify PsychicEndpoint to store the stuff we would pass to http_server
|
||||
* create a new function handleRequest() before PsychicHttpServer::defaultNotFoundHandler to process incoming requests.
|
||||
* bring in code from PsychicHttpServer::notFoundHandler
|
||||
* add new code to loop over endpoints to call match and filter
|
||||
* bring code from esp-idf library
|
||||
|
||||
* templating system
|
||||
* regex url matching
|
||||
* rewrite urls?
|
||||
* What else are we missing?
|
||||
## v2.0: ESPAsyncWebserver Parity
|
||||
|
||||
* As much ESPAsyncWebServer compatibility as possible
|
||||
* Update benchmarks and get new data
|
||||
* we should also track program size and memory usage
|
||||
|
||||
## Longterm Wants
|
||||
|
||||
* investigate websocket performance gap
|
||||
* support for esp-idf framework
|
||||
* support for arduino 3.0 framework
|
||||
* Enable worker based multithreading with esp-idf v5.x
|
||||
* 100-continue support?
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
* Update CHANGELOG
|
||||
* Bump version in src/PsychicVersion.h
|
||||
* Bump version in library.json
|
||||
* Bump version in library.properties
|
||||
* Make new release + tag
|
||||
* this will get pulled in automatically by Arduino Library Indexer
|
||||
* run ```pio pkg publish``` to publish to Platform.io
|
||||
* ~~run ```pio pkg publish``` to publish to Platform.io~~
|
||||
* automatically publishes on release via .github hook
|
||||
@@ -8,18 +8,18 @@
|
||||
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <ArduinoJSON.h>
|
||||
#include <LittleFS.h>
|
||||
#include <MongooseCore.h>
|
||||
#include <MongooseHttpServer.h>
|
||||
#include <LittleFS.h>
|
||||
#include <ArduinoJSON.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
const char *ssid = "";
|
||||
const char *password = "";
|
||||
const char* ssid = "";
|
||||
const char* password = "";
|
||||
|
||||
MongooseHttpServer server;
|
||||
|
||||
const char *htmlContent = R"(
|
||||
const char* htmlContent = R"(
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -154,25 +154,23 @@ void setup()
|
||||
// To debug, please enable Core Debug Level to Verbose
|
||||
if (connectToWifi())
|
||||
{
|
||||
if(!LittleFS.begin())
|
||||
if (!LittleFS.begin())
|
||||
{
|
||||
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
|
||||
return;
|
||||
}
|
||||
|
||||
//start our server
|
||||
// start our server
|
||||
Mongoose.begin();
|
||||
server.begin(80);
|
||||
|
||||
//index file
|
||||
server.on("/", HTTP_GET, [](MongooseHttpServerRequest *request)
|
||||
{
|
||||
request->send(200, "text/html", htmlContent);
|
||||
});
|
||||
// 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)
|
||||
{
|
||||
// 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";
|
||||
@@ -189,19 +187,18 @@ void setup()
|
||||
//serialize and return
|
||||
String jsonBuffer;
|
||||
serializeJson(output, jsonBuffer);
|
||||
request->send(200, "application/json", jsonBuffer.c_str());
|
||||
});
|
||||
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);
|
||||
});
|
||||
// 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)
|
||||
{
|
||||
// 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();
|
||||
@@ -223,8 +220,7 @@ void setup()
|
||||
free(data);
|
||||
}
|
||||
else
|
||||
request->send(503);
|
||||
});
|
||||
request->send(503); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,31 @@
|
||||
[env]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp32dev
|
||||
; board = esp32dev
|
||||
board = esp32-s3-devkitc-1
|
||||
upload_port = /dev/ttyACM0
|
||||
monitor_port = /dev/ttyACM1
|
||||
monitor_speed = 115200
|
||||
monitor_filters = esp32_exception_decoder
|
||||
lib_deps =
|
||||
https://github.com/me-no-dev/ESPAsyncWebServer
|
||||
bblanchon/ArduinoJson
|
||||
|
||||
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]
|
||||
@@ -7,19 +7,29 @@
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
*/
|
||||
#include "_secret.h"
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <LittleFS.h>
|
||||
#include <ArduinoJSON.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
const char *ssid = "";
|
||||
const char *password = "";
|
||||
#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"(
|
||||
const char* htmlContent = R"(
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -79,14 +89,16 @@ const char *htmlContent = R"(
|
||||
</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.setSleep(false);
|
||||
// WiFi.useStaticBuffers(true);
|
||||
|
||||
WiFi.begin(ssid, password);
|
||||
|
||||
@@ -95,10 +107,8 @@ bool connectToWifi()
|
||||
int numberOfTries = 20;
|
||||
|
||||
// Wait for the WiFi event
|
||||
while (true)
|
||||
{
|
||||
switch (WiFi.status())
|
||||
{
|
||||
while (true) {
|
||||
switch (WiFi.status()) {
|
||||
case WL_NO_SSID_AVAIL:
|
||||
Serial.println("[WiFi] SSID not found");
|
||||
break;
|
||||
@@ -128,15 +138,12 @@ bool connectToWifi()
|
||||
}
|
||||
delay(tryDelay);
|
||||
|
||||
if (numberOfTries <= 0)
|
||||
{
|
||||
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
|
||||
{
|
||||
} else {
|
||||
numberOfTries--;
|
||||
}
|
||||
}
|
||||
@@ -144,28 +151,29 @@ bool connectToWifi()
|
||||
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){
|
||||
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 {
|
||||
@@ -174,22 +182,21 @@ void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
|
||||
// }
|
||||
// Serial.printf("\n");
|
||||
}
|
||||
if(info->opcode == WS_TEXT)
|
||||
{
|
||||
client->text((char *)data, len);
|
||||
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){
|
||||
// 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){
|
||||
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 {
|
||||
@@ -199,13 +206,12 @@ void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
|
||||
// Serial.printf("\n");
|
||||
}
|
||||
|
||||
if((info->index + len) == info->len){
|
||||
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){
|
||||
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);
|
||||
if (info->message_opcode == WS_TEXT) {
|
||||
client->text((char*)data, info->len);
|
||||
}
|
||||
// else
|
||||
// client->binary("I got your binary message");
|
||||
@@ -223,40 +229,41 @@ void setup()
|
||||
|
||||
// We start by connecting to a WiFi network
|
||||
// To debug, please enable Core Debug Level to Verbose
|
||||
if (connectToWifi())
|
||||
{
|
||||
if(!LittleFS.begin())
|
||||
{
|
||||
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)
|
||||
{
|
||||
request->send(200, "text/html", htmlContent);
|
||||
// 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);
|
||||
});
|
||||
|
||||
//serve static files from LittleFS/www on /
|
||||
server.serveStatic("/", LittleFS, "/www/");
|
||||
|
||||
//api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/api", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||
{
|
||||
//create a response object
|
||||
StaticJsonDocument<128> output;
|
||||
// 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"))
|
||||
{
|
||||
AsyncWebParameter* foo = request->getParam("foo");
|
||||
// work with some params
|
||||
if (request->hasParam("foo")) {
|
||||
const AsyncWebParameter* foo = request->getParam("foo");
|
||||
output["foo"] = foo->value();
|
||||
}
|
||||
|
||||
//serialize and return
|
||||
// serialize and return
|
||||
String jsonBuffer;
|
||||
serializeJson(output, jsonBuffer);
|
||||
request->send(200, "application/json", jsonBuffer.c_str());
|
||||
@@ -265,6 +272,10 @@ void setup()
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -272,5 +283,6 @@ void setup()
|
||||
void loop()
|
||||
{
|
||||
ws.cleanupClients();
|
||||
Serial.printf("Free Heap: %d\n", esp_get_free_heap_size());
|
||||
delay(1000);
|
||||
}
|
||||
2
lib/PsychicHttp/benchmark/espasyncwebserver/src/secret.h
Normal file
2
lib/PsychicHttp/benchmark/espasyncwebserver/src/secret.h
Normal file
@@ -0,0 +1,2 @@
|
||||
#define WIFI_SSID "Your_SSID"
|
||||
#define WIFI_PASS "Your_PASS"
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
//stress test the client opening/closing code
|
||||
|
||||
const EventSource = require('eventsource');
|
||||
const url = 'http://192.168.2.131/events';
|
||||
const url = 'http://psychic.local/events';
|
||||
|
||||
async function eventSourceClient() {
|
||||
console.log(`Starting test`);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
//stress test the http request code
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
const url = 'http://192.168.2.131/api';
|
||||
const url = 'http://psychic.local/api';
|
||||
const queryParams = {
|
||||
foo: 'bar',
|
||||
foo1: 'bar',
|
||||
|
||||
@@ -1,37 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
#Command to install the testers:
|
||||
# npm install -g autocannon
|
||||
# npm install
|
||||
|
||||
TEST_IP="192.168.2.131"
|
||||
TEST_TIME=60
|
||||
LOG_FILE=psychic-http-loadtest.log
|
||||
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
|
||||
|
||||
if test -f "$LOG_FILE"; then
|
||||
rm $LOG_FILE
|
||||
fi
|
||||
echo "url,connections,rps,latency,errors" > $RESULTS_FILE
|
||||
|
||||
for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20
|
||||
#for CONCURRENCY in 20
|
||||
do
|
||||
printf "\n\nCLIENTS: *** $CONCURRENCY ***\n\n" >> $LOG_FILE
|
||||
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/"
|
||||
#loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/" --quiet >> $LOG_FILE
|
||||
autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/" >> $LOG_FILE 2>&1
|
||||
printf "\n\n----------------\n\n" >> $LOG_FILE
|
||||
sleep 1
|
||||
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"
|
||||
#loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/api?foo=bar" --quiet >> $LOG_FILE
|
||||
autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/api?foo=bar" >> $LOG_FILE 2>&1
|
||||
printf "\n\n----------------\n\n" >> $LOG_FILE
|
||||
sleep 1
|
||||
|
||||
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"
|
||||
#loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/alien.png" --quiet >> $LOG_FILE
|
||||
autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/alien.png" >> $LOG_FILE 2>&1
|
||||
printf "\n\n----------------\n\n" >> $LOG_FILE
|
||||
sleep 1
|
||||
done
|
||||
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
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
#Command to install the testers:
|
||||
# npm install -g loadtest
|
||||
# npm install
|
||||
|
||||
TEST_IP="192.168.2.131"
|
||||
TEST_IP="psychic.local"
|
||||
TEST_TIME=60
|
||||
LOG_FILE=psychic-websocket-loadtest.log
|
||||
LOG_FILE=psychic-websocket-loadtest.json
|
||||
RESULTS_FILE=websocket-loadtest-results.csv
|
||||
PROTOCOL=ws
|
||||
#PROTOCOL=wss
|
||||
|
||||
@@ -12,20 +13,33 @@ if test -f "$LOG_FILE"; then
|
||||
rm $LOG_FILE
|
||||
fi
|
||||
|
||||
for CONCURRENCY in 1 2 3 4 5 6 7
|
||||
echo "url,clients,rps,latency,errors" > $RESULTS_FILE
|
||||
|
||||
CORES=1
|
||||
for CONCURRENCY in 1 2 3 4 5
|
||||
do
|
||||
printf "\n\nCLIENTS: *** $CONCURRENCY ***\n\n" >> $LOG_FILE
|
||||
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/ws"
|
||||
loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
|
||||
sleep 1
|
||||
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
|
||||
|
||||
for CONNECTIONS in 8 10 16 20
|
||||
#for CONNECTIONS in 20
|
||||
CORES=2
|
||||
for CONNECTIONS in 6 8 10 12 14
|
||||
do
|
||||
CONCURRENCY=$((CONNECTIONS / 2))
|
||||
printf "\n\nCLIENTS: *** $CONNECTIONS ***\n\n" >> $LOG_FILE
|
||||
echo "Testing $CONNECTIONS clients on $PROTOCOL://$TEST_IP/ws"
|
||||
loadtest -c $CONCURRENCY --cores 2 -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
|
||||
sleep 1
|
||||
done
|
||||
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
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
55
lib/PsychicHttp/benchmark/parse-http-test.js
Normal file
55
lib/PsychicHttp/benchmark/parse-http-test.js
Normal file
@@ -0,0 +1,55 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
64
lib/PsychicHttp/benchmark/parse-websocket-test.js
Normal file
64
lib/PsychicHttp/benchmark/parse-websocket-test.js
Normal file
@@ -0,0 +1,64 @@
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -15,8 +15,14 @@ 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]
|
||||
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
|
||||
@@ -7,26 +7,30 @@
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <PsychicHttp.h>
|
||||
#include <LittleFS.h>
|
||||
#include <ArduinoJSON.h>
|
||||
#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;
|
||||
// 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"(
|
||||
const char* htmlContent = R"(
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -92,8 +96,8 @@ bool connectToWifi()
|
||||
Serial.print("[WiFi] Connecting to ");
|
||||
Serial.println(ssid);
|
||||
|
||||
WiFi.setSleep(false);
|
||||
WiFi.useStaticBuffers(true);
|
||||
// WiFi.setSleep(false);
|
||||
// WiFi.useStaticBuffers(true);
|
||||
|
||||
WiFi.begin(ssid, password);
|
||||
|
||||
@@ -102,10 +106,8 @@ bool connectToWifi()
|
||||
int numberOfTries = 20;
|
||||
|
||||
// Wait for the WiFi event
|
||||
while (true)
|
||||
{
|
||||
switch (WiFi.status())
|
||||
{
|
||||
while (true) {
|
||||
switch (WiFi.status()) {
|
||||
case WL_NO_SSID_AVAIL:
|
||||
Serial.println("[WiFi] SSID not found");
|
||||
break;
|
||||
@@ -135,15 +137,12 @@ bool connectToWifi()
|
||||
}
|
||||
delay(tryDelay);
|
||||
|
||||
if (numberOfTries <= 0)
|
||||
{
|
||||
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
|
||||
{
|
||||
} else {
|
||||
numberOfTries--;
|
||||
}
|
||||
}
|
||||
@@ -157,47 +156,42 @@ void setup()
|
||||
delay(10);
|
||||
Serial.println("PsychicHTTP Benchmark");
|
||||
|
||||
if (connectToWifi())
|
||||
{
|
||||
if(!LittleFS.begin())
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
//start our server
|
||||
server.listen(80);
|
||||
// our index
|
||||
server.on("/", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->send(200, "text/html", htmlContent); });
|
||||
|
||||
//our index
|
||||
server.on("/", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
return request->reply(200, "text/html", htmlContent);
|
||||
});
|
||||
|
||||
//serve static files from LittleFS/www on /
|
||||
// serve static files from LittleFS/www on /
|
||||
server.serveStatic("/", LittleFS, "/www/");
|
||||
|
||||
//a websocket echo server
|
||||
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
|
||||
client->sendMessage("Hello!");
|
||||
});
|
||||
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
|
||||
request->reply(frame);
|
||||
return ESP_OK;
|
||||
// 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);
|
||||
});
|
||||
// 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)
|
||||
{
|
||||
// api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/api", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
|
||||
//create a response object
|
||||
StaticJsonDocument<128> output;
|
||||
JsonDocument output;
|
||||
output["msg"] = "status";
|
||||
output["status"] = "success";
|
||||
output["millis"] = millis();
|
||||
@@ -212,16 +206,16 @@ void setup()
|
||||
//serialize and return
|
||||
String jsonBuffer;
|
||||
serializeJson(output, jsonBuffer);
|
||||
return request->reply(200, "application/json", jsonBuffer.c_str());
|
||||
});
|
||||
return response->send(200, "application/json", jsonBuffer.c_str()); });
|
||||
|
||||
server.begin();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long last;
|
||||
void loop()
|
||||
{
|
||||
if (millis() - last > 1000)
|
||||
{
|
||||
if (millis() - last > 1000) {
|
||||
Serial.printf("Free Heap: %d\n", esp_get_free_heap_size());
|
||||
last = millis();
|
||||
}
|
||||
|
||||
@@ -7,22 +7,21 @@
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <PsychicHttp.h>
|
||||
#include <LittleFS.h>
|
||||
#include <ArduinoJSON.h>
|
||||
#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;
|
||||
// Enter your WIFI credentials in secret.h
|
||||
const char* ssid = WIFI_SSID;
|
||||
const char* password = WIFI_PASS;
|
||||
|
||||
PsychicHttpsServer server;
|
||||
PsychicWebSocketHandler websocketHandler;
|
||||
@@ -30,7 +29,7 @@ PsychicWebSocketHandler websocketHandler;
|
||||
String server_cert;
|
||||
String server_key;
|
||||
|
||||
const char *htmlContent = R"(
|
||||
const char* htmlContent = R"(
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -162,52 +161,56 @@ void setup()
|
||||
|
||||
if (connectToWifi())
|
||||
{
|
||||
if(!LittleFS.begin())
|
||||
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) {
|
||||
if (fp)
|
||||
{
|
||||
server_cert = fp.readString();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("server.pem not found, SSL not available");
|
||||
return;
|
||||
}
|
||||
fp.close();
|
||||
|
||||
File fp2 = LittleFS.open("/server.key");
|
||||
if (fp2) {
|
||||
if (fp2)
|
||||
{
|
||||
server_key = fp2.readString();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("server.key not found, SSL not available");
|
||||
return;
|
||||
}
|
||||
fp2.close();
|
||||
|
||||
//start our server
|
||||
server.listen(443, server_cert.c_str(), server_key.c_str());
|
||||
// start our server
|
||||
server.setCertificate(server_cert.c_str(), server_key.c_str());
|
||||
|
||||
//our index
|
||||
server.on("/", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
return request->reply(200, "text/html", htmlContent);
|
||||
});
|
||||
// our index
|
||||
server.on("/", HTTP_GET, [](PsychicRequest* request)
|
||||
{ return response->send(200, "text/html", htmlContent); });
|
||||
|
||||
//serve static files from LittleFS/www on /
|
||||
// serve static files from LittleFS/www on /
|
||||
server.serveStatic("/", LittleFS, "/www/");
|
||||
|
||||
//a websocket echo server
|
||||
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
|
||||
request->reply(frame);
|
||||
return ESP_OK;
|
||||
});
|
||||
// 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)
|
||||
{
|
||||
// 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";
|
||||
@@ -224,8 +227,7 @@ void setup()
|
||||
//serialize and return
|
||||
String jsonBuffer;
|
||||
serializeJson(output, jsonBuffer);
|
||||
return request->reply(200, "application/json", jsonBuffer.c_str());
|
||||
});
|
||||
return response->send(200, "application/json", jsonBuffer.c_str()); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
//stress test the client open/close for websockets
|
||||
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const uri = 'ws://192.168.2.131/ws';
|
||||
const uri = 'ws://psychic.local/ws';
|
||||
|
||||
async function websocketClient() {
|
||||
console.log(`Starting test`);
|
||||
|
||||
@@ -206,30 +206,25 @@ void setup()
|
||||
//do we want secure or not?
|
||||
if (app_enable_ssl)
|
||||
{
|
||||
server.listen(443, server_cert.c_str(), server_key.c_str());
|
||||
server.setCertificate(server_cert.c_str(), server_key.c_str());
|
||||
|
||||
//this creates a 2nd server listening on port 80 and redirects all requests HTTPS
|
||||
PsychicHttpServer *redirectServer = new PsychicHttpServer();
|
||||
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
|
||||
redirectServer->listen(80);
|
||||
redirectServer->onNotFound([](PsychicRequest *request) {
|
||||
redirectServer->onNotFound([](PsychicRequest *request, PsychicResponse *response) {
|
||||
String url = "https://" + request->host() + request->url();
|
||||
return request->redirect(url.c_str());
|
||||
return response->redirect(url.c_str());
|
||||
});
|
||||
}
|
||||
else
|
||||
server.listen(80);
|
||||
#else
|
||||
server.listen(80);
|
||||
#endif
|
||||
|
||||
//serve static files from LittleFS/www on / only to clients on same wifi network
|
||||
//this is where our /index.html file lives
|
||||
server.serveStatic("/", LittleFS, "/www/")->setFilter(ON_STA_FILTER);
|
||||
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/")->setFilter(ON_AP_FILTER);
|
||||
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.
|
||||
@@ -240,16 +235,16 @@ void setup()
|
||||
|
||||
//example callback everytime a connection is opened
|
||||
server.onOpen([](PsychicClient *client) {
|
||||
Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString());
|
||||
Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
|
||||
});
|
||||
|
||||
//example callback everytime a connection is closed
|
||||
server.onClose([](PsychicClient *client) {
|
||||
Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString());
|
||||
Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str());
|
||||
});
|
||||
|
||||
//api - json message passed in as post body
|
||||
server.on("/api", HTTP_POST, [](PsychicRequest *request)
|
||||
server.on("/api", HTTP_POST, [](PsychicRequest *request, PsychicResponse *response)
|
||||
{
|
||||
//load our JSON request
|
||||
StaticJsonDocument<1024> json;
|
||||
@@ -272,18 +267,18 @@ void setup()
|
||||
//serialize and return
|
||||
String jsonBuffer;
|
||||
serializeJson(output, jsonBuffer);
|
||||
return request->reply(200, "application/json", jsonBuffer.c_str());
|
||||
return response->send(200, "application/json", jsonBuffer.c_str());
|
||||
});
|
||||
|
||||
//api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/ip", HTTP_GET, [](PsychicRequest *request)
|
||||
server.on("/ip", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response)
|
||||
{
|
||||
String output = "Your IP is: " + request->client()->remoteIP().toString();
|
||||
return request->reply(output.c_str());
|
||||
return response->send(output.c_str());
|
||||
});
|
||||
|
||||
//api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/api", HTTP_GET, [](PsychicRequest *request)
|
||||
server.on("/api", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response)
|
||||
{
|
||||
//create a response object
|
||||
StaticJsonDocument<128> output;
|
||||
@@ -301,65 +296,64 @@ void setup()
|
||||
//serialize and return
|
||||
String jsonBuffer;
|
||||
serializeJson(output, jsonBuffer);
|
||||
return request->reply(200, "application/json", jsonBuffer.c_str());
|
||||
return response->send(200, "application/json", jsonBuffer.c_str());
|
||||
});
|
||||
|
||||
//how to redirect a request
|
||||
server.on("/redirect", HTTP_GET, [](PsychicRequest *request)
|
||||
server.on("/redirect", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response)
|
||||
{
|
||||
return request->redirect("/alien.png");
|
||||
return response->redirect("/alien.png");
|
||||
});
|
||||
|
||||
//how to do basic auth
|
||||
server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request)
|
||||
server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response)
|
||||
{
|
||||
if (!request->authenticate(app_user, app_pass))
|
||||
return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in.");
|
||||
return request->reply("Auth Basic Success!");
|
||||
return response->send("Auth Basic Success!");
|
||||
});
|
||||
|
||||
//how to do digest auth
|
||||
server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request)
|
||||
server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response)
|
||||
{
|
||||
if (!request->authenticate(app_user, app_pass))
|
||||
return request->requestAuthentication(DIGEST_AUTH, app_name, "You must log in.");
|
||||
return request->reply("Auth Digest Success!");
|
||||
return response->send("Auth Digest Success!");
|
||||
});
|
||||
|
||||
//example of getting / setting cookies
|
||||
server.on("/cookies", HTTP_GET, [](PsychicRequest *request)
|
||||
server.on("/cookies", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response)
|
||||
{
|
||||
PsychicResponse response(request);
|
||||
|
||||
int counter = 0;
|
||||
if (request->hasCookie("counter"))
|
||||
char cookie[14];
|
||||
size_t size = 14;
|
||||
if (request->getCookie("counter", cookie, &size) == ESP_OK)
|
||||
{
|
||||
counter = std::stoi(request->getCookie("counter").c_str());
|
||||
// value is null-terminated.
|
||||
counter = std::stoi(cookie);
|
||||
counter++;
|
||||
}
|
||||
sprintf(cookie, "%d", counter);
|
||||
|
||||
char cookie[10];
|
||||
sprintf(cookie, "%i", counter);
|
||||
|
||||
response.setCookie("counter", cookie);
|
||||
response.setContent(cookie);
|
||||
return response.send();
|
||||
response->setCookie("counter", cookie);
|
||||
response->setContent(cookie);
|
||||
return response->send();
|
||||
});
|
||||
|
||||
//example of getting POST variables
|
||||
server.on("/post", HTTP_POST, [](PsychicRequest *request)
|
||||
server.on("/post", HTTP_POST, [](PsychicRequest *request, PsychicResponse *response)
|
||||
{
|
||||
String output;
|
||||
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
||||
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
|
||||
|
||||
return request->reply(output.c_str());
|
||||
return response->send(output.c_str());
|
||||
});
|
||||
|
||||
//you can set up a custom 404 handler.
|
||||
server.onNotFound([](PsychicRequest *request)
|
||||
server.onNotFound([](PsychicRequest *request, PsychicResponse *response)
|
||||
{
|
||||
return request->reply(404, "text/html", "Custom 404 Handler");
|
||||
return response->send(404, "text/html", "Custom 404 Handler");
|
||||
});
|
||||
|
||||
//handle a very basic upload as post body
|
||||
@@ -393,12 +387,12 @@ void setup()
|
||||
});
|
||||
|
||||
//gets called after upload has been handled
|
||||
uploadHandler->onRequest([](PsychicRequest *request)
|
||||
uploadHandler->onRequest([](PsychicRequest *request, PsychicResponse *response)
|
||||
{
|
||||
String url = "/" + request->getFilename();
|
||||
String output = "<a href=\"" + url + "\">" + url + "</a>";
|
||||
|
||||
return request->reply(output.c_str());
|
||||
return response->send(output.c_str());
|
||||
});
|
||||
|
||||
//wildcard basic file upload - POST to /upload/filename.ext
|
||||
@@ -435,7 +429,7 @@ void setup()
|
||||
});
|
||||
|
||||
//gets called after upload has been handled
|
||||
multipartHandler->onRequest([](PsychicRequest *request)
|
||||
multipartHandler->onRequest([](PsychicRequest *request, PsychicResponse *response)
|
||||
{
|
||||
PsychicWebParameter *file = request->getParam("file_upload");
|
||||
|
||||
@@ -447,7 +441,7 @@ void setup()
|
||||
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
||||
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
|
||||
|
||||
return request->reply(output.c_str());
|
||||
return response->send(output.c_str());
|
||||
});
|
||||
|
||||
//wildcard basic file upload - POST to /upload/filename.ext
|
||||
@@ -455,7 +449,7 @@ void setup()
|
||||
|
||||
//a websocket echo server
|
||||
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
|
||||
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->localIP().toString());
|
||||
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
|
||||
client->sendMessage("Hello!");
|
||||
});
|
||||
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
|
||||
@@ -463,17 +457,17 @@ void setup()
|
||||
return request->reply(frame);
|
||||
});
|
||||
websocketHandler.onClose([](PsychicWebSocketClient *client) {
|
||||
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->localIP().toString());
|
||||
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str());
|
||||
});
|
||||
server.on("/ws", &websocketHandler);
|
||||
|
||||
//EventSource server
|
||||
eventSource.onOpen([](PsychicEventSourceClient *client) {
|
||||
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->localIP().toString());
|
||||
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
|
||||
client->send("Hello user!", NULL, millis(), 1000);
|
||||
});
|
||||
eventSource.onClose([](PsychicEventSourceClient *client) {
|
||||
Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->localIP().toString());
|
||||
Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str());
|
||||
});
|
||||
server.on("/events", &eventSource);
|
||||
}
|
||||
@@ -486,10 +480,10 @@ void loop()
|
||||
{
|
||||
if (millis() - lastUpdate > 2000)
|
||||
{
|
||||
sprintf(output, "Millis: %d\n", millis());
|
||||
sprintf(output, "Millis: %lu\n", millis());
|
||||
websocketHandler.sendAll(output);
|
||||
|
||||
sprintf(output, "%d", millis());
|
||||
sprintf(output, "%lu", millis());
|
||||
eventSource.send(output, "millis", millis(), 0);
|
||||
|
||||
lastUpdate = millis();
|
||||
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
esp_err_t handleRequest(PsychicRequest *request) {
|
||||
//PsychicFileResponse response(request, LittleFS, "/captiveportal.html"); // uncomment : for captive portal page, if any, eg "captiveportal.html"
|
||||
//return response.send(); // uncomment : return captive portal page
|
||||
return request->reply(200,"text/html","Welcome to captive portal !"); // simple text, comment if captive portal page
|
||||
return response->send(200,"text/html","Welcome to captive portal !"); // simple text, comment if captive portal page
|
||||
}
|
||||
};
|
||||
CaptiveRequestHandler *captivehandler=NULL; // handler for captive portal
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <ESPmDNS.h>
|
||||
#include <PsychicHttp.h>
|
||||
|
||||
char* TAG = "CAPTPORT";
|
||||
#define TAG "CAPTPORT"
|
||||
|
||||
// captiveportal
|
||||
// credits https://github.com/me-no-dev/ESPAsyncWebServer/blob/master/examples/CaptivePortal/CaptivePortal.ino
|
||||
@@ -29,10 +29,10 @@ public:
|
||||
// ... if needed some tests ... return(false);
|
||||
return true; // activate captive portal
|
||||
}
|
||||
esp_err_t handleRequest(PsychicRequest *request) {
|
||||
esp_err_t handleRequest(PsychicRequest *request, PsychicResponse *response) {
|
||||
//PsychicFileResponse response(request, LittleFS, "/captiveportal.html"); // uncomment : for captive portal page, if any, eg "captiveportal.html"
|
||||
//return response.send(); // uncomment : return captive portal page
|
||||
return request->reply(200,"text/html","Welcome to captive portal !"); // simple text, comment if captive portal page
|
||||
return response->send(200,"text/html","Welcome to captive portal !"); // simple text, comment if captive portal page
|
||||
}
|
||||
};
|
||||
CaptiveRequestHandler *captivehandler=NULL; // handler for captive portal
|
||||
@@ -128,7 +128,6 @@ void setup() {
|
||||
|
||||
//setup server config stuff here
|
||||
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
|
||||
server.listen(80);
|
||||
|
||||
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
*/
|
||||
char *TAG = "OTA"; // ESP_LOG tag
|
||||
#define TAG "OTA" // ESP_LOG tag
|
||||
|
||||
// PsychicHttp
|
||||
#include <Arduino.h>
|
||||
@@ -100,7 +100,6 @@ void setup()
|
||||
|
||||
//setup server config stuff here
|
||||
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
|
||||
server.listen(80);
|
||||
|
||||
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
|
||||
|
||||
@@ -109,8 +108,8 @@ void setup()
|
||||
|
||||
//you can set up a custom 404 handler.
|
||||
// curl -i http://psychic.local/404
|
||||
server.onNotFound([](PsychicRequest *request) {
|
||||
return request->reply(404, "text/html", "Custom 404 Handler");
|
||||
server.onNotFound([](PsychicRequest *request, PsychicResponse *response) {
|
||||
return response->send(404, "text/html", "Custom 404 Handler");
|
||||
});
|
||||
|
||||
// OTA
|
||||
@@ -177,34 +176,34 @@ void setup()
|
||||
}
|
||||
}); // end onUpload
|
||||
|
||||
updateHandler->onRequest([](PsychicRequest *request) { // triggered when update is completed (either OK or KO) and returns request's response (important)
|
||||
updateHandler->onRequest([](PsychicRequest *request, PsychicResponse *response) { // triggered when update is completed (either OK or KO) and returns request's response (important)
|
||||
String result; // request result
|
||||
// code below is executed when update is finished
|
||||
if (!Update.hasError()) { // update is OK
|
||||
ESP_LOGI(TAG,"Update code or data OK Update.errorString() %s", Update.errorString());
|
||||
result = "<b style='color:green'>Update done for file.</b>";
|
||||
return request->reply(200,"text/html",result.c_str());
|
||||
return response->send(200,"text/html",result.c_str());
|
||||
// ESP.restart(); // restart ESP if needed
|
||||
} // end update is OK
|
||||
else { // update is KO, send request with pretty print error
|
||||
result = " Update.errorString() " + String(Update.errorString());
|
||||
ESP_LOGE(TAG,"ERROR : error %s",result.c_str());
|
||||
return request->reply(500, "text/html", result.c_str());
|
||||
return response->send(500, "text/html", result.c_str());
|
||||
} // end update is KO
|
||||
});
|
||||
|
||||
server.on("/update", HTTP_GET, [](PsychicRequest*request){
|
||||
PsychicFileResponse response(request, LittleFS, "/update.html");
|
||||
server.on("/update", HTTP_GET, [](PsychicRequest*request, PsychicResponse *res){
|
||||
PsychicFileResponse response(res, LittleFS, "/update.html");
|
||||
return response.send();
|
||||
});
|
||||
|
||||
server.on("/update", HTTP_POST, updateHandler);
|
||||
|
||||
server.on("/restart", HTTP_POST, [](PsychicRequest *request) {
|
||||
server.on("/restart", HTTP_POST, [](PsychicRequest *request, PsychicResponse *response) {
|
||||
String output = "<b style='color:green'>Restarting ...</b>";
|
||||
ESP_LOGI(TAG,"%s",output.c_str());
|
||||
esprestart=true;
|
||||
return request->reply(output.c_str());
|
||||
return response->send(output.c_str());
|
||||
});
|
||||
} // end onRequest
|
||||
|
||||
|
||||
@@ -9,66 +9,69 @@
|
||||
*/
|
||||
|
||||
/**********************************************************************************************
|
||||
* Note: this demo relies on the following libraries (Install via Library Manager)
|
||||
* ArduinoJson UrlEncode
|
||||
**********************************************************************************************/
|
||||
* Note: this demo relies on the following libraries (Install via Library Manager)
|
||||
* ArduinoJson UrlEncode
|
||||
**********************************************************************************************/
|
||||
|
||||
/**********************************************************************************************
|
||||
* Note: this demo relies on various files to be uploaded on the LittleFS partition
|
||||
* Follow instructions here: https://randomnerdtutorials.com/esp32-littlefs-arduino-ide/
|
||||
**********************************************************************************************/
|
||||
* Note: this demo relies on various files to be uploaded on the LittleFS partition
|
||||
* Follow instructions here: https://randomnerdtutorials.com/esp32-littlefs-arduino-ide/
|
||||
**********************************************************************************************/
|
||||
|
||||
#include "secret.h"
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <LittleFS.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include "secret.h"
|
||||
#include <LittleFS.h>
|
||||
#include <PsychicHttp.h>
|
||||
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE //set this to y in menuconfig to enable SSL
|
||||
#include <PsychicHttpsServer.h>
|
||||
#include <WiFi.h>
|
||||
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE // set this to y in menuconfig to enable SSL
|
||||
#include <PsychicHttpsServer.h>
|
||||
#endif
|
||||
|
||||
#ifndef WIFI_SSID
|
||||
#error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there."
|
||||
#endif
|
||||
|
||||
//Enter your WIFI credentials in secret.h
|
||||
const char *ssid = WIFI_SSID;
|
||||
const char *password = WIFI_PASS;
|
||||
// Enter your WIFI credentials in secret.h
|
||||
const char* ssid = WIFI_SSID;
|
||||
const char* password = WIFI_PASS;
|
||||
|
||||
// Set your SoftAP credentials
|
||||
const char *softap_ssid = "PsychicHttp";
|
||||
const char *softap_password = "";
|
||||
IPAddress softap_ip(10, 0, 0, 1);
|
||||
const char* softap_ssid = "PsychicHttp";
|
||||
const char* softap_password = "";
|
||||
IPAddress softap_ip(10, 0, 0, 1);
|
||||
|
||||
//credentials for the /auth-basic and /auth-digest examples
|
||||
const char *app_user = "admin";
|
||||
const char *app_pass = "admin";
|
||||
const char *app_name = "Your App";
|
||||
// credentials for the /auth-basic and /auth-digest examples
|
||||
const char* app_user = "admin";
|
||||
const char* app_pass = "admin";
|
||||
const char* app_name = "Your App";
|
||||
|
||||
//hostname for mdns (psychic.local)
|
||||
const char *local_hostname = "psychic";
|
||||
AuthenticationMiddleware basicAuth;
|
||||
AuthenticationMiddleware digestAuth;
|
||||
|
||||
//#define CONFIG_ESP_HTTPS_SERVER_ENABLE to enable ssl
|
||||
// hostname for mdns (psychic.local)
|
||||
const char* local_hostname = "psychic";
|
||||
|
||||
// #define CONFIG_ESP_HTTPS_SERVER_ENABLE to enable ssl
|
||||
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
|
||||
bool app_enable_ssl = true;
|
||||
String server_cert;
|
||||
String server_key;
|
||||
bool app_enable_ssl = true;
|
||||
String server_cert;
|
||||
String server_key;
|
||||
#endif
|
||||
|
||||
//our main server object
|
||||
// our main server object
|
||||
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
|
||||
PsychicHttpsServer server;
|
||||
PsychicHttpsServer server;
|
||||
#else
|
||||
PsychicHttpServer server;
|
||||
PsychicHttpServer server;
|
||||
#endif
|
||||
PsychicWebSocketHandler websocketHandler;
|
||||
PsychicEventSource eventSource;
|
||||
|
||||
bool connectToWifi()
|
||||
{
|
||||
//dual client and AP mode
|
||||
// dual client and AP mode
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
|
||||
// Configure SoftAP
|
||||
@@ -92,10 +95,8 @@ bool connectToWifi()
|
||||
int numberOfTries = 20;
|
||||
|
||||
// Wait for the WiFi event
|
||||
while (true)
|
||||
{
|
||||
switch (WiFi.status())
|
||||
{
|
||||
while (true) {
|
||||
switch (WiFi.status()) {
|
||||
case WL_NO_SSID_AVAIL:
|
||||
Serial.println("[WiFi] SSID not found");
|
||||
break;
|
||||
@@ -125,15 +126,12 @@ bool connectToWifi()
|
||||
}
|
||||
delay(tryDelay);
|
||||
|
||||
if (numberOfTries <= 0)
|
||||
{
|
||||
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
|
||||
{
|
||||
} else {
|
||||
numberOfTries--;
|
||||
}
|
||||
}
|
||||
@@ -148,111 +146,102 @@ void setup()
|
||||
|
||||
// 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 (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())
|
||||
{
|
||||
if (!LittleFS.begin()) {
|
||||
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
|
||||
return;
|
||||
}
|
||||
|
||||
//look up our keys?
|
||||
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
|
||||
if (app_enable_ssl)
|
||||
{
|
||||
File fp = LittleFS.open("/server.crt");
|
||||
if (fp)
|
||||
{
|
||||
server_cert = fp.readString();
|
||||
// look up our keys?
|
||||
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
|
||||
if (app_enable_ssl) {
|
||||
File fp = LittleFS.open("/server.crt");
|
||||
if (fp) {
|
||||
server_cert = fp.readString();
|
||||
|
||||
// Serial.println("Server Cert:");
|
||||
// Serial.println(server_cert);
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("server.pem not found, SSL not available");
|
||||
app_enable_ssl = false;
|
||||
}
|
||||
fp.close();
|
||||
|
||||
File fp2 = LittleFS.open("/server.key");
|
||||
if (fp2)
|
||||
{
|
||||
server_key = fp2.readString();
|
||||
|
||||
// Serial.println("Server Key:");
|
||||
// Serial.println(server_key);
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("server.key not found, SSL not available");
|
||||
app_enable_ssl = false;
|
||||
}
|
||||
fp2.close();
|
||||
// Serial.println("Server Cert:");
|
||||
// Serial.println(server_cert);
|
||||
} else {
|
||||
Serial.println("server.pem not found, SSL not available");
|
||||
app_enable_ssl = false;
|
||||
}
|
||||
#endif
|
||||
fp.close();
|
||||
|
||||
//setup server config stuff here
|
||||
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
|
||||
File fp2 = LittleFS.open("/server.key");
|
||||
if (fp2) {
|
||||
server_key = fp2.readString();
|
||||
|
||||
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
|
||||
server.ssl_config.httpd.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
|
||||
// Serial.println("Server Key:");
|
||||
// Serial.println(server_key);
|
||||
} else {
|
||||
Serial.println("server.key not found, SSL not available");
|
||||
app_enable_ssl = false;
|
||||
}
|
||||
fp2.close();
|
||||
}
|
||||
#endif
|
||||
|
||||
//do we want secure or not?
|
||||
if (app_enable_ssl)
|
||||
{
|
||||
server.listen(443, server_cert.c_str(), server_key.c_str());
|
||||
|
||||
//this creates a 2nd server listening on port 80 and redirects all requests HTTPS
|
||||
PsychicHttpServer *redirectServer = new PsychicHttpServer();
|
||||
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
|
||||
redirectServer->listen(80);
|
||||
redirectServer->onNotFound([](PsychicRequest *request) {
|
||||
// setup server config stuff here
|
||||
server.config.max_uri_handlers = 20; // maximum number of uri handlers (.on() calls)
|
||||
|
||||
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
|
||||
server.ssl_config.httpd.max_uri_handlers = 20; // maximum number of uri handlers (.on() calls)
|
||||
|
||||
// do we want secure or not?
|
||||
if (app_enable_ssl) {
|
||||
server.setCertificate(server_cert.c_str(), server_key.c_str());
|
||||
|
||||
// this creates a 2nd server listening on port 80 and redirects all requests HTTPS
|
||||
PsychicHttpServer* redirectServer = new PsychicHttpServer();
|
||||
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
|
||||
redirectServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) {
|
||||
String url = "https://" + request->host() + request->url();
|
||||
return request->redirect(url.c_str());
|
||||
});
|
||||
}
|
||||
else
|
||||
server.listen(80);
|
||||
#else
|
||||
server.listen(80);
|
||||
#endif
|
||||
return response->redirect(url.c_str()); });
|
||||
}
|
||||
#endif
|
||||
|
||||
//serve static files from LittleFS/www on / only to clients on same wifi network
|
||||
//this is where our /index.html file lives
|
||||
server.serveStatic("/", LittleFS, "/www/")->setFilter(ON_STA_FILTER);
|
||||
basicAuth.setUsername(app_user);
|
||||
basicAuth.setPassword(app_pass);
|
||||
basicAuth.setRealm(app_name);
|
||||
basicAuth.setAuthMethod(HTTPAuthMethod::BASIC_AUTH);
|
||||
basicAuth.setAuthFailureMessage("You must log in.");
|
||||
|
||||
//serve static files from LittleFS/www-ap on / only to clients on SoftAP
|
||||
//this is where our /index.html file lives
|
||||
server.serveStatic("/", LittleFS, "/www-ap/")->setFilter(ON_AP_FILTER);
|
||||
digestAuth.setUsername(app_user);
|
||||
digestAuth.setPassword(app_pass);
|
||||
digestAuth.setRealm(app_name);
|
||||
digestAuth.setAuthMethod(HTTPAuthMethod::DIGEST_AUTH);
|
||||
digestAuth.setAuthFailureMessage("You must log in.");
|
||||
|
||||
//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.
|
||||
// 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
|
||||
// you can also serve single files
|
||||
server.serveStatic("/myfile.txt", LittleFS, "/custom.txt");
|
||||
|
||||
//example callback everytime a connection is opened
|
||||
server.onOpen([](PsychicClient *client) {
|
||||
Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
|
||||
});
|
||||
// example callback everytime a connection is opened
|
||||
server.onOpen([](PsychicClient* client) { Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str()); });
|
||||
|
||||
//example callback everytime a connection is closed
|
||||
server.onClose([](PsychicClient *client) {
|
||||
Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str());
|
||||
});
|
||||
// example callback everytime a connection is closed
|
||||
server.onClose([](PsychicClient* client) { Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); });
|
||||
|
||||
//api - json message passed in as post body
|
||||
server.on("/api", HTTP_POST, [](PsychicRequest *request)
|
||||
{
|
||||
// api - json message passed in as post body
|
||||
server.on("/api", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) {
|
||||
//load our JSON request
|
||||
JsonDocument json;
|
||||
String body = request->body();
|
||||
@@ -284,19 +273,15 @@ void setup()
|
||||
//serialize and return
|
||||
String jsonBuffer;
|
||||
serializeJson(output, jsonBuffer);
|
||||
return request->reply(200, "application/json", jsonBuffer.c_str());
|
||||
});
|
||||
return response->send(200, "application/json", jsonBuffer.c_str()); });
|
||||
|
||||
//api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/ip", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
// api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/ip", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
|
||||
String output = "Your IP is: " + request->client()->remoteIP().toString();
|
||||
return request->reply(output.c_str());
|
||||
});
|
||||
return response->send(output.c_str()); });
|
||||
|
||||
//api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||
server.on("/api", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
// 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";
|
||||
@@ -313,70 +298,48 @@ void setup()
|
||||
//serialize and return
|
||||
String jsonBuffer;
|
||||
serializeJson(output, jsonBuffer);
|
||||
return request->reply(200, "application/json", jsonBuffer.c_str());
|
||||
});
|
||||
return response->send(200, "application/json", jsonBuffer.c_str()); });
|
||||
|
||||
//how to redirect a request
|
||||
server.on("/redirect", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
return request->redirect("/alien.png");
|
||||
});
|
||||
// how to redirect a request
|
||||
server.on("/redirect", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->redirect("/alien.png"); });
|
||||
|
||||
//how to do basic auth
|
||||
server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
if (!request->authenticate(app_user, app_pass))
|
||||
return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in.");
|
||||
return request->reply("Auth Basic Success!");
|
||||
});
|
||||
// how to do basic auth
|
||||
server.on("/auth-basic", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->send("Auth Basic Success!"); })->addMiddleware(&basicAuth);
|
||||
|
||||
//how to do digest auth
|
||||
server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
if (!request->authenticate(app_user, app_pass))
|
||||
return request->requestAuthentication(DIGEST_AUTH, app_name, "You must log in.");
|
||||
return request->reply("Auth Digest Success!");
|
||||
});
|
||||
|
||||
//example of getting / setting cookies
|
||||
server.on("/cookies", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
PsychicResponse response(request);
|
||||
// how to do digest auth
|
||||
server.on("/auth-digest", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->send("Auth Digest Success!"); })->addMiddleware(&digestAuth);
|
||||
|
||||
// example of getting / setting cookies
|
||||
server.on("/cookies", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
|
||||
int counter = 0;
|
||||
if (request->hasCookie("counter"))
|
||||
char cookie[14];
|
||||
size_t size = 14;
|
||||
if (request->getCookie("counter", cookie, &size) == ESP_OK)
|
||||
{
|
||||
counter = std::stoi(request->getCookie("counter").c_str());
|
||||
// value is null-terminated.
|
||||
counter = std::stoi(cookie);
|
||||
counter++;
|
||||
}
|
||||
sprintf(cookie, "%d", counter);
|
||||
|
||||
char cookie[12];
|
||||
sprintf(cookie, "%i", counter);
|
||||
response->setCookie("counter", cookie);
|
||||
response->setContent(cookie);
|
||||
return response->send(); });
|
||||
|
||||
response.setCookie("counter", cookie);
|
||||
response.setContent(cookie);
|
||||
return response.send();
|
||||
});
|
||||
|
||||
//example of getting POST variables
|
||||
server.on("/post", HTTP_POST, [](PsychicRequest *request)
|
||||
{
|
||||
// example of getting POST variables
|
||||
server.on("/post", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) {
|
||||
String output;
|
||||
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
||||
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
|
||||
|
||||
return request->reply(output.c_str());
|
||||
});
|
||||
return response->send(output.c_str()); });
|
||||
|
||||
//you can set up a custom 404 handler.
|
||||
server.onNotFound([](PsychicRequest *request)
|
||||
{
|
||||
return request->reply(404, "text/html", "Custom 404 Handler");
|
||||
});
|
||||
// you can set up a custom 404 handler.
|
||||
server.onNotFound([](PsychicRequest* request, PsychicResponse* response) { return response->send(404, "text/html", "Custom 404 Handler"); });
|
||||
|
||||
//handle a very basic upload as post body
|
||||
PsychicUploadHandler *uploadHandler = new PsychicUploadHandler();
|
||||
uploadHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) {
|
||||
// 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;
|
||||
|
||||
@@ -401,24 +364,21 @@ void setup()
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
});
|
||||
return ESP_OK; });
|
||||
|
||||
//gets called after upload has been handled
|
||||
uploadHandler->onRequest([](PsychicRequest *request)
|
||||
{
|
||||
// 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 request->reply(output.c_str());
|
||||
});
|
||||
return response->send(output.c_str()); });
|
||||
|
||||
//wildcard basic file upload - POST to /upload/filename.ext
|
||||
// wildcard basic file upload - POST to /upload/filename.ext
|
||||
server.on("/upload/*", HTTP_POST, uploadHandler);
|
||||
|
||||
//a little bit more complicated multipart form
|
||||
PsychicUploadHandler *multipartHandler = new PsychicUploadHandler();
|
||||
multipartHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) {
|
||||
// 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;
|
||||
|
||||
@@ -443,12 +403,10 @@ void setup()
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
});
|
||||
return ESP_OK; });
|
||||
|
||||
//gets called after upload has been handled
|
||||
multipartHandler->onRequest([](PsychicRequest *request)
|
||||
{
|
||||
// gets called after upload has been handled
|
||||
multipartHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) {
|
||||
PsychicWebParameter *file = request->getParam("file_upload");
|
||||
|
||||
String url = "/" + file->value();
|
||||
@@ -459,34 +417,26 @@ void setup()
|
||||
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
||||
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
|
||||
|
||||
return request->reply(output.c_str());
|
||||
});
|
||||
return response->send(output.c_str()); });
|
||||
|
||||
//wildcard basic file upload - POST to /upload/filename.ext
|
||||
// wildcard basic file upload - POST to /upload/filename.ext
|
||||
server.on("/multipart", HTTP_POST, multipartHandler);
|
||||
|
||||
//a websocket echo server
|
||||
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
|
||||
// a websocket echo server
|
||||
websocketHandler.onOpen([](PsychicWebSocketClient* client) {
|
||||
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
|
||||
client->sendMessage("Hello!");
|
||||
});
|
||||
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
|
||||
client->sendMessage("Hello!"); });
|
||||
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) {
|
||||
Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload);
|
||||
return request->reply(frame);
|
||||
});
|
||||
websocketHandler.onClose([](PsychicWebSocketClient *client) {
|
||||
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str());
|
||||
});
|
||||
return request->reply(frame); });
|
||||
websocketHandler.onClose([](PsychicWebSocketClient* client) { Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); });
|
||||
server.on("/ws", &websocketHandler);
|
||||
|
||||
//EventSource server
|
||||
eventSource.onOpen([](PsychicEventSourceClient *client) {
|
||||
// EventSource server
|
||||
eventSource.onOpen([](PsychicEventSourceClient* client) {
|
||||
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
|
||||
client->send("Hello user!", NULL, millis(), 1000);
|
||||
});
|
||||
eventSource.onClose([](PsychicEventSourceClient *client) {
|
||||
Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str());
|
||||
});
|
||||
client->send("Hello user!", NULL, millis(), 1000); });
|
||||
eventSource.onClose([](PsychicEventSourceClient* client) { Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); });
|
||||
server.on("/events", &eventSource);
|
||||
}
|
||||
}
|
||||
@@ -496,8 +446,7 @@ char output[60];
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (millis() - lastUpdate > 2000)
|
||||
{
|
||||
if (millis() - lastUpdate > 2000) {
|
||||
sprintf(output, "Millis: %lu\n", millis());
|
||||
websocketHandler.sendAll(output);
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
.pioenvs
|
||||
.clang_complete
|
||||
.gcc-flags.json
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
# Fortran module files
|
||||
*.mod
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
# Visual Studio/VisualMicro stuff
|
||||
Visual\ Micro
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.suo
|
||||
.pioenvs
|
||||
.piolibdeps
|
||||
.pio
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/settings.json
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/ipch
|
||||
@@ -1,39 +0,0 @@
|
||||
|
||||
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
|
||||
@@ -1,46 +0,0 @@
|
||||
|
||||
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
|
||||
@@ -1,74 +0,0 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[common]
|
||||
lib_deps = ArduinoMongoose
|
||||
monitor_speed = 115200
|
||||
monitor_port = /dev/ttyUSB1
|
||||
build_flags =
|
||||
-DENABLE_DEBUG
|
||||
# -DCS_ENABLE_STDIO
|
||||
-DMG_ENABLE_HTTP_STREAMING_MULTIPART=1
|
||||
|
||||
build_flags_secure =
|
||||
-DSIMPLE_SERVER_SECURE
|
||||
-DMG_ENABLE_SSL=1
|
||||
|
||||
# -DMG_SSL_IF=MG_SSL_IF_OPENSSL
|
||||
# -DKR_VERSION
|
||||
|
||||
-DMG_SSL_MBED_DUMMY_RANDOM=1
|
||||
-DMG_SSL_IF=MG_SSL_IF_MBEDTLS
|
||||
-DMG_SSL_IF_MBEDTLS_FREE_CERTS=1
|
||||
-DMG_SSL_IF_MBEDTLS_MAX_FRAG_LEN=2048
|
||||
|
||||
build_flags_auth =
|
||||
-DADMIN_USER='"admin"'
|
||||
-DADMIN_PASS='"admin"'
|
||||
-DADMIN_REALM='"esp_ota_http_server"'
|
||||
|
||||
#[env:huzzah]
|
||||
#platform = espressif8266
|
||||
#board = huzzah
|
||||
#framework = arduino
|
||||
#monitor_speed = ${common.monitor_speed}
|
||||
#monitor_port = ${common.monitor_port}
|
||||
#lib_deps = ${common.lib_deps}
|
||||
#build_flags = ${common.build_flags}
|
||||
|
||||
[env:esp-wrover-kit]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp-wrover-kit
|
||||
monitor_speed = ${common.monitor_speed}
|
||||
monitor_port = ${common.monitor_port}
|
||||
lib_deps = ${common.lib_deps}
|
||||
build_flags = ${common.build_flags}
|
||||
|
||||
[env:esp-wrover-kit-secure]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp-wrover-kit
|
||||
monitor_speed = ${common.monitor_speed}
|
||||
monitor_port = ${common.monitor_port}
|
||||
lib_deps = ${common.lib_deps}
|
||||
build_flags = ${common.build_flags} ${common.build_flags_secure}
|
||||
|
||||
[env:esp-wrover-kit-auth]
|
||||
extends = env:esp-wrover-kit
|
||||
build_flags = ${common.build_flags} ${common.build_flags_auth} -ggdb
|
||||
|
||||
#[env:linux_x86_64]
|
||||
#platform = linux_x86_64
|
||||
#framework = arduino
|
||||
#board = generic
|
||||
#lib_deps = ${common.lib_deps}
|
||||
#build_flags = ${common.build_flags}
|
||||
#build_flags = -DSERIAL_TO_CONSOLE
|
||||
@@ -1,229 +0,0 @@
|
||||
//
|
||||
// A simple server implementation showing how to:
|
||||
// * serve static messages
|
||||
// * read GET and POST parameters
|
||||
// * handle missing pages / 404s
|
||||
//
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MongooseCore.h>
|
||||
#include <MongooseHttpServer.h>
|
||||
#include <Update.h>
|
||||
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#define START_ESP_WIFI
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#define START_ESP_WIFI
|
||||
#else
|
||||
#error Platform not supported
|
||||
#endif
|
||||
|
||||
MongooseHttpServer server;
|
||||
|
||||
const char *ssid = "wifi";
|
||||
const char *password = "password";
|
||||
|
||||
const char *server_pem =
|
||||
"-----BEGIN CERTIFICATE-----\r\n"
|
||||
"MIIDDjCCAfagAwIBAgIBBDANBgkqhkiG9w0BAQsFADA/MRkwFwYDVQQDDBB0ZXN0\r\n"
|
||||
"LmNlc2FudGEuY29tMRAwDgYDVQQKDAdDZXNhbnRhMRAwDgYDVQQLDAd0ZXN0aW5n\r\n"
|
||||
"MB4XDTE2MTExMzEzMTgwMVoXDTI2MDgxMzEzMTgwMVowFDESMBAGA1UEAwwJbG9j\r\n"
|
||||
"YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAro8CW1X0xaGm\r\n"
|
||||
"GkDaMxKbXWA5Lw+seA61tioGrSIQzuqLYeJoFnwVgF0jB5PTj+3EiGMBcA/mh73V\r\n"
|
||||
"AthTFmJBxj+agIp7/cvUBpgfLClmSYL2fZi6Fodz+f9mcry3XRw7O6vlamtWfTX8\r\n"
|
||||
"TAmMSR6PXVBHLgjs5pDOFFmrNAsM5sLYU1/1MFvE2Z9InTI5G437IE1WchRSbpYd\r\n"
|
||||
"HchC39XzpDGoInZB1a3OhcHm+xUtLpMJ0G0oE5VFEynZreZoEIY4JxspQ7LPsay9\r\n"
|
||||
"fx3Tlk09gEMQgVCeCNiQwUxZdtLau2x61LNcdZCKN7FbFLJszv1U2uguELsTmi7E\r\n"
|
||||
"6pHrTziosQIDAQABo0AwPjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIDqDATBgNVHSUE\r\n"
|
||||
"DDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB\r\n"
|
||||
"AQBUw0hbTcT6crzODO4QAXU7z4Xxn0LkxbXEsoThG1QCVgMc4Bhpx8gyz5CLyHYz\r\n"
|
||||
"AiJOBFEeV0XEqoGTNMMFelR3Q5Tg9y1TYO3qwwAWxe6/brVzpts6NiG1uEMBnBFg\r\n"
|
||||
"oN1x3I9x4NpOxU5MU1dlIxvKs5HQCoNJ8D0SqOX9BV/pZqwEgiCbuWDWQAlxkFpn\r\n"
|
||||
"iLonlkVI5hTuybCSBsa9FEI9M6JJn9LZmlH90FYHeS4t6P8eOJCeekHL0jUG4Iae\r\n"
|
||||
"DMP12h8Sd0yxIKmmZ+Q/p/D/BkuHf5Idv3hgyLkZ4mNznjK49wHaYM+BgBoL3Zeg\r\n"
|
||||
"gJ2sWjUlokrbHswSBLLbUJIF\r\n"
|
||||
"-----END CERTIFICATE-----\r\n";
|
||||
|
||||
const char *server_key =
|
||||
"-----BEGIN PRIVATE KEY-----\r\n"
|
||||
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCujwJbVfTFoaYa\r\n"
|
||||
"QNozEptdYDkvD6x4DrW2KgatIhDO6oth4mgWfBWAXSMHk9OP7cSIYwFwD+aHvdUC\r\n"
|
||||
"2FMWYkHGP5qAinv9y9QGmB8sKWZJgvZ9mLoWh3P5/2ZyvLddHDs7q+Vqa1Z9NfxM\r\n"
|
||||
"CYxJHo9dUEcuCOzmkM4UWas0CwzmwthTX/UwW8TZn0idMjkbjfsgTVZyFFJulh0d\r\n"
|
||||
"yELf1fOkMagidkHVrc6Fweb7FS0ukwnQbSgTlUUTKdmt5mgQhjgnGylDss+xrL1/\r\n"
|
||||
"HdOWTT2AQxCBUJ4I2JDBTFl20tq7bHrUs1x1kIo3sVsUsmzO/VTa6C4QuxOaLsTq\r\n"
|
||||
"ketPOKixAgMBAAECggEAI+uNwpnHirue4Jwjyoqzqd1ZJxQEm5f7UIcJZKsz5kBh\r\n"
|
||||
"ej0KykWybv27bZ2/1UhKPv6QlyzOdXRc1v8I6fxCKLeB5Z2Zsjo1YT4AfCfwwoPO\r\n"
|
||||
"kT3SXTx2YyVpQYcP/HsIvVi8FtALtixbxJHaall9iugwHYr8pN17arihAE6d0wZC\r\n"
|
||||
"JXtXRjUWwjKzXP8FoH4KhyadhHbDwIbbJe3cyLfdvp54Gr0YHha0JcOxYgDYNya4\r\n"
|
||||
"OKxlCluI+hPF31iNzOmFLQVrdYynyPcR6vY5XOiANKE2iNbqCzRb54CvW9WMqObX\r\n"
|
||||
"RD9t3DMOxGsbVNIwyzZndWy13HoQMGnrHfnGak9ueQKBgQDiVtOqYfLnUnTxvJ/b\r\n"
|
||||
"qlQZr2ZmsYPZztxlP+DSqZGPD+WtGSo9+rozWfzjTv3KGIDLvf+GFVmjVHwlLQfd\r\n"
|
||||
"u7eTemWHFc4HK68wruzPO/FdyVpQ4w9v3Usg+ll4a/PDEId0fDMjAr6kk4LC6t8y\r\n"
|
||||
"9fJR0HjOz57jVnlrDt3v50G8BwKBgQDFbw+jRiUxXnBbDyXZLi+I4iGBGdC+CbaJ\r\n"
|
||||
"CmsM6/TsOFc+GRsPwQF1gCGqdaURw76noIVKZJOSc8I+yiwU6izyh/xaju5JiWQd\r\n"
|
||||
"kwbU1j4DE6GnxmT3ARmB7VvCxjaEZEAtICWs1QTKRz7PcTV8yr7Ng1A3VIy+NSpo\r\n"
|
||||
"LFMMmk83hwKBgQDVCEwpLg/mUeHoNVVw95w4oLKNLb+gHeerFLiTDy8FrDzM88ai\r\n"
|
||||
"l37yHly7xflxYia3nZkHpsi7xiUjCINC3BApKyasQoWskh1OgRY653yCfaYYQ96f\r\n"
|
||||
"t3WjEH9trI2+p6wWo1+uMEMnu/9zXoW9/WeaQdGzNg+igh29+jxCNTPVuQKBgGV4\r\n"
|
||||
"CN9vI5pV4QTLqjYOSJvfLDz/mYqxz0BrPE1tz3jAFAZ0PLZCCY/sBGFpCScyJQBd\r\n"
|
||||
"vWNYgYeZOtGuci1llSgov4eDQfBFTlDsyWwFl+VY55IkoqtXw1ZFOQ3HdSlhpKIM\r\n"
|
||||
"jZBgApA7QYq3sjeqs5lHzahCKftvs5XKgfxOKjxtAoGBALdnYe6xkDvGLvI51Yr+\r\n"
|
||||
"Dy0TNcB5W84SxUKvM7DVEomy1QPB57ZpyQaoBq7adOz0pWJXfp7qo4950ZOhBGH1\r\n"
|
||||
"hKbZ6c4ggwVJy2j49EgMok5NGCKvPAtabbR6H8Mz8DW9aXURxhWJvij+Qw1fWK4b\r\n"
|
||||
"7G/qUI9iE5iUU7MkIcLIbTf/\r\n"
|
||||
"-----END PRIVATE KEY-----\r\n";
|
||||
|
||||
const char* server_index =
|
||||
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
|
||||
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
|
||||
"<input type='file' name='update'>"
|
||||
"<input type='submit' value='Update'>"
|
||||
"</form>"
|
||||
"<div id='prg'>progress: 0%</div>"
|
||||
"<script>"
|
||||
"$('form').submit(function(e){"
|
||||
"e.preventDefault();"
|
||||
"var form = $('#upload_form')[0];"
|
||||
"var data = new FormData(form);"
|
||||
"$.ajax({"
|
||||
"url: '/update',"
|
||||
"type: 'POST',"
|
||||
"data: data,"
|
||||
"contentType: false,"
|
||||
"processData:false,"
|
||||
"xhr: function() {"
|
||||
"var xhr = new window.XMLHttpRequest();"
|
||||
"xhr.upload.addEventListener('progress', function(evt) {"
|
||||
"if (evt.lengthComputable) {"
|
||||
"var per = evt.loaded / evt.total;"
|
||||
"$('#prg').html('progress: ' + Math.round(per*100) + '%');"
|
||||
"}"
|
||||
"}, false);"
|
||||
"return xhr;"
|
||||
"},"
|
||||
"success:function(d, s) {"
|
||||
"console.log('success!')"
|
||||
"},"
|
||||
"error: function (a, b, c) {"
|
||||
"}"
|
||||
"});"
|
||||
"});"
|
||||
"</script>";
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
bool updateCompleted = false;
|
||||
|
||||
static void updateError(MongooseHttpServerRequest *request)
|
||||
{
|
||||
MongooseHttpServerResponseStream *resp = request->beginResponseStream();
|
||||
resp->setCode(500);
|
||||
resp->setContentType("text/plain");
|
||||
resp->printf("Error: %d", Update.getError());
|
||||
request->send(resp);
|
||||
|
||||
// Anoyingly this uses Stream rather than Print...
|
||||
Update.printError(Serial);
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
|
||||
#ifdef START_ESP_WIFI
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
if (WiFi.waitForConnectResult() != WL_CONNECTED)
|
||||
{
|
||||
Serial.printf("WiFi Failed!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.print("IP Address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
Serial.print("Hostname: ");
|
||||
#ifdef ESP32
|
||||
Serial.println(WiFi.getHostname());
|
||||
#elif defined(ESP8266)
|
||||
Serial.println(WiFi.hostname());
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Mongoose.begin();
|
||||
|
||||
#ifdef SIMPLE_SERVER_SECURE
|
||||
if(false == server.begin(443, server_pem, server_key)) {
|
||||
Serial.print("Failed to start server");
|
||||
return;
|
||||
}
|
||||
#else
|
||||
server.begin(80);
|
||||
#endif
|
||||
|
||||
server.on("/$", HTTP_GET, [](MongooseHttpServerRequest *request) {
|
||||
#if defined(ADMIN_USER) && defined(ADMIN_PASS) && defined(ADMIN_REALM)
|
||||
if(false == request->authenticate(ADMIN_USER, ADMIN_PASS)) {
|
||||
request->requestAuthentication(ADMIN_REALM);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
request->send(200, "text/html", server_index);
|
||||
});
|
||||
|
||||
server.on("/update$", HTTP_POST)->
|
||||
onRequest([](MongooseHttpServerRequest *request) {
|
||||
#if defined(ADMIN_USER) && defined(ADMIN_PASS) && defined(ADMIN_REALM)
|
||||
if(false == request->authenticate(ADMIN_USER, ADMIN_PASS)) {
|
||||
request->requestAuthentication(ADMIN_REALM);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
updateCompleted = false;
|
||||
})->
|
||||
onUpload([](MongooseHttpServerRequest *request, int ev, MongooseString filename, uint64_t index, uint8_t *data, size_t len)
|
||||
{
|
||||
if(MG_EV_HTTP_PART_BEGIN == ev) {
|
||||
Serial.printf("Update Start: %s\n", filename.c_str());
|
||||
|
||||
if (!Update.begin()) { //start with max available size
|
||||
updateError(request);
|
||||
}
|
||||
}
|
||||
|
||||
if(!Update.hasError())
|
||||
{
|
||||
Serial.printf("Update Writing %llu\n", index);
|
||||
if(Update.write(data, len) != len) {
|
||||
updateError(request);
|
||||
}
|
||||
}
|
||||
|
||||
if(MG_EV_HTTP_PART_END == ev) {
|
||||
Serial.println("Data finished");
|
||||
if(Update.end(true)) {
|
||||
Serial.printf("Update Success: %lluB\n", index+len);
|
||||
request->send(200, "text/plain", "OK");
|
||||
updateCompleted = true;
|
||||
} else {
|
||||
updateError(request);
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
})->
|
||||
onClose([](MongooseHttpServerRequest *request)
|
||||
{
|
||||
if(updateCompleted) {
|
||||
ESP.restart();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
Mongoose.poll(1000);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
|
||||
This directory is intended for PIO Unit Testing and project tests.
|
||||
|
||||
Unit Testing is a software testing method by which individual units of
|
||||
source code, sets of one or more MCU program modules together with associated
|
||||
control data, usage procedures, and operating procedures, are tested to
|
||||
determine whether they are fit for use. Unit testing finds problems early
|
||||
in the development cycle.
|
||||
|
||||
More information about PIO Unit Testing:
|
||||
- https://docs.platformio.org/page/plus/unit-testing.html
|
||||
@@ -1,39 +0,0 @@
|
||||
.pioenvs
|
||||
.clang_complete
|
||||
.gcc-flags.json
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
# Fortran module files
|
||||
*.mod
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
# Visual Studio/VisualMicro stuff
|
||||
Visual\ Micro
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.suo
|
||||
.pioenvs
|
||||
.piolibdeps
|
||||
.pio
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/settings.json
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/ipch
|
||||
@@ -1,39 +0,0 @@
|
||||
|
||||
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
|
||||
@@ -1,46 +0,0 @@
|
||||
|
||||
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
|
||||
@@ -1,64 +0,0 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[common]
|
||||
lib_deps = ArduinoMongoose
|
||||
monitor_speed = 115200
|
||||
monitor_port = /dev/ttyUSB1
|
||||
build_flags =
|
||||
-DENABLE_DEBUG
|
||||
# -DCS_ENABLE_STDIO
|
||||
|
||||
build_flags_secure =
|
||||
-DSIMPLE_SERVER_SECURE
|
||||
-DMG_ENABLE_SSL=1
|
||||
|
||||
# -DMG_SSL_IF=MG_SSL_IF_OPENSSL
|
||||
# -DKR_VERSION
|
||||
|
||||
-DMG_SSL_MBED_DUMMY_RANDOM=1
|
||||
-DMG_SSL_IF=MG_SSL_IF_MBEDTLS
|
||||
-DMG_SSL_IF_MBEDTLS_FREE_CERTS=1
|
||||
-DMG_SSL_IF_MBEDTLS_MAX_FRAG_LEN=2048
|
||||
|
||||
[env:huzzah]
|
||||
platform = espressif8266
|
||||
board = huzzah
|
||||
framework = arduino
|
||||
monitor_speed = ${common.monitor_speed}
|
||||
monitor_port = ${common.monitor_port}
|
||||
lib_deps = ${common.lib_deps}
|
||||
build_flags = ${common.build_flags}
|
||||
|
||||
[env:esp-wrover-kit]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp-wrover-kit
|
||||
monitor_speed = ${common.monitor_speed}
|
||||
monitor_port = ${common.monitor_port}
|
||||
lib_deps = ${common.lib_deps}
|
||||
build_flags = ${common.build_flags}
|
||||
|
||||
[env:esp-wrover-kit-secure]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp-wrover-kit
|
||||
monitor_speed = ${common.monitor_speed}
|
||||
monitor_port = ${common.monitor_port}
|
||||
lib_deps = ${common.lib_deps}
|
||||
build_flags = ${common.build_flags} ${common.build_flags_secure}
|
||||
|
||||
#[env:linux_x86_64]
|
||||
#platform = linux_x86_64
|
||||
#framework = arduino
|
||||
#board = generic
|
||||
#lib_deps = ${common.lib_deps}
|
||||
#build_flags = ${common.build_flags}
|
||||
#build_flags = -DSERIAL_TO_CONSOLE
|
||||
@@ -1,211 +0,0 @@
|
||||
//
|
||||
// A simple server implementation showing how to:
|
||||
// * serve static messages
|
||||
// * read GET and POST parameters
|
||||
// * handle missing pages / 404s
|
||||
//
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MongooseCore.h>
|
||||
#include <MongooseHttpServer.h>
|
||||
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#define START_ESP_WIFI
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#define START_ESP_WIFI
|
||||
#else
|
||||
#error Platform not supported
|
||||
#endif
|
||||
|
||||
MongooseHttpServer server;
|
||||
|
||||
const char *ssid = "wifi";
|
||||
const char *password = "password";
|
||||
|
||||
const char *PARAM_MESSAGE = "message";
|
||||
|
||||
const char *server_pem =
|
||||
"-----BEGIN CERTIFICATE-----\r\n"
|
||||
"MIIDDjCCAfagAwIBAgIBBDANBgkqhkiG9w0BAQsFADA/MRkwFwYDVQQDDBB0ZXN0\r\n"
|
||||
"LmNlc2FudGEuY29tMRAwDgYDVQQKDAdDZXNhbnRhMRAwDgYDVQQLDAd0ZXN0aW5n\r\n"
|
||||
"MB4XDTE2MTExMzEzMTgwMVoXDTI2MDgxMzEzMTgwMVowFDESMBAGA1UEAwwJbG9j\r\n"
|
||||
"YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAro8CW1X0xaGm\r\n"
|
||||
"GkDaMxKbXWA5Lw+seA61tioGrSIQzuqLYeJoFnwVgF0jB5PTj+3EiGMBcA/mh73V\r\n"
|
||||
"AthTFmJBxj+agIp7/cvUBpgfLClmSYL2fZi6Fodz+f9mcry3XRw7O6vlamtWfTX8\r\n"
|
||||
"TAmMSR6PXVBHLgjs5pDOFFmrNAsM5sLYU1/1MFvE2Z9InTI5G437IE1WchRSbpYd\r\n"
|
||||
"HchC39XzpDGoInZB1a3OhcHm+xUtLpMJ0G0oE5VFEynZreZoEIY4JxspQ7LPsay9\r\n"
|
||||
"fx3Tlk09gEMQgVCeCNiQwUxZdtLau2x61LNcdZCKN7FbFLJszv1U2uguELsTmi7E\r\n"
|
||||
"6pHrTziosQIDAQABo0AwPjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIDqDATBgNVHSUE\r\n"
|
||||
"DDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB\r\n"
|
||||
"AQBUw0hbTcT6crzODO4QAXU7z4Xxn0LkxbXEsoThG1QCVgMc4Bhpx8gyz5CLyHYz\r\n"
|
||||
"AiJOBFEeV0XEqoGTNMMFelR3Q5Tg9y1TYO3qwwAWxe6/brVzpts6NiG1uEMBnBFg\r\n"
|
||||
"oN1x3I9x4NpOxU5MU1dlIxvKs5HQCoNJ8D0SqOX9BV/pZqwEgiCbuWDWQAlxkFpn\r\n"
|
||||
"iLonlkVI5hTuybCSBsa9FEI9M6JJn9LZmlH90FYHeS4t6P8eOJCeekHL0jUG4Iae\r\n"
|
||||
"DMP12h8Sd0yxIKmmZ+Q/p/D/BkuHf5Idv3hgyLkZ4mNznjK49wHaYM+BgBoL3Zeg\r\n"
|
||||
"gJ2sWjUlokrbHswSBLLbUJIF\r\n"
|
||||
"-----END CERTIFICATE-----\r\n";
|
||||
|
||||
const char *server_key =
|
||||
"-----BEGIN PRIVATE KEY-----\r\n"
|
||||
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCujwJbVfTFoaYa\r\n"
|
||||
"QNozEptdYDkvD6x4DrW2KgatIhDO6oth4mgWfBWAXSMHk9OP7cSIYwFwD+aHvdUC\r\n"
|
||||
"2FMWYkHGP5qAinv9y9QGmB8sKWZJgvZ9mLoWh3P5/2ZyvLddHDs7q+Vqa1Z9NfxM\r\n"
|
||||
"CYxJHo9dUEcuCOzmkM4UWas0CwzmwthTX/UwW8TZn0idMjkbjfsgTVZyFFJulh0d\r\n"
|
||||
"yELf1fOkMagidkHVrc6Fweb7FS0ukwnQbSgTlUUTKdmt5mgQhjgnGylDss+xrL1/\r\n"
|
||||
"HdOWTT2AQxCBUJ4I2JDBTFl20tq7bHrUs1x1kIo3sVsUsmzO/VTa6C4QuxOaLsTq\r\n"
|
||||
"ketPOKixAgMBAAECggEAI+uNwpnHirue4Jwjyoqzqd1ZJxQEm5f7UIcJZKsz5kBh\r\n"
|
||||
"ej0KykWybv27bZ2/1UhKPv6QlyzOdXRc1v8I6fxCKLeB5Z2Zsjo1YT4AfCfwwoPO\r\n"
|
||||
"kT3SXTx2YyVpQYcP/HsIvVi8FtALtixbxJHaall9iugwHYr8pN17arihAE6d0wZC\r\n"
|
||||
"JXtXRjUWwjKzXP8FoH4KhyadhHbDwIbbJe3cyLfdvp54Gr0YHha0JcOxYgDYNya4\r\n"
|
||||
"OKxlCluI+hPF31iNzOmFLQVrdYynyPcR6vY5XOiANKE2iNbqCzRb54CvW9WMqObX\r\n"
|
||||
"RD9t3DMOxGsbVNIwyzZndWy13HoQMGnrHfnGak9ueQKBgQDiVtOqYfLnUnTxvJ/b\r\n"
|
||||
"qlQZr2ZmsYPZztxlP+DSqZGPD+WtGSo9+rozWfzjTv3KGIDLvf+GFVmjVHwlLQfd\r\n"
|
||||
"u7eTemWHFc4HK68wruzPO/FdyVpQ4w9v3Usg+ll4a/PDEId0fDMjAr6kk4LC6t8y\r\n"
|
||||
"9fJR0HjOz57jVnlrDt3v50G8BwKBgQDFbw+jRiUxXnBbDyXZLi+I4iGBGdC+CbaJ\r\n"
|
||||
"CmsM6/TsOFc+GRsPwQF1gCGqdaURw76noIVKZJOSc8I+yiwU6izyh/xaju5JiWQd\r\n"
|
||||
"kwbU1j4DE6GnxmT3ARmB7VvCxjaEZEAtICWs1QTKRz7PcTV8yr7Ng1A3VIy+NSpo\r\n"
|
||||
"LFMMmk83hwKBgQDVCEwpLg/mUeHoNVVw95w4oLKNLb+gHeerFLiTDy8FrDzM88ai\r\n"
|
||||
"l37yHly7xflxYia3nZkHpsi7xiUjCINC3BApKyasQoWskh1OgRY653yCfaYYQ96f\r\n"
|
||||
"t3WjEH9trI2+p6wWo1+uMEMnu/9zXoW9/WeaQdGzNg+igh29+jxCNTPVuQKBgGV4\r\n"
|
||||
"CN9vI5pV4QTLqjYOSJvfLDz/mYqxz0BrPE1tz3jAFAZ0PLZCCY/sBGFpCScyJQBd\r\n"
|
||||
"vWNYgYeZOtGuci1llSgov4eDQfBFTlDsyWwFl+VY55IkoqtXw1ZFOQ3HdSlhpKIM\r\n"
|
||||
"jZBgApA7QYq3sjeqs5lHzahCKftvs5XKgfxOKjxtAoGBALdnYe6xkDvGLvI51Yr+\r\n"
|
||||
"Dy0TNcB5W84SxUKvM7DVEomy1QPB57ZpyQaoBq7adOz0pWJXfp7qo4950ZOhBGH1\r\n"
|
||||
"hKbZ6c4ggwVJy2j49EgMok5NGCKvPAtabbR6H8Mz8DW9aXURxhWJvij+Qw1fWK4b\r\n"
|
||||
"7G/qUI9iE5iUU7MkIcLIbTf/\r\n"
|
||||
"-----END PRIVATE KEY-----\r\n";
|
||||
|
||||
static void notFound(MongooseHttpServerRequest *request);
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
|
||||
#ifdef START_ESP_WIFI
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
if (WiFi.waitForConnectResult() != WL_CONNECTED)
|
||||
{
|
||||
Serial.printf("WiFi Failed!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.print("IP Address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
Serial.print("Hostname: ");
|
||||
#ifdef ESP32
|
||||
Serial.println(WiFi.getHostname());
|
||||
#elif defined(ESP8266)
|
||||
Serial.println(WiFi.hostname());
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Mongoose.begin();
|
||||
|
||||
#ifdef SIMPLE_SERVER_SECURE
|
||||
if(false == server.begin(443, server_pem, server_key)) {
|
||||
Serial.print("Failed to start server");
|
||||
return;
|
||||
}
|
||||
#else
|
||||
server.begin(80);
|
||||
#endif
|
||||
|
||||
server.on("/$", HTTP_GET, [](MongooseHttpServerRequest *request) {
|
||||
request->send(200, "text/plain", "Hello world");
|
||||
});
|
||||
|
||||
// Send a GET request to <IP>/get?message=<message>
|
||||
server.on("/get$", HTTP_GET, [](MongooseHttpServerRequest *request) {
|
||||
String message;
|
||||
if (request->hasParam(PARAM_MESSAGE))
|
||||
{
|
||||
message = request->getParam(PARAM_MESSAGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
message = "No message sent";
|
||||
}
|
||||
request->send(200, "text/plain", "Hello, GET: " + message);
|
||||
});
|
||||
|
||||
// Send a POST request to <IP>/post with a form field message set to <message>
|
||||
server.on("/post$", HTTP_POST, [](MongooseHttpServerRequest *request) {
|
||||
String message;
|
||||
if (request->hasParam(PARAM_MESSAGE))
|
||||
{
|
||||
message = request->getParam(PARAM_MESSAGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
message = "No message sent";
|
||||
}
|
||||
request->send(200, "text/plain", "Hello, POST: " + message);
|
||||
});
|
||||
|
||||
// Test the basic response class
|
||||
server.on("/basic$", HTTP_GET, [](MongooseHttpServerRequest *request) {
|
||||
MongooseHttpServerResponseBasic *resp = request->beginResponse();
|
||||
resp->setCode(200);
|
||||
resp->setContentType("text/html");
|
||||
resp->addHeader("Cache-Control", "max-age=300");
|
||||
resp->addHeader("X-hello", "world");
|
||||
resp->setContent(
|
||||
"<html>\n"
|
||||
"<head>\n"
|
||||
"<title>Basic Page</title>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
"<h1>Basic Page</h1>\n"
|
||||
"<p>\n"
|
||||
"This page has been sent using the MongooseHttpServerResponseBasic class\n"
|
||||
"</p>\n"
|
||||
"</body>\n"
|
||||
"</html>\n");
|
||||
request->send(resp);
|
||||
});
|
||||
|
||||
// Test the stream response class
|
||||
server.on("/stream$", HTTP_GET, [](MongooseHttpServerRequest *request) {
|
||||
MongooseHttpServerResponseStream *resp = request->beginResponseStream();
|
||||
resp->setCode(200);
|
||||
resp->setContentType("text/html");
|
||||
resp->addHeader("Cache-Control", "max-age=300");
|
||||
resp->addHeader("X-hello", "world");
|
||||
|
||||
resp->println("<html>");
|
||||
resp->println("<head>");
|
||||
resp->println("<title>Stream Page</title>");
|
||||
resp->println("</head>");
|
||||
resp->println("<body>");
|
||||
resp->println("<h1>Stream Page</h1>");
|
||||
resp->println("<p>");
|
||||
resp->println("This page has been sent using the MongooseHttpServerResponseStream class");
|
||||
resp->println("</p>");
|
||||
resp->println("<p>");
|
||||
resp->printf("micros = %lu<br/>", micros());
|
||||
resp->printf("free = %u<br/>", ESP.getFreeHeap());
|
||||
resp->println("</p>");
|
||||
resp->println("</body>");
|
||||
resp->println("</html>");
|
||||
|
||||
request->send(resp);
|
||||
});
|
||||
|
||||
server.onNotFound(notFound);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
Mongoose.poll(1000);
|
||||
Serial.printf("Free memory %u\n", ESP.getFreeHeap());
|
||||
}
|
||||
|
||||
static void notFound(MongooseHttpServerRequest *request)
|
||||
{
|
||||
request->send(404, "text/plain", "Not found");
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
|
||||
This directory is intended for PIO Unit Testing and project tests.
|
||||
|
||||
Unit Testing is a software testing method by which individual units of
|
||||
source code, sets of one or more MCU program modules together with associated
|
||||
control data, usage procedures, and operating procedures, are tested to
|
||||
determine whether they are fit for use. Unit testing finds problems early
|
||||
in the development cycle.
|
||||
|
||||
More information about PIO Unit Testing:
|
||||
- https://docs.platformio.org/page/plus/unit-testing.html
|
||||
@@ -1,35 +0,0 @@
|
||||
# Name: REST Client
|
||||
# Id: humao.rest-client
|
||||
# Description: REST Client for Visual Studio Code
|
||||
# Version: 0.21.3
|
||||
# Publisher: Huachao Mao
|
||||
# VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=humao.rest-client
|
||||
|
||||
@baseUrl = http://172.16.0.87
|
||||
|
||||
###
|
||||
|
||||
GET {{baseUrl}}/ HTTP/1.1
|
||||
|
||||
###
|
||||
|
||||
GET {{baseUrl}}/get?message=Hello+World HTTP/1.1
|
||||
|
||||
###
|
||||
|
||||
POST {{baseUrl}}/post HTTP/1.1
|
||||
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
|
||||
|
||||
message=Hello+World
|
||||
|
||||
###
|
||||
|
||||
GET {{baseUrl}}/someRandomFile HTTP/1.1
|
||||
|
||||
###
|
||||
|
||||
GET {{baseUrl}}/basic HTTP/1.1
|
||||
|
||||
###
|
||||
|
||||
GET {{baseUrl}}/stream HTTP/1.1
|
||||
@@ -1,39 +0,0 @@
|
||||
.pioenvs
|
||||
.clang_complete
|
||||
.gcc-flags.json
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
# Fortran module files
|
||||
*.mod
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
# Visual Studio/VisualMicro stuff
|
||||
Visual\ Micro
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.suo
|
||||
.pioenvs
|
||||
.piolibdeps
|
||||
.pio
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/settings.json
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/ipch
|
||||
@@ -1,67 +0,0 @@
|
||||
# Continuous Integration (CI) is the practice, in software
|
||||
# engineering, of merging all developer working copies with a shared mainline
|
||||
# several times a day < https://docs.platformio.org/page/ci/index.html >
|
||||
#
|
||||
# Documentation:
|
||||
#
|
||||
# * Travis CI Embedded Builds with PlatformIO
|
||||
# < https://docs.travis-ci.com/user/integration/platformio/ >
|
||||
#
|
||||
# * PlatformIO integration with Travis CI
|
||||
# < https://docs.platformio.org/page/ci/travis.html >
|
||||
#
|
||||
# * User Guide for `platformio ci` command
|
||||
# < https://docs.platformio.org/page/userguide/cmd_ci.html >
|
||||
#
|
||||
#
|
||||
# Please choose one of the following templates (proposed below) and uncomment
|
||||
# it (remove "# " before each line) or use own configuration according to the
|
||||
# Travis CI documentation (see above).
|
||||
#
|
||||
|
||||
|
||||
#
|
||||
# Template #1: General project. Test it using existing `platformio.ini`.
|
||||
#
|
||||
|
||||
# language: python
|
||||
# python:
|
||||
# - "2.7"
|
||||
#
|
||||
# sudo: false
|
||||
# cache:
|
||||
# directories:
|
||||
# - "~/.platformio"
|
||||
#
|
||||
# install:
|
||||
# - pip install -U platformio
|
||||
# - platformio update
|
||||
#
|
||||
# script:
|
||||
# - platformio run
|
||||
|
||||
|
||||
#
|
||||
# Template #2: The project is intended to be used as a library with examples.
|
||||
#
|
||||
|
||||
# language: python
|
||||
# python:
|
||||
# - "2.7"
|
||||
#
|
||||
# sudo: false
|
||||
# cache:
|
||||
# directories:
|
||||
# - "~/.platformio"
|
||||
#
|
||||
# env:
|
||||
# - PLATFORMIO_CI_SRC=path/to/test/file.c
|
||||
# - PLATFORMIO_CI_SRC=examples/file.ino
|
||||
# - PLATFORMIO_CI_SRC=path/to/test/directory
|
||||
#
|
||||
# install:
|
||||
# - pip install -U platformio
|
||||
# - platformio update
|
||||
#
|
||||
# script:
|
||||
# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N
|
||||
@@ -1,39 +0,0 @@
|
||||
|
||||
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
|
||||
@@ -1,46 +0,0 @@
|
||||
|
||||
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
|
||||
@@ -1,40 +0,0 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[common]
|
||||
lib_deps = ArduinoMongoose
|
||||
monitor_speed = 115200
|
||||
build_flags =
|
||||
|
||||
[espressif8266]
|
||||
build_flags = -DMG_ESP8266
|
||||
|
||||
[espressif32]
|
||||
build_flags =
|
||||
|
||||
[env:huzzah]
|
||||
platform = espressif8266
|
||||
framework = arduino
|
||||
board = huzzah
|
||||
monitor_speed = ${common.monitor_speed}
|
||||
lib_deps = ${common.lib_deps}
|
||||
build_flags =
|
||||
${espressif8266.build_flags}
|
||||
${common.build_flags}
|
||||
|
||||
[env:espwroverkit]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp-wrover-kit
|
||||
monitor_speed = ${common.monitor_speed}
|
||||
lib_deps = ${common.lib_deps}
|
||||
build_flags =
|
||||
${espressif32.build_flags}
|
||||
${common.build_flags}
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright (c) 2015 Cesanta Software Limited
|
||||
// All rights reserved
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#else
|
||||
#error Platform not supported
|
||||
#endif
|
||||
|
||||
#include "mongoose.h"
|
||||
|
||||
const char* ssid = "my-ssid";
|
||||
const char* password = "my-password";
|
||||
|
||||
static const char *s_http_port = "80";
|
||||
//static struct mg_serve_http_opts s_http_server_opts;
|
||||
|
||||
static void ev_handler(struct mg_connection *nc, int ev, void *p, void *d) {
|
||||
static const char *reply_fmt =
|
||||
"HTTP/1.0 200 OK\r\n"
|
||||
"Connection: close\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"\r\n"
|
||||
"Hello %s\n";
|
||||
|
||||
switch (ev) {
|
||||
case MG_EV_ACCEPT: {
|
||||
char addr[32];
|
||||
mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),
|
||||
MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
|
||||
Serial.printf("Connection %p from %s\n", nc, addr);
|
||||
break;
|
||||
}
|
||||
case MG_EV_HTTP_REQUEST: {
|
||||
char addr[32];
|
||||
struct http_message *hm = (struct http_message *) p;
|
||||
mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),
|
||||
MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
|
||||
Serial.printf("HTTP request from %s: %.*s %.*s\n", addr, (int) hm->method.len,
|
||||
hm->method.p, (int) hm->uri.len, hm->uri.p);
|
||||
mg_printf(nc, reply_fmt, addr);
|
||||
nc->flags |= MG_F_SEND_AND_CLOSE;
|
||||
break;
|
||||
}
|
||||
case MG_EV_CLOSE: {
|
||||
Serial.printf("Connection %p closed\n", nc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *nc;
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
|
||||
Serial.print("Connecting to ");
|
||||
Serial.println(ssid);
|
||||
|
||||
WiFi.begin(ssid, password);
|
||||
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
|
||||
Serial.println("");
|
||||
Serial.println("WiFi connected");
|
||||
Serial.println("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
mg_mgr_init(&mgr, NULL);
|
||||
Serial.printf("Starting web server on port %s\n", s_http_port);
|
||||
nc = mg_bind(&mgr, s_http_port, ev_handler, NULL);
|
||||
if (nc == NULL) {
|
||||
Serial.printf("Failed to create listener\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up HTTP server parameters
|
||||
mg_set_protocol_http_websocket(nc);
|
||||
// s_http_server_opts.document_root = "."; // Serve current directory
|
||||
// s_http_server_opts.enable_directory_listing = "yes";
|
||||
}
|
||||
|
||||
static uint32_t count = 0;
|
||||
void loop()
|
||||
{
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
//Serial.println(count++);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
|
||||
This directory is intended for PIO Unit Testing and project tests.
|
||||
|
||||
Unit Testing is a software testing method by which individual units of
|
||||
source code, sets of one or more MCU program modules together with associated
|
||||
control data, usage procedures, and operating procedures, are tested to
|
||||
determine whether they are fit for use. Unit testing finds problems early
|
||||
in the development cycle.
|
||||
|
||||
More information about PIO Unit Testing:
|
||||
- https://docs.platformio.org/page/plus/unit-testing.html
|
||||
@@ -1,39 +0,0 @@
|
||||
.pioenvs
|
||||
.clang_complete
|
||||
.gcc-flags.json
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
# Fortran module files
|
||||
*.mod
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
# Visual Studio/VisualMicro stuff
|
||||
Visual\ Micro
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.suo
|
||||
.pioenvs
|
||||
.piolibdeps
|
||||
.pio
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/settings.json
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/ipch
|
||||
@@ -1,39 +0,0 @@
|
||||
|
||||
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
|
||||
@@ -1,46 +0,0 @@
|
||||
|
||||
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
|
||||
@@ -1,64 +0,0 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[common]
|
||||
lib_deps = ArduinoMongoose
|
||||
monitor_speed = 115200
|
||||
monitor_port = /dev/ttyUSB2
|
||||
build_flags =
|
||||
-DENABLE_DEBUG
|
||||
# -DCS_ENABLE_STDIO
|
||||
|
||||
build_flags_secure =
|
||||
-DSIMPLE_SERVER_SECURE
|
||||
-DMG_ENABLE_SSL=1
|
||||
|
||||
# -DMG_SSL_IF=MG_SSL_IF_OPENSSL
|
||||
# -DKR_VERSION
|
||||
|
||||
-DMG_SSL_MBED_DUMMY_RANDOM=1
|
||||
-DMG_SSL_IF=MG_SSL_IF_MBEDTLS
|
||||
-DMG_SSL_IF_MBEDTLS_FREE_CERTS=1
|
||||
-DMG_SSL_IF_MBEDTLS_MAX_FRAG_LEN=2048
|
||||
|
||||
[env:huzzah]
|
||||
platform = espressif8266
|
||||
board = huzzah
|
||||
framework = arduino
|
||||
monitor_speed = ${common.monitor_speed}
|
||||
monitor_port = ${common.monitor_port}
|
||||
lib_deps = ${common.lib_deps}
|
||||
build_flags = ${common.build_flags}
|
||||
|
||||
[env:esp-wrover-kit]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp-wrover-kit
|
||||
monitor_speed = ${common.monitor_speed}
|
||||
monitor_port = ${common.monitor_port}
|
||||
lib_deps = ${common.lib_deps}
|
||||
build_flags = ${common.build_flags}
|
||||
|
||||
[env:esp-wrover-kit-secure]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp-wrover-kit
|
||||
monitor_speed = ${common.monitor_speed}
|
||||
monitor_port = ${common.monitor_port}
|
||||
lib_deps = ${common.lib_deps}
|
||||
build_flags = ${common.build_flags} ${common.build_flags_secure}
|
||||
|
||||
#[env:linux_x86_64]
|
||||
#platform = linux_x86_64
|
||||
#framework = arduino
|
||||
#board = generic
|
||||
#lib_deps = ${common.lib_deps}
|
||||
#build_flags = ${common.build_flags}
|
||||
#build_flags = -DSERIAL_TO_CONSOLE
|
||||
@@ -1,235 +0,0 @@
|
||||
//
|
||||
// A simple server implementation showing how to:
|
||||
// * serve static messages
|
||||
// * read GET and POST parameters
|
||||
// * handle missing pages / 404s
|
||||
//
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MongooseCore.h>
|
||||
#include <MongooseHttpServer.h>
|
||||
|
||||
#ifdef ESP32
|
||||
#include <WiFi.h>
|
||||
#define START_ESP_WIFI
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#define START_ESP_WIFI
|
||||
#else
|
||||
#error Platform not supported
|
||||
#endif
|
||||
|
||||
MongooseHttpServer server;
|
||||
|
||||
const char *ssid = "wifi";
|
||||
const char *password = "password";
|
||||
|
||||
const char *server_pem =
|
||||
"-----BEGIN CERTIFICATE-----\r\n"
|
||||
"MIIDDjCCAfagAwIBAgIBBDANBgkqhkiG9w0BAQsFADA/MRkwFwYDVQQDDBB0ZXN0\r\n"
|
||||
"LmNlc2FudGEuY29tMRAwDgYDVQQKDAdDZXNhbnRhMRAwDgYDVQQLDAd0ZXN0aW5n\r\n"
|
||||
"MB4XDTE2MTExMzEzMTgwMVoXDTI2MDgxMzEzMTgwMVowFDESMBAGA1UEAwwJbG9j\r\n"
|
||||
"YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAro8CW1X0xaGm\r\n"
|
||||
"GkDaMxKbXWA5Lw+seA61tioGrSIQzuqLYeJoFnwVgF0jB5PTj+3EiGMBcA/mh73V\r\n"
|
||||
"AthTFmJBxj+agIp7/cvUBpgfLClmSYL2fZi6Fodz+f9mcry3XRw7O6vlamtWfTX8\r\n"
|
||||
"TAmMSR6PXVBHLgjs5pDOFFmrNAsM5sLYU1/1MFvE2Z9InTI5G437IE1WchRSbpYd\r\n"
|
||||
"HchC39XzpDGoInZB1a3OhcHm+xUtLpMJ0G0oE5VFEynZreZoEIY4JxspQ7LPsay9\r\n"
|
||||
"fx3Tlk09gEMQgVCeCNiQwUxZdtLau2x61LNcdZCKN7FbFLJszv1U2uguELsTmi7E\r\n"
|
||||
"6pHrTziosQIDAQABo0AwPjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIDqDATBgNVHSUE\r\n"
|
||||
"DDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB\r\n"
|
||||
"AQBUw0hbTcT6crzODO4QAXU7z4Xxn0LkxbXEsoThG1QCVgMc4Bhpx8gyz5CLyHYz\r\n"
|
||||
"AiJOBFEeV0XEqoGTNMMFelR3Q5Tg9y1TYO3qwwAWxe6/brVzpts6NiG1uEMBnBFg\r\n"
|
||||
"oN1x3I9x4NpOxU5MU1dlIxvKs5HQCoNJ8D0SqOX9BV/pZqwEgiCbuWDWQAlxkFpn\r\n"
|
||||
"iLonlkVI5hTuybCSBsa9FEI9M6JJn9LZmlH90FYHeS4t6P8eOJCeekHL0jUG4Iae\r\n"
|
||||
"DMP12h8Sd0yxIKmmZ+Q/p/D/BkuHf5Idv3hgyLkZ4mNznjK49wHaYM+BgBoL3Zeg\r\n"
|
||||
"gJ2sWjUlokrbHswSBLLbUJIF\r\n"
|
||||
"-----END CERTIFICATE-----\r\n";
|
||||
|
||||
const char *server_key =
|
||||
"-----BEGIN PRIVATE KEY-----\r\n"
|
||||
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCujwJbVfTFoaYa\r\n"
|
||||
"QNozEptdYDkvD6x4DrW2KgatIhDO6oth4mgWfBWAXSMHk9OP7cSIYwFwD+aHvdUC\r\n"
|
||||
"2FMWYkHGP5qAinv9y9QGmB8sKWZJgvZ9mLoWh3P5/2ZyvLddHDs7q+Vqa1Z9NfxM\r\n"
|
||||
"CYxJHo9dUEcuCOzmkM4UWas0CwzmwthTX/UwW8TZn0idMjkbjfsgTVZyFFJulh0d\r\n"
|
||||
"yELf1fOkMagidkHVrc6Fweb7FS0ukwnQbSgTlUUTKdmt5mgQhjgnGylDss+xrL1/\r\n"
|
||||
"HdOWTT2AQxCBUJ4I2JDBTFl20tq7bHrUs1x1kIo3sVsUsmzO/VTa6C4QuxOaLsTq\r\n"
|
||||
"ketPOKixAgMBAAECggEAI+uNwpnHirue4Jwjyoqzqd1ZJxQEm5f7UIcJZKsz5kBh\r\n"
|
||||
"ej0KykWybv27bZ2/1UhKPv6QlyzOdXRc1v8I6fxCKLeB5Z2Zsjo1YT4AfCfwwoPO\r\n"
|
||||
"kT3SXTx2YyVpQYcP/HsIvVi8FtALtixbxJHaall9iugwHYr8pN17arihAE6d0wZC\r\n"
|
||||
"JXtXRjUWwjKzXP8FoH4KhyadhHbDwIbbJe3cyLfdvp54Gr0YHha0JcOxYgDYNya4\r\n"
|
||||
"OKxlCluI+hPF31iNzOmFLQVrdYynyPcR6vY5XOiANKE2iNbqCzRb54CvW9WMqObX\r\n"
|
||||
"RD9t3DMOxGsbVNIwyzZndWy13HoQMGnrHfnGak9ueQKBgQDiVtOqYfLnUnTxvJ/b\r\n"
|
||||
"qlQZr2ZmsYPZztxlP+DSqZGPD+WtGSo9+rozWfzjTv3KGIDLvf+GFVmjVHwlLQfd\r\n"
|
||||
"u7eTemWHFc4HK68wruzPO/FdyVpQ4w9v3Usg+ll4a/PDEId0fDMjAr6kk4LC6t8y\r\n"
|
||||
"9fJR0HjOz57jVnlrDt3v50G8BwKBgQDFbw+jRiUxXnBbDyXZLi+I4iGBGdC+CbaJ\r\n"
|
||||
"CmsM6/TsOFc+GRsPwQF1gCGqdaURw76noIVKZJOSc8I+yiwU6izyh/xaju5JiWQd\r\n"
|
||||
"kwbU1j4DE6GnxmT3ARmB7VvCxjaEZEAtICWs1QTKRz7PcTV8yr7Ng1A3VIy+NSpo\r\n"
|
||||
"LFMMmk83hwKBgQDVCEwpLg/mUeHoNVVw95w4oLKNLb+gHeerFLiTDy8FrDzM88ai\r\n"
|
||||
"l37yHly7xflxYia3nZkHpsi7xiUjCINC3BApKyasQoWskh1OgRY653yCfaYYQ96f\r\n"
|
||||
"t3WjEH9trI2+p6wWo1+uMEMnu/9zXoW9/WeaQdGzNg+igh29+jxCNTPVuQKBgGV4\r\n"
|
||||
"CN9vI5pV4QTLqjYOSJvfLDz/mYqxz0BrPE1tz3jAFAZ0PLZCCY/sBGFpCScyJQBd\r\n"
|
||||
"vWNYgYeZOtGuci1llSgov4eDQfBFTlDsyWwFl+VY55IkoqtXw1ZFOQ3HdSlhpKIM\r\n"
|
||||
"jZBgApA7QYq3sjeqs5lHzahCKftvs5XKgfxOKjxtAoGBALdnYe6xkDvGLvI51Yr+\r\n"
|
||||
"Dy0TNcB5W84SxUKvM7DVEomy1QPB57ZpyQaoBq7adOz0pWJXfp7qo4950ZOhBGH1\r\n"
|
||||
"hKbZ6c4ggwVJy2j49EgMok5NGCKvPAtabbR6H8Mz8DW9aXURxhWJvij+Qw1fWK4b\r\n"
|
||||
"7G/qUI9iE5iUU7MkIcLIbTf/\r\n"
|
||||
"-----END PRIVATE KEY-----\r\n";
|
||||
|
||||
const char *index_page =
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html lang=\"en\">\n"
|
||||
"<head>\n"
|
||||
" <meta charset=\"utf-8\" />\n"
|
||||
" <title>WebSocket Test</title>\n"
|
||||
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n"
|
||||
" <style type=\"text/css\">\n"
|
||||
" body {\n"
|
||||
" background-color: #789; margin: 0;\n"
|
||||
" padding: 0; font: 14px Helvetica, Arial, sans-serif;\n"
|
||||
" }\n"
|
||||
" div.content {\n"
|
||||
" width: 800px; margin: 2em auto; padding: 20px 50px;\n"
|
||||
" background-color: #fff; border-radius: 1em;\n"
|
||||
" }\n"
|
||||
" #messages {\n"
|
||||
" border: 2px solid #fec; border-radius: 1em;\n"
|
||||
" height: 10em; overflow: scroll; padding: 0.5em 1em;\n"
|
||||
" }\n"
|
||||
" a:link, a:visited { color: #69c; text-decoration: none; }\n"
|
||||
" @media (max-width: 700px) {\n"
|
||||
" body { background-color: #fff; }\n"
|
||||
" div.content {\n"
|
||||
" width: auto; margin: 0 auto; border-radius: 0;\n"
|
||||
" padding: 1em;\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"</style>\n"
|
||||
"\n"
|
||||
"<script language=\"javascript\" type=\"text/javascript\">\n"
|
||||
"\n"
|
||||
" var rooms = [];\n"
|
||||
" var ws = new WebSocket(\'ws://\' + location.host + \'/ws\');\n"
|
||||
"\n"
|
||||
" if (!window.console) { window.console = { log: function() {} } };\n"
|
||||
"\n"
|
||||
" ws.onopen = function(ev) { console.log(ev); };\n"
|
||||
" ws.onerror = function(ev) { console.log(ev); };\n"
|
||||
" ws.onclose = function(ev) { console.log(ev); };\n"
|
||||
" ws.onmessage = function(ev) {\n"
|
||||
" console.log(ev);\n"
|
||||
" var div = document.createElement(\'div\');\n"
|
||||
" div.innerHTML = ev.data;\n"
|
||||
" document.getElementById(\'messages\').appendChild(div);\n"
|
||||
"\n"
|
||||
" };\n"
|
||||
"\n"
|
||||
" window.onload = function() {\n"
|
||||
" document.getElementById(\'send_button\').onclick = function(ev) {\n"
|
||||
" var msg = document.getElementById(\'send_input\').value;\n"
|
||||
" document.getElementById(\'send_input\').value = \'\';\n"
|
||||
" ws.send(msg);\n"
|
||||
" };\n"
|
||||
" document.getElementById(\'send_input\').onkeypress = function(ev) {\n"
|
||||
" if (ev.keyCode == 13 || ev.which == 13) {\n"
|
||||
" document.getElementById(\'send_button\').click();\n"
|
||||
" }\n"
|
||||
" };\n"
|
||||
" };\n"
|
||||
"</script>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
" <div class=\"content\">\n"
|
||||
" <h1>Websocket PubSub Demonstration</h1>\n"
|
||||
"\n"
|
||||
" <p>\n"
|
||||
" This page demonstrates how Mongoose could be used to implement\n"
|
||||
" <a href=\"http://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern\">\n"
|
||||
" publish–subscribe pattern</a>. Open this page in several browser\n"
|
||||
" windows. Each window initiates persistent\n"
|
||||
" <a href=\"http://en.wikipedia.org/wiki/WebSocket\">WebSocket</a>\n"
|
||||
" connection with the server, making each browser window a websocket client.\n"
|
||||
" Send messages, and see messages sent by other clients.\n"
|
||||
" </p>\n"
|
||||
"\n"
|
||||
" <div id=\"messages\">\n"
|
||||
" </div>\n"
|
||||
"\n"
|
||||
" <p>\n"
|
||||
" <input type=\"text\" id=\"send_input\" />\n"
|
||||
" <button id=\"send_button\">Send Message</button>\n"
|
||||
" </p>\n"
|
||||
" </div>\n"
|
||||
"</body>\n"
|
||||
"</html>\n";
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
void broadcast(MongooseHttpWebSocketConnection *from, MongooseString msg)
|
||||
{
|
||||
char buf[500];
|
||||
char addr[32];
|
||||
mg_sock_addr_to_str(from->getRemoteAddress(), addr, sizeof(addr),
|
||||
MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s %.*s", addr, (int) msg.length(), msg.c_str());
|
||||
printf("%s\n", buf);
|
||||
server.sendAll(from, buf);
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
|
||||
#ifdef START_ESP_WIFI
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
if (WiFi.waitForConnectResult() != WL_CONNECTED)
|
||||
{
|
||||
Serial.printf("WiFi Failed!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.print("IP Address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
Serial.print("Hostname: ");
|
||||
#ifdef ESP32
|
||||
Serial.println(WiFi.getHostname());
|
||||
#elif defined(ESP8266)
|
||||
Serial.println(WiFi.hostname());
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Mongoose.begin();
|
||||
|
||||
#ifdef SIMPLE_SERVER_SECURE
|
||||
if(false == server.begin(443, server_pem, server_key)) {
|
||||
Serial.print("Failed to start server");
|
||||
return;
|
||||
}
|
||||
#else
|
||||
server.begin(80);
|
||||
#endif
|
||||
|
||||
server.on("/$", HTTP_GET, [](MongooseHttpServerRequest *request) {
|
||||
request->send(200, "text/html", index_page);
|
||||
});
|
||||
|
||||
// Test the stream response class
|
||||
server.on("/ws$")->
|
||||
onConnect([](MongooseHttpWebSocketConnection *connection) {
|
||||
broadcast(connection, MongooseString("++ joined"));
|
||||
})->
|
||||
onClose([](MongooseHttpServerRequest *c) {
|
||||
MongooseHttpWebSocketConnection *connection = static_cast<MongooseHttpWebSocketConnection *>(c);
|
||||
broadcast(connection, MongooseString("++ left"));
|
||||
})->
|
||||
onFrame([](MongooseHttpWebSocketConnection *connection, int flags, uint8_t *data, size_t len) {
|
||||
broadcast(connection, MongooseString((const char *)data, len));
|
||||
});
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
Mongoose.poll(1000);
|
||||
Serial.printf("Free memory %u\n", ESP.getFreeHeap());
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
|
||||
This directory is intended for PIO Unit Testing and project tests.
|
||||
|
||||
Unit Testing is a software testing method by which individual units of
|
||||
source code, sets of one or more MCU program modules together with associated
|
||||
control data, usage procedures, and operating procedures, are tested to
|
||||
determine whether they are fit for use. Unit testing finds problems early
|
||||
in the development cycle.
|
||||
|
||||
More information about PIO Unit Testing:
|
||||
- https://docs.platformio.org/page/plus/unit-testing.html
|
||||
@@ -1,35 +0,0 @@
|
||||
# Name: REST Client
|
||||
# Id: humao.rest-client
|
||||
# Description: REST Client for Visual Studio Code
|
||||
# Version: 0.21.3
|
||||
# Publisher: Huachao Mao
|
||||
# VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=humao.rest-client
|
||||
|
||||
@baseUrl = http://172.16.0.87
|
||||
|
||||
###
|
||||
|
||||
GET {{baseUrl}}/ HTTP/1.1
|
||||
|
||||
###
|
||||
|
||||
GET {{baseUrl}}/get?message=Hello+World HTTP/1.1
|
||||
|
||||
###
|
||||
|
||||
POST {{baseUrl}}/post HTTP/1.1
|
||||
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
|
||||
|
||||
message=Hello+World
|
||||
|
||||
###
|
||||
|
||||
GET {{baseUrl}}/someRandomFile HTTP/1.1
|
||||
|
||||
###
|
||||
|
||||
GET {{baseUrl}}/basic HTTP/1.1
|
||||
|
||||
###
|
||||
|
||||
GET {{baseUrl}}/stream HTTP/1.1
|
||||
@@ -1,66 +1,73 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PsychicHTTP Demo</title>
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Basic Request Examples</h1>
|
||||
<ul>
|
||||
<li><a href="/api?foo=bar">API Call</a></li>
|
||||
<li><a href="/redirect">Redirection</a></li>
|
||||
<li><a href="/auth-digest">Authentication (Digest)</a></li>
|
||||
<li><a href="/auth-basic">Authentication (Basic)</a></li>
|
||||
<li><a href="/cookies">Cookies Demo</a></li>
|
||||
<li><a href="/404">404</a></li>
|
||||
</ul>
|
||||
|
||||
<h1>Static Serving</h1>
|
||||
<p>
|
||||
<a href="/alien.png"><img width="60" src="/alien.png"></a>
|
||||
<a href="/img/request_flow.png"><img width="60" src="/img/request_flow.png"></a>
|
||||
</p>
|
||||
<p><a href="/myfile.txt">Text File</a></p>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PsychicHTTP Demo</title>
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<h1>Simple POST Form</h1>
|
||||
<form action="/post" method="post">
|
||||
<label for="param1">Parameter 1:</label>
|
||||
<input type="text" id="param1" name="param1" value="Parameter 1">
|
||||
<br>
|
||||
<label for="param2">Parameter 2:</label>
|
||||
<input type="text" id="param2" name="param2" value="Parameter 2">
|
||||
<br>
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Basic Request Examples</h1>
|
||||
<ul>
|
||||
<li><a href="/api?foo=bar">API Call</a></li>
|
||||
<li><a href="/redirect">Redirection</a></li>
|
||||
<li><a href="/auth-digest">Authentication (Digest)</a></li>
|
||||
<li><a href="/auth-basic">Authentication (Basic)</a></li>
|
||||
<li><a href="/cookies">Cookies Demo</a></li>
|
||||
<li><a href="/404">404</a></li>
|
||||
</ul>
|
||||
|
||||
<h1>Basic File Upload</h1>
|
||||
<table border="0">
|
||||
<tr>
|
||||
<td>
|
||||
<label for="newfile">Upload a file</label>
|
||||
</td>
|
||||
<td colspan="2">
|
||||
<input id="newfile" type="file" onchange="setpath()" style="width:100%;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="filepath">Set path on server</label>
|
||||
</td>
|
||||
<td>
|
||||
<input id="filepath" type="text" style="width:100%;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<button id="upload" type="button" onclick="upload()">Upload</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<script>
|
||||
<h1>Utilities</h1>
|
||||
<ul>
|
||||
<li><a href="/websocket-test.html">WebSocket Tester</a></li>
|
||||
</ul>
|
||||
|
||||
<h1>Static Serving</h1>
|
||||
<p>
|
||||
<a href="/alien.png"><img width="60" src="/alien.png"></a>
|
||||
<a href="/img/request_flow.png"><img width="60" src="/img/request_flow.png"></a>
|
||||
</p>
|
||||
<p><a href="/myfile.txt">Text File</a></p>
|
||||
|
||||
<h1>Simple POST Form</h1>
|
||||
<form action="/post" method="post">
|
||||
<label for="param1">Parameter 1:</label>
|
||||
<input type="text" id="param1" name="param1" value="Parameter 1">
|
||||
<br>
|
||||
<label for="param2">Parameter 2:</label>
|
||||
<input type="text" id="param2" name="param2" value="Parameter 2">
|
||||
<br>
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
|
||||
<h1>Basic File Upload</h1>
|
||||
<table border="0">
|
||||
<tr>
|
||||
<td>
|
||||
<label for="newfile">Upload a file</label>
|
||||
</td>
|
||||
<td colspan="2">
|
||||
<input id="newfile" type="file" onchange="setpath()" style="width:100%;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="filepath">Set path on server</label>
|
||||
</td>
|
||||
<td>
|
||||
<input id="filepath" type="text" style="width:100%;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<button id="upload" type="button" onclick="upload()">Upload</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<script>
|
||||
function setpath() {
|
||||
var default_path = document.getElementById("newfile").files[0].name;
|
||||
document.getElementById("filepath").value = default_path;
|
||||
@@ -72,7 +79,7 @@
|
||||
|
||||
/* Max size of an individual file. Make sure this
|
||||
* value is same as that set in file_server.c */
|
||||
var MAX_FILE_SIZE = 2048*1024;
|
||||
var MAX_FILE_SIZE = 2048 * 1024;
|
||||
var MAX_FILE_SIZE_STR = "2MB";
|
||||
|
||||
if (fileInput.length == 0) {
|
||||
@@ -81,9 +88,9 @@
|
||||
alert("File path on server is not set!");
|
||||
} else if (filePath.indexOf(' ') >= 0) {
|
||||
alert("File path on server cannot have spaces!");
|
||||
} else if (filePath[filePath.length-1] == '/') {
|
||||
} else if (filePath[filePath.length - 1] == '/') {
|
||||
alert("File name not specified after path!");
|
||||
} else if (fileInput[0].size > 200*1024) {
|
||||
} else if (fileInput[0].size > 200 * 1024) {
|
||||
alert("File size must be less than 200KB!");
|
||||
} else {
|
||||
document.getElementById("newfile").disabled = true;
|
||||
@@ -92,7 +99,7 @@
|
||||
|
||||
var file = fileInput[0];
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
xhttp.onreadystatechange = function () {
|
||||
if (xhttp.readyState == 4) {
|
||||
if (xhttp.status == 200) {
|
||||
document.open();
|
||||
@@ -111,126 +118,125 @@
|
||||
xhttp.send(file);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<h1>Multipart POST Form</h1>
|
||||
<form action="/multipart" method="post" enctype="multipart/form-data">
|
||||
<label for="param1">Parameter 1:</label>
|
||||
<input type="text" id="param1" name="param1" value="Parameter 1">
|
||||
<br>
|
||||
<h1>Multipart POST Form</h1>
|
||||
<form action="/multipart" method="post" enctype="multipart/form-data">
|
||||
<label for="param1">Parameter 1:</label>
|
||||
<input type="text" id="param1" name="param1" value="Parameter 1">
|
||||
<br>
|
||||
|
||||
<label for="param2">Parameter 2:</label>
|
||||
<input type="text" id="param2" name="param2" value="Parameter 2">
|
||||
<br>
|
||||
<label for="param2">Parameter 2:</label>
|
||||
<input type="text" id="param2" name="param2" value="Parameter 2">
|
||||
<br>
|
||||
|
||||
<label for="file-upload">File Upload:</label>
|
||||
<input type="file" id="file-upload" name="file_upload" accept=".txt, .html, .pdf, .png, .jpg, .gif" required>
|
||||
<br>
|
||||
|
||||
<input type="submit" value="Upload File">
|
||||
</form>
|
||||
<label for="file-upload">File Upload:</label>
|
||||
<input type="file" id="file-upload" name="file_upload" accept=".txt, .html, .pdf, .png, .jpg, .gif"
|
||||
required>
|
||||
<br>
|
||||
|
||||
<h1>Websocket Demo</h1>
|
||||
<input type="text" id="message_input" placeholder="Type your message">
|
||||
<button onclick="sendMessage()">Send Message</button>
|
||||
<button onclick="websocketConnect()">Connect</button>
|
||||
<div>
|
||||
<textarea id="websocket_output" style="width: 50%; height: 250px;"></textarea>
|
||||
</div>
|
||||
<input type="submit" value="Upload File">
|
||||
</form>
|
||||
|
||||
<script>
|
||||
let socket;
|
||||
const outputText = document.getElementById('websocket_output');
|
||||
const messageInput = document.getElementById('message_input');
|
||||
<h1>Websocket Demo</h1>
|
||||
<input type="text" id="message_input" placeholder="Type your message">
|
||||
<button onclick="sendMessage()">Send Message</button>
|
||||
<button onclick="websocketConnect()">Connect</button>
|
||||
<div>
|
||||
<textarea id="websocket_output" style="width: 50%; height: 250px;"></textarea>
|
||||
</div>
|
||||
|
||||
function websocketConnect()
|
||||
{
|
||||
// Create a WebSocket connection
|
||||
socket = new WebSocket((location.protocol === "https:" ? "wss://" : "ws://") + window.location.host + "/ws");
|
||||
|
||||
// Event handler for when the WebSocket connection is open
|
||||
socket.addEventListener('open', (event) => {
|
||||
outputText.value += `[socket] connection opened!\n`;
|
||||
outputText.scrollTop = outputText.scrollHeight;
|
||||
});
|
||||
|
||||
// Event handler for when a message is received from the WebSocket server
|
||||
socket.addEventListener('message', (event) => {
|
||||
// Echo the received message into the output div
|
||||
let data = event.data.trim();
|
||||
outputText.value += `[received] ${data}\n`;
|
||||
outputText.scrollTop = outputText.scrollHeight;
|
||||
});
|
||||
|
||||
// Event handler for when an error occurs with the WebSocket connection
|
||||
socket.addEventListener('error', (event) => {
|
||||
let data = event.data.trim();
|
||||
outputText.value += `[error] ${event.data}\n`;
|
||||
outputText.scrollTop = outputText.scrollHeight;
|
||||
});
|
||||
|
||||
// Event handler for when the WebSocket connection is closed
|
||||
socket.addEventListener('close', (event) => {
|
||||
outputText.value += `[socket] connection closed!\n`;
|
||||
});
|
||||
}
|
||||
|
||||
// Function to send a message to the WebSocket server
|
||||
function sendMessage() {
|
||||
if (socket.readyState == WebSocket.OPEN) {
|
||||
const message = messageInput.value.trim();
|
||||
if (message) {
|
||||
socket.send(message);
|
||||
messageInput.value = ''; // Clear the input field after sending the message
|
||||
outputText.value += `[sent] ${message}\n`;
|
||||
outputText.scrollTop = outputText.scrollHeight;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
outputText.value += `[error] Not Connected\n`;
|
||||
<script>
|
||||
let socket;
|
||||
const outputText = document.getElementById('websocket_output');
|
||||
const messageInput = document.getElementById('message_input');
|
||||
|
||||
function websocketConnect() {
|
||||
// Create a WebSocket connection
|
||||
socket = new WebSocket((location.protocol === "https:" ? "wss://" : "ws://") + window.location.host + "/ws");
|
||||
|
||||
// Event handler for when the WebSocket connection is open
|
||||
socket.addEventListener('open', (event) => {
|
||||
outputText.value += `[socket] connection opened!\n`;
|
||||
outputText.scrollTop = outputText.scrollHeight;
|
||||
});
|
||||
|
||||
// Event handler for when a message is received from the WebSocket server
|
||||
socket.addEventListener('message', (event) => {
|
||||
// Echo the received message into the output div
|
||||
let data = event.data.trim();
|
||||
outputText.value += `[received] ${data}\n`;
|
||||
outputText.scrollTop = outputText.scrollHeight;
|
||||
});
|
||||
|
||||
// Event handler for when an error occurs with the WebSocket connection
|
||||
socket.addEventListener('error', (event) => {
|
||||
let data = event.data.trim();
|
||||
outputText.value += `[error] ${event.data}\n`;
|
||||
outputText.scrollTop = outputText.scrollHeight;
|
||||
});
|
||||
|
||||
// Event handler for when the WebSocket connection is closed
|
||||
socket.addEventListener('close', (event) => {
|
||||
outputText.value += `[socket] connection closed!\n`;
|
||||
});
|
||||
}
|
||||
|
||||
// Function to send a message to the WebSocket server
|
||||
function sendMessage() {
|
||||
if (socket.readyState == WebSocket.OPEN) {
|
||||
const message = messageInput.value.trim();
|
||||
if (message) {
|
||||
socket.send(message);
|
||||
messageInput.value = ''; // Clear the input field after sending the message
|
||||
outputText.value += `[sent] ${message}\n`;
|
||||
outputText.scrollTop = outputText.scrollHeight;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<h1>EventSource Demo</h1>
|
||||
<button onclick="eventSourceConnect()">Connect</button>
|
||||
<div>
|
||||
<textarea id="eventsource_output" style="width: 50%; height: 250px;"></textarea>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const dataElement = document.getElementById('eventsource_output');
|
||||
|
||||
function eventSourceConnect()
|
||||
{
|
||||
const eventSource = new EventSource('/events');
|
||||
|
||||
eventSource.onopen = () => {
|
||||
dataElement.value += `[connected]\n`;
|
||||
dataElement.scrollTop = dataElement.scrollHeight;
|
||||
};
|
||||
|
||||
eventSource.addEventListener('millis', (event) => {
|
||||
let data = event.data.trim()
|
||||
dataElement.value += `[millis] ${data}\n`;
|
||||
dataElement.scrollTop = dataElement.scrollHeight;
|
||||
});
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
let data = event.data.trim()
|
||||
dataElement.value += `[message] ${data}\n`;
|
||||
dataElement.scrollTop = dataElement.scrollHeight;
|
||||
};
|
||||
|
||||
eventSource.onerror = (error) => {
|
||||
dataElement.value += `[error] ${error}\n`;
|
||||
dataElement.scrollTop = dataElement.scrollHeight;
|
||||
eventSource.close();
|
||||
};
|
||||
else {
|
||||
outputText.value += `[error] Not Connected\n`;
|
||||
outputText.scrollTop = outputText.scrollHeight;
|
||||
}
|
||||
</script>
|
||||
</main>
|
||||
</body>
|
||||
}
|
||||
</script>
|
||||
|
||||
<h1>EventSource Demo</h1>
|
||||
<button onclick="eventSourceConnect()">Connect</button>
|
||||
<div>
|
||||
<textarea id="eventsource_output" style="width: 50%; height: 250px;"></textarea>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const dataElement = document.getElementById('eventsource_output');
|
||||
|
||||
function eventSourceConnect() {
|
||||
const eventSource = new EventSource('/events');
|
||||
|
||||
eventSource.onopen = () => {
|
||||
dataElement.value += `[connected]\n`;
|
||||
dataElement.scrollTop = dataElement.scrollHeight;
|
||||
};
|
||||
|
||||
eventSource.addEventListener('millis', (event) => {
|
||||
let data = event.data.trim()
|
||||
dataElement.value += `[millis] ${data}\n`;
|
||||
dataElement.scrollTop = dataElement.scrollHeight;
|
||||
});
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
let data = event.data.trim()
|
||||
dataElement.value += `[message] ${data}\n`;
|
||||
dataElement.scrollTop = dataElement.scrollHeight;
|
||||
};
|
||||
|
||||
eventSource.onerror = (error) => {
|
||||
dataElement.value += `[error] ${error}\n`;
|
||||
dataElement.scrollTop = dataElement.scrollHeight;
|
||||
eventSource.close();
|
||||
};
|
||||
}
|
||||
</script>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
135
lib/PsychicHttp/examples/platformio/data/www/websocket-test.html
Normal file
135
lib/PsychicHttp/examples/platformio/data/www/websocket-test.html
Normal file
@@ -0,0 +1,135 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WebSocket Message Rate Test</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>WebSocket Message Rate Test</h1>
|
||||
<p>Time Remaining: <span id="time_remaining">0</span></p>
|
||||
<p>Messages Count: <span id="message_count">0</span></p>
|
||||
<p>Messages per second: <span id="rate">0</span></p>
|
||||
<label for="duration">Test Duration (seconds):</label>
|
||||
<input type="number" id="duration" value="30" min="1">
|
||||
<p>
|
||||
<button id="startTestSmall">Start Test (256b json)</button>
|
||||
<button id="startTestBig">Start Test (2k json)</button>
|
||||
</p>
|
||||
<p id="status"></p>
|
||||
|
||||
<script>
|
||||
let ws;
|
||||
let messageCount = 0;
|
||||
let startTime;
|
||||
let endTime;
|
||||
let testRunning = false;
|
||||
let smallPayload = { "user": { "id": 123456789, "name": "JohnDoe", "email": "johndoe@example.com", "preferences": { "theme": "dark", "notifications": { "email": true, "sms": false }, "language": "en" } } };
|
||||
let bigPayload = { "user": { "id": 123456789, "name": "JohnDoe", "email": "johndoe@example.com", "preferences": { "theme": "dark", "notifications": { "email": true, "sms": false }, "language": "en", "options": { "option1": "value1", "option2": "value2", "option3": "value3", "option4": "value4", "option5": "value5", "option6": "value6", "option7": "value7", "option8": "value8", "option9": "value9", "option10": "value10", "option11": "value11", "option12": "value12", "option13": "value13", "option14": "value14", "option15": "value15", "option16": "value16", "option17": "value17", "option18": "value18", "option19": "value19", "option20": "value20", "option21": "value21", "option22": "value22", "option23": "value23", "option24": "value24", "option25": "value25", "option26": "value26", "option27": "value27", "option28": "value28", "option29": "value29", "option30": "value30", "option31": "value31", "option32": "value32", "option33": "value33", "option34": "value34", "option35": "value35", "option36": "value36", "option37": "value37", "option38": "value38", "option39": "value39", "option40": "value40", "option41": "value41", "option42": "value42", "option43": "value43", "option44": "value44", "option45": "value45", "option46": "value46", "option47": "value47", "option48": "value48", "option49": "value49", "option50": "value50", "option51": "value51", "option52": "value52", "option53": "value53", "option54": "value54", "option55": "value55", "option56": "value56", "option57": "value57", "option58": "value58", "option59": "value59", "option60": "value60", "option61": "value61", "option62": "value62", "option63": "value63", "option64": "value64", "option65": "value65", "option66": "value66", "option67": "value67", "option68": "value68", "option69": "value69", "option70": "value70", "option71": "value71", "option72": "value72", "option73": "value73", "option74": "value74", "option75": "value75", "option76": "value76", "option77": "value77", "option78": "value78", "option79": "value79", "option80": "value80", "option81": "value81", "option82": "value82", "option83": "value83", "option84": "value84", "option85": "value85", "option86": "value86", "option87": "value87", "option88": "value88", "option89": "value89", "option90": "value90", "option91": "value91", "option92": "value92", "option93": "value93", "option94": "value94", "option95": "value95", "option96": "value96", "option97": "value97", "option98": "value98", "option99": "value99", "option100": "value100", "option101": "value101", "option102": "value102", "option103": "value103", "option104": "value104", "option105": "value105", "option106": "value106", "option107": "value107", "option108": "value108", "option109": "value109", "option110": "value110", "option111": "value111", "option112": "value112", "option113": "value113", "option114": "value114", "option115": "value115", "option116": "value116", "option117": "value117", "option118": "value118", "option119": "value119", "option120": "value120" } } } };
|
||||
let payload;
|
||||
|
||||
// Function to update the message rate
|
||||
function updateRate(force = false) {
|
||||
const currentTime = Date.now();
|
||||
const elapsedTime = (currentTime - startTime) / 1000; // in seconds
|
||||
let remainingTime = (endTime - currentTime) / 1000; // in seconds
|
||||
remainingTime = Math.max(0, remainingTime);
|
||||
const rate = messageCount / elapsedTime;
|
||||
|
||||
if (rate) {
|
||||
document.getElementById('rate').innerText = rate.toFixed(2);
|
||||
document.getElementById('message_count').innerText = messageCount;
|
||||
document.getElementById('time_remaining').innerText = remainingTime.toFixed(2);
|
||||
}
|
||||
|
||||
if (testRunning)
|
||||
setTimeout(updateRate, 25);
|
||||
}
|
||||
|
||||
function startTestSmall() {
|
||||
payload = smallPayload;
|
||||
startTest();
|
||||
}
|
||||
|
||||
function startTestBig() {
|
||||
payload = bigPayload;
|
||||
startTest();
|
||||
}
|
||||
|
||||
// Function to start the WebSocket connection and the test
|
||||
function startTest() {
|
||||
if (testRunning) return;
|
||||
|
||||
console.log("Payload length: " + JSON.stringify(payload).length);
|
||||
|
||||
document.getElementById('startTestSmall').disabled = true;
|
||||
document.getElementById('startTestBig').disabled = true;
|
||||
|
||||
document.getElementById('status').innerText = 'Connecting';
|
||||
|
||||
const durationInput = document.getElementById('duration').value;
|
||||
const testDuration = parseInt(durationInput) * 1000 || 60000; // default to 60 seconds if invalid
|
||||
|
||||
// Determine the WebSocket protocol based on the current protocol
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
||||
ws = new WebSocket(`${protocol}${window.location.host}/ws`);
|
||||
|
||||
ws.onopen = function () {
|
||||
document.getElementById('status').innerText = 'Connected';
|
||||
startTime = Date.now();
|
||||
endTime = startTime + testDuration;
|
||||
messageCount = 0;
|
||||
testRunning = true;
|
||||
sendAndReceiveMessage();
|
||||
|
||||
updateRate();
|
||||
};
|
||||
|
||||
ws.onmessage = function (event) {
|
||||
try {
|
||||
const parsedData = JSON.parse(event.data);
|
||||
|
||||
if (parsedData.user) {
|
||||
messageCount++;
|
||||
if (testRunning) {
|
||||
sendAndReceiveMessage();
|
||||
}
|
||||
}
|
||||
} catch (err) { }
|
||||
};
|
||||
|
||||
ws.onerror = function (error) {
|
||||
document.getElementById('status').innerText = 'Error: ' + error.message;
|
||||
};
|
||||
|
||||
ws.onclose = function () {
|
||||
//document.getElementById('status').innerText = 'Connection Closed';
|
||||
};
|
||||
}
|
||||
|
||||
// Function to send a message and wait for the response
|
||||
function sendAndReceiveMessage() {
|
||||
if (Date.now() >= endTime) {
|
||||
testRunning = false;
|
||||
document.getElementById('startTestSmall').disabled = false;
|
||||
document.getElementById('startTestBig').disabled = false;
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.close();
|
||||
}
|
||||
document.getElementById('status').innerText = 'Test Finished';
|
||||
updateRate(true);
|
||||
} else {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify(payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('startTestSmall').addEventListener('click', startTestSmall);
|
||||
document.getElementById('startTestBig').addEventListener('click', startTestBig);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,39 +0,0 @@
|
||||
|
||||
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
|
||||
@@ -1,46 +0,0 @@
|
||||
|
||||
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
|
||||
@@ -12,19 +12,44 @@
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp32-s3-devkitc-1
|
||||
upload_port = /dev/ttyACM0
|
||||
monitor_port = /dev/ttyACM1
|
||||
monitor_speed = 115200
|
||||
monitor_filters = esp32_exception_decoder
|
||||
lib_deps =
|
||||
; devmode: with this disabled make a symlink from platformio/lib to the PsychicHttp directory
|
||||
;hoeken/PsychicHttp
|
||||
bblanchon/ArduinoJson
|
||||
; hoeken/PsychicHttp
|
||||
; PIO is not able to consider installed project in CI
|
||||
;../..
|
||||
board_build.filesystem = littlefs
|
||||
|
||||
[env:default]
|
||||
build_flags =
|
||||
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_WARN
|
||||
;-D ENABLE_ASYNC
|
||||
-Wall
|
||||
-Wextra
|
||||
|
||||
; [env:arduino3]
|
||||
; platform = https://github.com/platformio/platform-espressif32.git
|
||||
; platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32#master
|
||||
[env:arduino2]
|
||||
platform = espressif32@6.8.1
|
||||
|
||||
[env:arduino2-ssl]
|
||||
platform = espressif32@6.8.1
|
||||
build_flags = -D PSY_ENABLE_SSL
|
||||
|
||||
[env:arduino2-regex]
|
||||
platform = espressif32@6.8.1
|
||||
build_flags = -D PSY_ENABLE_REGEX
|
||||
|
||||
[env:arduino3]
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
|
||||
[env:arduino3-ssl]
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
build_flags = -D PSY_ENABLE_SSL
|
||||
|
||||
[env:arduino3-regex]
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
build_flags = -D PSY_ENABLE_REGEX
|
||||
|
||||
[env:waveshare-4-3-touchscreen]
|
||||
lib_deps = ${env.lib_deps}
|
||||
https://github.com/esp-arduino-libs/ESP32_IO_Expander
|
||||
build_flags =
|
||||
-D PSY_ENABLE_SDCARD
|
||||
-D WAVESHARE_43_TOUCH
|
||||
|
||||
@@ -9,66 +9,113 @@
|
||||
*/
|
||||
|
||||
/**********************************************************************************************
|
||||
* Note: this demo relies on various files to be uploaded on the LittleFS partition
|
||||
* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image
|
||||
**********************************************************************************************/
|
||||
* Note: this demo relies on various files to be uploaded on the LittleFS partition
|
||||
* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image
|
||||
**********************************************************************************************/
|
||||
|
||||
#include "_secret.h"
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <LittleFS.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <esp_sntp.h>
|
||||
#include "_secret.h"
|
||||
#include <LittleFS.h>
|
||||
#include <PsychicHttp.h>
|
||||
//#include <PsychicHttpsServer.h> //uncomment this to enable HTTPS / SSL
|
||||
#include <WiFi.h>
|
||||
#include <esp_sntp.h>
|
||||
|
||||
// #define this to enable SD card support
|
||||
#ifdef PSY_ENABLE_SDCARD
|
||||
|
||||
#ifdef WAVESHARE_43_TOUCH
|
||||
#include <ESP_IOExpander_Library.h>
|
||||
// Extend IO Pin define
|
||||
#define TP_RST 1
|
||||
#define LCD_BL 2
|
||||
#define LCD_RST 3
|
||||
#define SD_CS 4
|
||||
#define USB_SEL 5
|
||||
|
||||
// I2C Pin define
|
||||
#define I2C_MASTER_NUM I2C_NUM_0
|
||||
#define I2C_MASTER_SDA_IO 8
|
||||
#define I2C_MASTER_SCL_IO 9
|
||||
|
||||
#define SD_MOSI 11
|
||||
#define SD_CLK 12
|
||||
#define SD_MISO 13
|
||||
#define SD_SS -1
|
||||
#else
|
||||
#define SD_MOSI 11
|
||||
#define SD_CLK 12
|
||||
#define SD_MISO 13
|
||||
#define SD_SS 5
|
||||
#endif
|
||||
|
||||
#include <FS.h>
|
||||
#include <SD.h>
|
||||
#include <SPI.h>
|
||||
#endif
|
||||
|
||||
// #define this to enable SSL at build (or switch to the 'ssl' build target in vscode)
|
||||
#ifdef PSY_ENABLE_SSL
|
||||
#include <PsychicHttpsServer.h>
|
||||
#endif
|
||||
|
||||
// debugging library
|
||||
#ifdef PSY_DEVMODE
|
||||
#include <ArduinoTrace.h>
|
||||
#endif
|
||||
|
||||
#ifndef WIFI_SSID
|
||||
#error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there."
|
||||
#endif
|
||||
|
||||
//Enter your WIFI credentials in secret.h
|
||||
const char *ssid = WIFI_SSID;
|
||||
const char *password = WIFI_PASS;
|
||||
// Enter your WIFI credentials in secret.h
|
||||
const char* ssid = WIFI_SSID;
|
||||
const char* password = WIFI_PASS;
|
||||
|
||||
// Set your SoftAP credentials
|
||||
const char *softap_ssid = "PsychicHttp";
|
||||
const char *softap_password = "";
|
||||
IPAddress softap_ip(10, 0, 0, 1);
|
||||
const char* softap_ssid = "PsychicHttp";
|
||||
const char* softap_password = "";
|
||||
IPAddress softap_ip(10, 0, 0, 1);
|
||||
|
||||
//credentials for the /auth-basic and /auth-digest examples
|
||||
const char *app_user = "admin";
|
||||
const char *app_pass = "admin";
|
||||
const char *app_name = "Your App";
|
||||
// credentials for the /auth-basic and /auth-digest examples
|
||||
const char* app_user = "admin";
|
||||
const char* app_pass = "admin";
|
||||
const char* app_name = "Your App";
|
||||
|
||||
//hostname for mdns (psychic.local)
|
||||
const char *local_hostname = "psychic";
|
||||
LoggingMiddleware loggingMiddleware;
|
||||
AuthenticationMiddleware basicAuth;
|
||||
AuthenticationMiddleware digestAuth;
|
||||
|
||||
//#define PSY_ENABLE_SSL to enable ssl
|
||||
// hostname for mdns (psychic.local)
|
||||
const char* local_hostname = "psychic";
|
||||
|
||||
// #define PSY_ENABLE_SSL to enable ssl
|
||||
#ifdef PSY_ENABLE_SSL
|
||||
bool app_enable_ssl = true;
|
||||
String server_cert;
|
||||
String server_key;
|
||||
bool app_enable_ssl = true;
|
||||
String server_cert;
|
||||
String server_key;
|
||||
#endif
|
||||
|
||||
//our main server object
|
||||
// our main server object
|
||||
#ifdef PSY_ENABLE_SSL
|
||||
PsychicHttpsServer server;
|
||||
PsychicHttpsServer server;
|
||||
#else
|
||||
PsychicHttpServer server;
|
||||
PsychicHttpServer server;
|
||||
#endif
|
||||
PsychicWebSocketHandler websocketHandler;
|
||||
PsychicEventSource eventSource;
|
||||
CorsMiddleware corsMiddleware;
|
||||
|
||||
//NTP server stuff
|
||||
const char *ntpServer1 = "pool.ntp.org";
|
||||
const char *ntpServer2 = "time.nist.gov";
|
||||
// NTP server stuff
|
||||
const char* ntpServer1 = "pool.ntp.org";
|
||||
const char* ntpServer2 = "time.nist.gov";
|
||||
const long gmtOffset_sec = 0;
|
||||
const int daylightOffset_sec = 0;
|
||||
struct tm timeinfo;
|
||||
|
||||
// Callback function (gets called when time adjusts via NTP)
|
||||
void timeAvailable(struct timeval *t)
|
||||
void timeAvailable(struct timeval* t)
|
||||
{
|
||||
if (!getLocalTime(&timeinfo)) {
|
||||
Serial.println("Failed to obtain time");
|
||||
@@ -83,10 +130,12 @@ void timeAvailable(struct timeval *t)
|
||||
|
||||
bool connectToWifi()
|
||||
{
|
||||
//dual client and AP mode
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
// WiFi.mode(WIFI_AP); // ap only mode
|
||||
// WiFi.mode(WIFI_STA); // client only mode
|
||||
WiFi.mode(WIFI_AP_STA); // ap and client
|
||||
|
||||
// Configure SoftAP
|
||||
// dual client and AP mode
|
||||
WiFi.softAPConfig(softap_ip, softap_ip, IPAddress(255, 255, 255, 0)); // subnet FF FF FF 00
|
||||
WiFi.softAP(softap_ssid, softap_password);
|
||||
IPAddress myIP = WiFi.softAPIP();
|
||||
@@ -107,10 +156,8 @@ bool connectToWifi()
|
||||
int numberOfTries = 20;
|
||||
|
||||
// Wait for the WiFi event
|
||||
while (true)
|
||||
{
|
||||
switch (WiFi.status())
|
||||
{
|
||||
while (true) {
|
||||
switch (WiFi.status()) {
|
||||
case WL_NO_SSID_AVAIL:
|
||||
Serial.println("[WiFi] SSID not found");
|
||||
break;
|
||||
@@ -140,15 +187,12 @@ bool connectToWifi()
|
||||
}
|
||||
delay(tryDelay);
|
||||
|
||||
if (numberOfTries <= 0)
|
||||
{
|
||||
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
|
||||
{
|
||||
} else {
|
||||
numberOfTries--;
|
||||
}
|
||||
}
|
||||
@@ -156,198 +200,247 @@ bool connectToWifi()
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef PSY_ENABLE_SDCARD
|
||||
bool setupSDCard()
|
||||
{
|
||||
#ifdef WAVESHARE_43_TOUCH
|
||||
ESP_IOExpander* expander = new ESP_IOExpander_CH422G((i2c_port_t)I2C_MASTER_NUM, ESP_IO_EXPANDER_I2C_CH422G_ADDRESS_000, I2C_MASTER_SCL_IO, I2C_MASTER_SDA_IO);
|
||||
expander->init();
|
||||
expander->begin();
|
||||
expander->multiPinMode(TP_RST | LCD_BL | LCD_RST | SD_CS | USB_SEL, OUTPUT);
|
||||
expander->multiDigitalWrite(TP_RST | LCD_BL | LCD_RST, HIGH);
|
||||
|
||||
// use extend GPIO for SD card
|
||||
expander->digitalWrite(SD_CS, LOW);
|
||||
SPI.setHwCs(false);
|
||||
#endif
|
||||
|
||||
SPI.begin(SD_CLK, SD_MISO, SD_MOSI, SD_SS);
|
||||
if (!SD.begin()) {
|
||||
Serial.println("SD Card Mount Failed");
|
||||
return false;
|
||||
}
|
||||
uint8_t cardType = SD.cardType();
|
||||
|
||||
if (cardType == CARD_NONE) {
|
||||
Serial.println("No SD card attached");
|
||||
return false;
|
||||
}
|
||||
|
||||
Serial.print("SD Card Type: ");
|
||||
if (cardType == CARD_MMC) {
|
||||
Serial.println("MMC");
|
||||
} else if (cardType == CARD_SD) {
|
||||
Serial.println("SDSC");
|
||||
} else if (cardType == CARD_SDHC) {
|
||||
Serial.println("SDHC");
|
||||
} else {
|
||||
Serial.println("UNKNOWN");
|
||||
}
|
||||
|
||||
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
|
||||
Serial.printf("SD Card Size: %lluMB\n", cardSize);
|
||||
Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
|
||||
Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void setup()
|
||||
{
|
||||
esp_log_level_set(PH_TAG, ESP_LOG_DEBUG);
|
||||
esp_log_level_set("httpd_uri", ESP_LOG_DEBUG);
|
||||
|
||||
Serial.begin(115200);
|
||||
delay(10);
|
||||
|
||||
Serial.printf("ESP-IDF Version: %s\n", esp_get_idf_version());
|
||||
|
||||
#ifdef ESP_ARDUINO_VERSION_STR
|
||||
Serial.printf("Arduino Version: %s\n", ESP_ARDUINO_VERSION_STR);
|
||||
#else
|
||||
Serial.printf("Arduino Version: %d.%d.%d\n", ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATCH);
|
||||
#endif
|
||||
Serial.printf("PsychicHttp Version: %s\n", PSYCHIC_VERSION_STR);
|
||||
|
||||
// We start by connecting to a WiFi network
|
||||
// To debug, please enable Core Debug Level to Verbose
|
||||
if (connectToWifi())
|
||||
{
|
||||
//Setup our NTP to get the current time.
|
||||
if (connectToWifi()) {
|
||||
// Setup our NTP to get the current time.
|
||||
sntp_set_time_sync_notification_cb(timeAvailable);
|
||||
sntp_servermode_dhcp(1); // (optional)
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2)
|
||||
esp_sntp_servermode_dhcp(1); // (optional)
|
||||
#else
|
||||
sntp_servermode_dhcp(1); // (optional)
|
||||
#endif
|
||||
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1, ntpServer2);
|
||||
|
||||
//set up our esp32 to listen on the local_hostname.local domain
|
||||
if (!MDNS.begin(local_hostname)) {
|
||||
// set up our esp32 to listen on the psychic.local domain
|
||||
if (MDNS.begin(local_hostname))
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
else
|
||||
Serial.println("Error starting mDNS");
|
||||
return;
|
||||
}
|
||||
MDNS.addService("http", "tcp", 80);
|
||||
|
||||
if(!LittleFS.begin())
|
||||
{
|
||||
if (!LittleFS.begin()) {
|
||||
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
|
||||
return;
|
||||
}
|
||||
|
||||
//look up our keys?
|
||||
#ifdef PSY_ENABLE_SSL
|
||||
if (app_enable_ssl)
|
||||
{
|
||||
File fp = LittleFS.open("/server.crt");
|
||||
if (fp)
|
||||
{
|
||||
server_cert = fp.readString();
|
||||
#ifdef PSY_ENABLE_SSL
|
||||
// look up our keys?
|
||||
if (app_enable_ssl) {
|
||||
File fp = LittleFS.open("/server.crt");
|
||||
if (fp) {
|
||||
server_cert = fp.readString();
|
||||
|
||||
// Serial.println("Server Cert:");
|
||||
// Serial.println(server_cert);
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("server.pem not found, SSL not available");
|
||||
app_enable_ssl = false;
|
||||
}
|
||||
fp.close();
|
||||
|
||||
File fp2 = LittleFS.open("/server.key");
|
||||
if (fp2)
|
||||
{
|
||||
server_key = fp2.readString();
|
||||
|
||||
// Serial.println("Server Key:");
|
||||
// Serial.println(server_key);
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.println("server.key not found, SSL not available");
|
||||
app_enable_ssl = false;
|
||||
}
|
||||
fp2.close();
|
||||
// Serial.println("Server Cert:");
|
||||
// Serial.println(server_cert);
|
||||
} else {
|
||||
Serial.println("server.pem not found, SSL not available");
|
||||
app_enable_ssl = false;
|
||||
}
|
||||
#endif
|
||||
fp.close();
|
||||
|
||||
//setup server config stuff here
|
||||
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
|
||||
File fp2 = LittleFS.open("/server.key");
|
||||
if (fp2) {
|
||||
server_key = fp2.readString();
|
||||
|
||||
#ifdef PSY_ENABLE_SSL
|
||||
server.ssl_config.httpd.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
|
||||
|
||||
//do we want secure or not?
|
||||
if (app_enable_ssl)
|
||||
{
|
||||
server.listen(443, server_cert.c_str(), server_key.c_str());
|
||||
|
||||
//this creates a 2nd server listening on port 80 and redirects all requests HTTPS
|
||||
PsychicHttpServer *redirectServer = new PsychicHttpServer();
|
||||
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
|
||||
redirectServer->listen(80);
|
||||
redirectServer->onNotFound([](PsychicRequest *request) {
|
||||
String url = "https://" + request->host() + request->url();
|
||||
return request->redirect(url.c_str());
|
||||
});
|
||||
// Serial.println("Server Key:");
|
||||
// Serial.println(server_key);
|
||||
} else {
|
||||
Serial.println("server.key not found, SSL not available");
|
||||
app_enable_ssl = false;
|
||||
}
|
||||
else
|
||||
server.listen(80);
|
||||
#else
|
||||
server.listen(80);
|
||||
#endif
|
||||
fp2.close();
|
||||
}
|
||||
|
||||
// do we want secure or not?
|
||||
if (app_enable_ssl) {
|
||||
server.setCertificate(server_cert.c_str(), server_key.c_str());
|
||||
|
||||
// this creates a 2nd server listening on port 80 and redirects all requests HTTPS
|
||||
PsychicHttpServer* redirectServer = new PsychicHttpServer();
|
||||
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
|
||||
redirectServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) {
|
||||
String url = "https://" + request->host() + request->url();
|
||||
return response->redirect(url.c_str()); });
|
||||
}
|
||||
#endif
|
||||
|
||||
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
|
||||
|
||||
//serve static files from LittleFS/www on / only to clients on same wifi network
|
||||
//this is where our /index.html file lives
|
||||
// curl -i http://psychic.local/
|
||||
PsychicStaticFileHandler* handler = server.serveStatic("/", LittleFS, "/www/");
|
||||
handler->setFilter(ON_STA_FILTER);
|
||||
handler->setCacheControl("max-age=60");
|
||||
loggingMiddleware.setOutput(Serial);
|
||||
|
||||
//serve static files from LittleFS/www-ap on / only to clients on SoftAP
|
||||
//this is where our /index.html file lives
|
||||
server.serveStatic("/", LittleFS, "/www-ap/")->setFilter(ON_AP_FILTER);
|
||||
basicAuth.setUsername(app_user);
|
||||
basicAuth.setPassword(app_pass);
|
||||
basicAuth.setRealm(app_name);
|
||||
basicAuth.setAuthMethod(HTTPAuthMethod::BASIC_AUTH);
|
||||
basicAuth.setAuthFailureMessage("You must log in.");
|
||||
|
||||
//serve static files from LittleFS/img on /img
|
||||
//it's more efficient to serve everything from a single www directory, but this is also possible.
|
||||
// curl -i http://psychic.local/img/request_flow.png
|
||||
digestAuth.setUsername(app_user);
|
||||
digestAuth.setPassword(app_pass);
|
||||
digestAuth.setRealm(app_name);
|
||||
digestAuth.setAuthMethod(HTTPAuthMethod::DIGEST_AUTH);
|
||||
digestAuth.setAuthFailureMessage("You must log in.");
|
||||
|
||||
// corsMiddleware.setAllowCredentials(true);
|
||||
// corsMiddleware.setOrigin("http://www.example.com,https://www.example.com,http://api.example.com,https://api.example.com");
|
||||
// corsMiddleware.setHeaders("Origin,X-Requested-With,Content-Type,Accept,Content-Type,Authorization,X-Access-Token");
|
||||
|
||||
server.addMiddleware(&loggingMiddleware);
|
||||
// this will send CORS headers on every HTTP_OPTIONS request that contains the Origin: header
|
||||
server.addMiddleware(&corsMiddleware);
|
||||
|
||||
// rewrites!
|
||||
server.rewrite("/rewrite", "/api?foo=rewrite");
|
||||
|
||||
// serve static files from LittleFS/www on / only to clients on same wifi network
|
||||
// this is where our /index.html file lives
|
||||
// curl -i http://psychic.local/
|
||||
server.serveStatic("/", LittleFS, "/www/")
|
||||
->setCacheControl("max-age=60")
|
||||
->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.
|
||||
// curl -i http://psychic.local/img/request_flow.png
|
||||
server.serveStatic("/img", LittleFS, "/img/");
|
||||
|
||||
//you can also serve single files
|
||||
// curl -i http://psychic.local/myfile.txt
|
||||
#ifdef PSY_ENABLE_SDCARD
|
||||
// if we detect an SD card, serve all files from sd:/ on http://psychic.local/sd
|
||||
if (setupSDCard())
|
||||
server.serveStatic("/sd", SD, "/");
|
||||
#endif
|
||||
|
||||
// you can also serve single files
|
||||
// curl -i http://psychic.local/myfile.txt
|
||||
server.serveStatic("/myfile.txt", LittleFS, "/custom.txt");
|
||||
|
||||
//example callback everytime a connection is opened
|
||||
server.onOpen([](PsychicClient *client) {
|
||||
Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
|
||||
});
|
||||
// example callback everytime a connection is opened
|
||||
server.onOpen([](PsychicClient* client) { Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str()); });
|
||||
|
||||
//example callback everytime a connection is closed
|
||||
server.onClose([](PsychicClient *client) {
|
||||
Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
|
||||
});
|
||||
// example callback everytime a connection is closed
|
||||
server.onClose([](PsychicClient* client) { Serial.printf("[http] connection #%u closed\n", client->socket()); });
|
||||
|
||||
//api - json message passed in as post body
|
||||
// curl -i -X POST -H "Content-Type: application/json" -d '{"foo":"bar"}' http://psychic.local/api
|
||||
server.on("/api", HTTP_POST, [](PsychicRequest *request, JsonVariant &json)
|
||||
{
|
||||
// api - json message passed in as post body
|
||||
// curl -i -X POST -H "Content-Type: application/json" -d '{"foo":"bar"}' http://psychic.local/api
|
||||
server.on("/api", HTTP_POST, [](PsychicRequest* request, PsychicResponse* resp, JsonVariant& json) {
|
||||
JsonObject input = json.as<JsonObject>();
|
||||
|
||||
//create our response json
|
||||
PsychicJsonResponse response = PsychicJsonResponse(request);
|
||||
// create our response json
|
||||
PsychicJsonResponse response(resp);
|
||||
JsonObject output = response.getRoot();
|
||||
|
||||
output["msg"] = "status";
|
||||
output["status"] = "success";
|
||||
output["millis"] = millis();
|
||||
output["method"] = request->methodStr();
|
||||
|
||||
//work with some params
|
||||
if (input.containsKey("foo"))
|
||||
{
|
||||
// work with some params
|
||||
if (input.containsKey("foo")) {
|
||||
String foo = input["foo"];
|
||||
output["foo"] = foo;
|
||||
}
|
||||
|
||||
|
||||
return response.send();
|
||||
});
|
||||
|
||||
//ip - get info about the client
|
||||
// curl -i http://psychic.local/ip
|
||||
server.on("/ip", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
// ip - get info about the client
|
||||
// curl -i http://psychic.local/ip
|
||||
server.on("/ip", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
|
||||
String output = "Your IP is: " + request->client()->remoteIP().toString();
|
||||
return request->reply(output.c_str());
|
||||
return response->send(output.c_str());
|
||||
});
|
||||
|
||||
//client connect/disconnect to a url
|
||||
// curl -i http://psychic.local/handler
|
||||
PsychicWebHandler *connectionHandler = new PsychicWebHandler();
|
||||
connectionHandler->onRequest([](PsychicRequest *request)
|
||||
{
|
||||
return request->reply("OK");
|
||||
});
|
||||
connectionHandler->onOpen([](PsychicClient *client) {
|
||||
Serial.printf("[handler] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
|
||||
});
|
||||
connectionHandler->onClose([](PsychicClient *client) {
|
||||
Serial.printf("[handler] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
|
||||
});
|
||||
// client connect/disconnect to a url
|
||||
// curl -i http://psychic.local/handler
|
||||
PsychicWebHandler* connectionHandler = new PsychicWebHandler();
|
||||
connectionHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) { return response->send("OK"); });
|
||||
connectionHandler->onOpen([](PsychicClient* client) { Serial.printf("[handler] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str()); });
|
||||
connectionHandler->onClose([](PsychicClient* client) { Serial.printf("[handler] connection #%u closed\n", client->socket()); });
|
||||
|
||||
//add it to our server
|
||||
// add it to our server
|
||||
server.on("/handler", connectionHandler);
|
||||
|
||||
//api - parameters passed in via query eg. /api?foo=bar
|
||||
// curl -i 'http://psychic.local/api?foo=bar'
|
||||
server.on("/api", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
//showcase some of the variables
|
||||
Serial.println(request->host());
|
||||
Serial.println(request->uri());
|
||||
Serial.println(request->path());
|
||||
Serial.println(request->queryString());
|
||||
|
||||
//create a response object
|
||||
//create our response json
|
||||
PsychicJsonResponse response = PsychicJsonResponse(request);
|
||||
// api - parameters passed in via query eg. /api?foo=bar
|
||||
// curl -i 'http://psychic.local/api?foo=bar'
|
||||
server.on("/api", HTTP_GET, [](PsychicRequest* request, PsychicResponse* resp) {
|
||||
// create our response json
|
||||
PsychicJsonResponse response = PsychicJsonResponse(resp);
|
||||
JsonObject output = response.getRoot();
|
||||
|
||||
output["msg"] = "status";
|
||||
output["status"] = "success";
|
||||
output["millis"] = millis();
|
||||
output["method"] = request->methodStr();
|
||||
|
||||
//work with some params
|
||||
if (request->hasParam("foo"))
|
||||
{
|
||||
// work with some params
|
||||
if (request->hasParam("foo")) {
|
||||
String foo = request->getParam("foo")->value();
|
||||
output["foo"] = foo;
|
||||
}
|
||||
@@ -355,112 +448,135 @@ void setup()
|
||||
return response.send();
|
||||
});
|
||||
|
||||
//JsonResponse example
|
||||
// curl -i http://psychic.local/json
|
||||
server.on("/json", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
PsychicJsonResponse response = PsychicJsonResponse(request);
|
||||
// curl -i -X GET 'http://psychic.local/any'
|
||||
// curl -i -X POST 'http://psychic.local/any'
|
||||
server.on("/any", HTTP_ANY, [](PsychicRequest* request, PsychicResponse* resp) {
|
||||
// create our response json
|
||||
PsychicJsonResponse response = PsychicJsonResponse(resp);
|
||||
JsonObject output = response.getRoot();
|
||||
|
||||
output["msg"] = "status";
|
||||
output["status"] = "success";
|
||||
output["millis"] = millis();
|
||||
output["method"] = request->methodStr();
|
||||
|
||||
return response.send();
|
||||
});
|
||||
|
||||
// curl -i 'http://psychic.local/simple'
|
||||
server.on("/simple", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
|
||||
return response->send("Simple");
|
||||
})
|
||||
->setURIMatchFunction(MATCH_SIMPLE);
|
||||
|
||||
#ifdef PSY_ENABLE_REGEX
|
||||
// curl -i 'http://psychic.local/regex/23'
|
||||
// curl -i 'http://psychic.local/regex/4223'
|
||||
server.on("^/regex/([\\d]+)/?$", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
|
||||
// look up our regex matches
|
||||
std::smatch matches;
|
||||
if (request->getRegexMatches(matches)) {
|
||||
String output;
|
||||
output += "Matches: " + String(matches.size()) + "<br/>\n";
|
||||
output += "Matched URI: " + String(matches.str(0).c_str()) + "<br/>\n";
|
||||
output += "Match 1: " + String(matches.str(1).c_str()) + "<br/>\n";
|
||||
|
||||
return response->send(output.c_str());
|
||||
} else
|
||||
return response->send("No regex match.");
|
||||
})
|
||||
->setURIMatchFunction(MATCH_REGEX);
|
||||
#endif
|
||||
|
||||
// JsonResponse example
|
||||
// curl -i http://psychic.local/json
|
||||
server.on("/json", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
|
||||
PsychicJsonResponse jsonResponse = PsychicJsonResponse(response);
|
||||
|
||||
char key[16];
|
||||
char value[32];
|
||||
JsonObject root = response.getRoot();
|
||||
for (int i=0; i<100; i++)
|
||||
{
|
||||
JsonObject root = jsonResponse.getRoot();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
sprintf(key, "key%d", i);
|
||||
sprintf(value, "value is %d", i);
|
||||
root[key] = value;
|
||||
}
|
||||
|
||||
return response.send();
|
||||
});
|
||||
|
||||
//how to redirect a request
|
||||
// curl -i http://psychic.local/redirect
|
||||
server.on("/redirect", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
return request->redirect("/alien.png");
|
||||
return jsonResponse.send();
|
||||
});
|
||||
|
||||
//how to do basic auth
|
||||
// curl -i --user admin:admin http://psychic.local/auth-basic
|
||||
server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
if (!request->authenticate(app_user, app_pass))
|
||||
return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in.");
|
||||
return request->reply("Auth Basic Success!");
|
||||
});
|
||||
// how to redirect a request
|
||||
// curl -i http://psychic.local/redirect
|
||||
server.on("/redirect", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->redirect("/alien.png"); });
|
||||
|
||||
//how to do digest auth
|
||||
// curl -i --user admin:admin http://psychic.local/auth-digest
|
||||
server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
if (!request->authenticate(app_user, app_pass))
|
||||
return request->requestAuthentication(DIGEST_AUTH, app_name, "You must log in.");
|
||||
return request->reply("Auth Digest Success!");
|
||||
});
|
||||
// how to do basic auth
|
||||
// curl -i --user admin:admin http://psychic.local/auth-basic
|
||||
server.on("/auth-basic", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
|
||||
return response->send("Auth Basic Success!");
|
||||
})->addMiddleware(&basicAuth);
|
||||
|
||||
//example of getting / setting cookies
|
||||
// curl -i -b cookie.txt -c cookie.txt http://psychic.local/cookies
|
||||
server.on("/cookies", HTTP_GET, [](PsychicRequest *request)
|
||||
{
|
||||
PsychicResponse response(request);
|
||||
// how to do digest auth
|
||||
// curl -i --user admin:admin http://psychic.local/auth-digest
|
||||
server.on("/auth-digest", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
|
||||
return response->send("Auth Digest Success!");
|
||||
})->addMiddleware(&digestAuth);
|
||||
|
||||
// example of getting / setting cookies
|
||||
// curl -i -b cookie.txt -c cookie.txt http://psychic.local/cookies
|
||||
server.on("/cookies", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
|
||||
int counter = 0;
|
||||
if (request->hasCookie("counter"))
|
||||
{
|
||||
counter = std::stoi(request->getCookie("counter").c_str());
|
||||
char cookie[14];
|
||||
size_t size = sizeof(cookie);
|
||||
if (request->getCookie("counter", cookie, &size) == ESP_OK) {
|
||||
// value is null-terminated.
|
||||
counter = std::stoi(cookie);
|
||||
counter++;
|
||||
}
|
||||
sprintf(cookie, "%d", counter);
|
||||
|
||||
char cookie[10];
|
||||
sprintf(cookie, "%i", counter);
|
||||
|
||||
response.setCookie("counter", cookie);
|
||||
response.setContent(cookie);
|
||||
return response.send();
|
||||
response->setCookie("counter", cookie);
|
||||
response->setContent(cookie);
|
||||
return response->send();
|
||||
});
|
||||
|
||||
//example of getting POST variables
|
||||
// curl -i -d "param1=value1¶m2=value2" -X POST http://psychic.local/post
|
||||
server.on("/post", HTTP_POST, [](PsychicRequest *request)
|
||||
{
|
||||
// example of getting POST variables
|
||||
// curl -i -d "param1=value1¶m2=value2" -X POST http://psychic.local/post
|
||||
// curl -F "param1=value1" -F "param2=value2" -X POST http://psychic.local/post
|
||||
server.on("/post", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) {
|
||||
String output;
|
||||
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
||||
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
|
||||
|
||||
return request->reply(output.c_str());
|
||||
return response->send(output.c_str());
|
||||
});
|
||||
|
||||
//you can set up a custom 404 handler.
|
||||
// curl -i http://psychic.local/404
|
||||
server.onNotFound([](PsychicRequest *request)
|
||||
{
|
||||
return request->reply(404, "text/html", "Custom 404 Handler");
|
||||
});
|
||||
// you can set up a custom 404 handler.
|
||||
// curl -i http://psychic.local/404
|
||||
server.onNotFound([](PsychicRequest* request, PsychicResponse* response) { return response->send(404, "text/html", "Custom 404 Handler"); });
|
||||
|
||||
//handle a very basic upload as post body
|
||||
PsychicUploadHandler *uploadHandler = new PsychicUploadHandler();
|
||||
uploadHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) {
|
||||
// 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());
|
||||
Serial.printf("Writing %d/%d bytes to: %s\n", (int)index + (int)len, request->contentLength(), path.c_str());
|
||||
|
||||
if (last)
|
||||
Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len);
|
||||
Serial.printf("%s is finished. Total bytes: %llu\n", path.c_str(), (uint64_t)index + (uint64_t)len);
|
||||
|
||||
//our first call?
|
||||
// our first call?
|
||||
if (!index)
|
||||
file = LittleFS.open(path, FILE_WRITE);
|
||||
else
|
||||
file = LittleFS.open(path, FILE_APPEND);
|
||||
|
||||
if(!file) {
|
||||
|
||||
if (!file) {
|
||||
Serial.println("Failed to open file");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if(!file.write(data, len)) {
|
||||
if (!file.write(data, len)) {
|
||||
Serial.println("Write failed");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
@@ -468,42 +584,41 @@ void setup()
|
||||
return ESP_OK;
|
||||
});
|
||||
|
||||
//gets called after upload has been handled
|
||||
uploadHandler->onRequest([](PsychicRequest *request)
|
||||
{
|
||||
// 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 request->reply(output.c_str());
|
||||
return response->send(output.c_str());
|
||||
});
|
||||
|
||||
//wildcard basic file upload - POST to /upload/filename.ext
|
||||
// use http://psychic.local/ to test
|
||||
// wildcard basic file upload - POST to /upload/filename.ext
|
||||
// use http://psychic.local/ to test
|
||||
server.on("/upload/*", HTTP_POST, uploadHandler);
|
||||
|
||||
//a little bit more complicated multipart form
|
||||
PsychicUploadHandler *multipartHandler = new PsychicUploadHandler();
|
||||
multipartHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) {
|
||||
// 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());
|
||||
// some progress over serial.
|
||||
Serial.printf("Writing %d bytes to: %s @ index %llu\n", (int)len, path.c_str(), index);
|
||||
if (last)
|
||||
Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len);
|
||||
Serial.printf("%s is finished. Total bytes: %llu\n", path.c_str(), (uint64_t)index + (uint64_t)len);
|
||||
|
||||
//our first call?
|
||||
// our first call?
|
||||
if (!index)
|
||||
file = LittleFS.open(path, FILE_WRITE);
|
||||
else
|
||||
file = LittleFS.open(path, FILE_APPEND);
|
||||
|
||||
if(!file) {
|
||||
|
||||
if (!file) {
|
||||
Serial.println("Failed to open file");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if(!file.write(data, len)) {
|
||||
if (!file.write(data, len)) {
|
||||
Serial.println("Write failed");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
@@ -511,57 +626,83 @@ void setup()
|
||||
return ESP_OK;
|
||||
});
|
||||
|
||||
//gets called after upload has been handled
|
||||
multipartHandler->onRequest([](PsychicRequest *request)
|
||||
{
|
||||
if (request->hasParam("file_upload"))
|
||||
{
|
||||
PsychicWebParameter *file = request->getParam("file_upload");
|
||||
// gets called after upload has been handled
|
||||
multipartHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) {
|
||||
String output;
|
||||
if (request->hasParam("file_upload")) {
|
||||
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 request->reply(output.c_str());
|
||||
}
|
||||
else
|
||||
return request->reply("No upload.");
|
||||
|
||||
if (request->hasParam("param1"))
|
||||
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
||||
if (request->hasParam("param2"))
|
||||
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
|
||||
|
||||
return response->send(output.c_str());
|
||||
});
|
||||
|
||||
//wildcard basic file upload - POST to /upload/filename.ext
|
||||
// use http://psychic.local/ to test
|
||||
// wildcard basic file upload - POST to /upload/filename.ext
|
||||
// use http://psychic.local/ to test
|
||||
// just multipart data: curl -F "param1=multi" -F "param2=part" http://psychic.local/multipart
|
||||
server.on("/multipart", HTTP_POST, multipartHandler);
|
||||
|
||||
//a websocket echo server
|
||||
// npm install -g wscat
|
||||
// wscat -c ws://psychic.local/ws
|
||||
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
|
||||
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
|
||||
// form only multipart handler
|
||||
// curl -F "param1=multi" -F "param2=part" http://psychic.local/multipart-data
|
||||
PsychicUploadHandler* multipartFormHandler = new PsychicUploadHandler();
|
||||
multipartFormHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) {
|
||||
String output;
|
||||
if (request->hasParam("param1"))
|
||||
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
||||
if (request->hasParam("param2"))
|
||||
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
|
||||
|
||||
return response->send(output.c_str());
|
||||
});
|
||||
server.on("/multipart-data", HTTP_POST, multipartFormHandler);
|
||||
|
||||
// a websocket echo server
|
||||
// npm install -g wscat
|
||||
// Plaintext: wscat -c ws://psychic.local/ws
|
||||
// SSL: wscat -n -c wss://psychic.local/ws
|
||||
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);
|
||||
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) {
|
||||
// Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), String((char*)frame->payload, frame->len).c_str());
|
||||
return request->reply(frame);
|
||||
});
|
||||
websocketHandler.onClose([](PsychicWebSocketClient *client) {
|
||||
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
|
||||
});
|
||||
websocketHandler.onClose([](PsychicWebSocketClient* client) { Serial.printf("[socket] connection #%u closed\n", client->socket()); });
|
||||
server.on("/ws", &websocketHandler);
|
||||
|
||||
//EventSource server
|
||||
// curl -i -N http://psychic.local/events
|
||||
eventSource.onOpen([](PsychicEventSourceClient *client) {
|
||||
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
|
||||
// EventSource server
|
||||
// curl -i -N http://psychic.local/events
|
||||
eventSource.onOpen([](PsychicEventSourceClient* client) {
|
||||
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().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());
|
||||
});
|
||||
eventSource.onClose([](PsychicEventSourceClient* client) { Serial.printf("[eventsource] connection #%u closed\n", client->socket()); });
|
||||
server.on("/events", &eventSource);
|
||||
|
||||
// example of using POST data inside the filter
|
||||
// works: curl -F "secret=password" http://psychic.local/post-filter
|
||||
// 404: curl -F "foo=bar" http://psychic.local/post-filter
|
||||
server.on("/post-filter", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) {
|
||||
String output;
|
||||
output += "Secret: " + request->getParam("secret")->value() + "<br/>\n";
|
||||
|
||||
return response->send(output.c_str());
|
||||
})
|
||||
->addFilter([](PsychicRequest* request) {
|
||||
request->loadParams();
|
||||
return request->hasParam("secret");
|
||||
});
|
||||
|
||||
server.begin();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,14 +711,21 @@ char output[60];
|
||||
|
||||
void loop()
|
||||
{
|
||||
if (millis() - lastUpdate > 2000)
|
||||
{
|
||||
sprintf(output, "Millis: %d\n", millis());
|
||||
if (millis() - lastUpdate > 1000) {
|
||||
sprintf(output, "Millis: %lu\n", millis());
|
||||
websocketHandler.sendAll(output);
|
||||
|
||||
sprintf(output, "%d", millis());
|
||||
sprintf(output, "%lu", millis());
|
||||
eventSource.send(output, "millis", millis(), 0);
|
||||
|
||||
lastUpdate = millis();
|
||||
}
|
||||
|
||||
// just some dev code to test that starting / stopping the server works okay.
|
||||
// delay(5000);
|
||||
// Serial.println("Stopping Server");
|
||||
// server.stop();
|
||||
// delay(5000);
|
||||
// Serial.println("Starting Server");
|
||||
// server.start();
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
|
||||
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
|
||||
@@ -9,38 +9,39 @@
|
||||
*/
|
||||
|
||||
/**********************************************************************************************
|
||||
* Note: this demo relies on various files to be uploaded on the LittleFS partition
|
||||
* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image
|
||||
**********************************************************************************************/
|
||||
* Note: this demo relies on various files to be uploaded on the LittleFS partition
|
||||
* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image
|
||||
**********************************************************************************************/
|
||||
|
||||
#include "_secret.h"
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <LittleFS.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <esp_sntp.h>
|
||||
#include "_secret.h"
|
||||
#include <LittleFS.h>
|
||||
#include <PsychicHttp.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_sntp.h>
|
||||
#include <freertos/queue.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;
|
||||
// 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";
|
||||
// hostname for mdns (psychic.local)
|
||||
const char* local_hostname = "psychic";
|
||||
|
||||
PsychicHttpServer server;
|
||||
PsychicWebSocketHandler websocketHandler;
|
||||
|
||||
typedef struct {
|
||||
int socket;
|
||||
char *buffer;
|
||||
size_t len;
|
||||
typedef struct
|
||||
{
|
||||
int socket;
|
||||
char* buffer;
|
||||
size_t len;
|
||||
} WebsocketMessage;
|
||||
|
||||
QueueHandle_t wsMessages;
|
||||
@@ -50,7 +51,7 @@ bool connectToWifi()
|
||||
Serial.print("[WiFi] Connecting to ");
|
||||
Serial.println(ssid);
|
||||
|
||||
//setup our wifi
|
||||
// setup our wifi
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
|
||||
@@ -113,7 +114,7 @@ void setup()
|
||||
Serial.begin(115200);
|
||||
delay(10);
|
||||
|
||||
//prepare our message queue of 10 messages
|
||||
// prepare our message queue of 10 messages
|
||||
wsMessages = xQueueCreate(10, sizeof(WebsocketMessage));
|
||||
if (wsMessages == 0)
|
||||
Serial.printf("Failed to create queue= %p\n", wsMessages);
|
||||
@@ -122,34 +123,33 @@ void setup()
|
||||
// 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)) {
|
||||
// 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())
|
||||
if (!LittleFS.begin())
|
||||
{
|
||||
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
|
||||
return;
|
||||
}
|
||||
|
||||
server.listen(80);
|
||||
// this is where our /index.html file lives
|
||||
// curl -i http://psychic.local/
|
||||
server.serveStatic("/", LittleFS, "/www/");
|
||||
|
||||
//this is where our /index.html file lives
|
||||
// curl -i http://psychic.local/
|
||||
PsychicStaticFileHandler* handler = server.serveStatic("/", LittleFS, "/www/");
|
||||
|
||||
//a websocket echo server
|
||||
// npm install -g wscat
|
||||
// wscat -c ws://psychic.local/ws
|
||||
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
|
||||
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
|
||||
client->sendMessage("Hello!");
|
||||
});
|
||||
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame)
|
||||
{
|
||||
// a websocket echo server
|
||||
// npm install -g wscat
|
||||
// wscat -c ws://psychic.local/ws
|
||||
websocketHandler.onOpen([](PsychicWebSocketClient* client)
|
||||
{
|
||||
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().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);
|
||||
|
||||
//we are allocating memory here, and the worker will free it
|
||||
@@ -181,11 +181,9 @@ void setup()
|
||||
if (!uxQueueSpacesAvailable(wsMessages))
|
||||
return request->reply("Queue Full");
|
||||
|
||||
return ESP_OK;
|
||||
});
|
||||
websocketHandler.onClose([](PsychicWebSocketClient *client) {
|
||||
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
|
||||
});
|
||||
return ESP_OK; });
|
||||
websocketHandler.onClose([](PsychicWebSocketClient* client)
|
||||
{ Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString().c_str()); });
|
||||
server.on("/ws", &websocketHandler);
|
||||
}
|
||||
}
|
||||
@@ -195,29 +193,30 @@ char output[60];
|
||||
|
||||
void loop()
|
||||
{
|
||||
//process our websockets outside the callback.
|
||||
// process our websockets outside the callback.
|
||||
WebsocketMessage message;
|
||||
while (xQueueReceive(wsMessages, &message, 0) == pdTRUE)
|
||||
{
|
||||
//make sure our client is still good.
|
||||
PsychicWebSocketClient *client = websocketHandler.getClient(message.socket);
|
||||
if (client == NULL) {
|
||||
// make sure our client is still good.
|
||||
PsychicWebSocketClient* client = websocketHandler.getClient(message.socket);
|
||||
if (client == NULL)
|
||||
{
|
||||
Serial.printf("[socket] client #%d bad, bailing\n", message.socket);
|
||||
return;
|
||||
}
|
||||
|
||||
//echo it back to the client.
|
||||
//alternatively, this is where you would deserialize a json message, parse it, and generate a response if needed
|
||||
// echo it back to the client.
|
||||
// alternatively, this is where you would deserialize a json message, parse it, and generate a response if needed
|
||||
client->sendMessage(HTTPD_WS_TYPE_TEXT, message.buffer, message.len);
|
||||
|
||||
//make sure to release our memory!
|
||||
// make sure to release our memory!
|
||||
free(message.buffer);
|
||||
}
|
||||
|
||||
//send a periodic update to all clients
|
||||
// send a periodic update to all clients
|
||||
if (millis() - lastUpdate > 2000)
|
||||
{
|
||||
sprintf(output, "Millis: %d\n", millis());
|
||||
sprintf(output, "Millis: %lu\n", millis());
|
||||
websocketHandler.sendAll(output);
|
||||
|
||||
lastUpdate = millis();
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
{
|
||||
"name": "PsychicHttp",
|
||||
"version": "1.2.1",
|
||||
"version": "2.0.0",
|
||||
"description": "Arduino style wrapper around ESP-IDF HTTP library. HTTP server with SSL + websockets. Works on esp32 and probably esp8266",
|
||||
"keywords": "network,http,https,tcp,ssl,tls,websocket,espasyncwebserver",
|
||||
"repository":
|
||||
{
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/hoeken/PsychicHttp"
|
||||
},
|
||||
"authors":
|
||||
[
|
||||
"authors": [
|
||||
{
|
||||
"name": "Zach Hoeken",
|
||||
"email": "hoeken@gmail.com",
|
||||
@@ -36,8 +34,8 @@
|
||||
},
|
||||
{
|
||||
"owner": "plageoj",
|
||||
"name" : "UrlEncode",
|
||||
"version" : "^1.0.1"
|
||||
"name": "UrlEncode",
|
||||
"version": "^1.0.1"
|
||||
}
|
||||
],
|
||||
"export": {
|
||||
@@ -50,4 +48,4 @@
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
name=PsychicHttp
|
||||
version=1.2.1
|
||||
version=2.0.0
|
||||
author=Zach Hoeken <hoeken@gmail.com>
|
||||
maintainer=Zach Hoeken <hoeken@gmail.com>
|
||||
sentence=PsychicHttp is a robust webserver that supports http/https + websockets.
|
||||
|
||||
30
lib/PsychicHttp/middleware.md
Normal file
30
lib/PsychicHttp/middleware.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# PsychicHandler
|
||||
|
||||
- [x] create addMiddleware()
|
||||
- [x] create runMiddleware()
|
||||
- [ ] move all the handler::canHandle() stuff into filter();
|
||||
- [ ] canHandle should be declared static
|
||||
|
||||
# PsychicEndpoint
|
||||
|
||||
- [ ] convert setAuthentication() to add AuthMiddleware instead.
|
||||
|
||||
## PsychicHttpServer
|
||||
|
||||
- [ ] add _chain
|
||||
- [ ] create addMiddleware()
|
||||
- [ ] create runMiddleware()
|
||||
- [ ] create removeMiddleware(name)
|
||||
- [ ] _filters -> _middleware
|
||||
- [ ] destructor / cleanup
|
||||
|
||||
# PsychicRequest
|
||||
|
||||
- [ ] add _response pointer to PsychicRequest, created in constructor
|
||||
- [ ] request->beginReply() should return existing _response pointer
|
||||
- [ ] requestAuthentication() -> should move to response?
|
||||
|
||||
# PsychicResponse
|
||||
|
||||
- how do we have extended classes when we have a pre-declared base PsychicResponse object?
|
||||
- the delegation style is really ugly and causes problems with inheritance
|
||||
7
lib/PsychicHttp/partitions-4MB.csv
Normal file
7
lib/PsychicHttp/partitions-4MB.csv
Normal file
@@ -0,0 +1,7 @@
|
||||
# Name ,Type ,SubType ,Offset ,Size ,Flags
|
||||
nvs ,data ,nvs ,36K ,20K ,
|
||||
otadata ,data ,ota ,56K ,8K ,
|
||||
app0 ,app ,ota_0 ,64K ,1856K ,
|
||||
app1 ,app ,ota_1 ,1920K ,1856K ,
|
||||
spiffs ,data ,spiffs ,3776K ,256K ,
|
||||
coredump ,data ,coredump ,4032K ,64K ,
|
||||
|
108
lib/PsychicHttp/platformio.ini
Normal file
108
lib/PsychicHttp/platformio.ini
Normal file
@@ -0,0 +1,108 @@
|
||||
; 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
|
||||
[platformio]
|
||||
lib_dir = .
|
||||
|
||||
|
||||
src_dir = examples/platformio/src
|
||||
data_dir = examples/platformio/data
|
||||
|
||||
; src_dir = examples/websockets/src
|
||||
; data_dir = examples/websockets/data
|
||||
|
||||
; src_dir = examples/arduino
|
||||
; data_dir = examples/arduino/data
|
||||
|
||||
; src_dir = examples/arduino/arduino_captive_portal
|
||||
; data_dir = examples/arduino/arduino_captive_portal/data
|
||||
|
||||
; src_dir = examples/arduino/arduino_ota
|
||||
; data_dir = examples/arduino/arduino_ota/data
|
||||
|
||||
[env]
|
||||
platform = espressif32
|
||||
framework = arduino
|
||||
board = esp32-s3-devkitc-1
|
||||
monitor_speed = 115200
|
||||
monitor_filters = esp32_exception_decoder
|
||||
board_build.partitions = partitions-4MB.csv
|
||||
board_build.filesystem = littlefs
|
||||
lib_deps =
|
||||
bblanchon/ArduinoJson
|
||||
plageoj/UrlEncode
|
||||
lib_ignore =
|
||||
examples
|
||||
build_flags =
|
||||
-Wall
|
||||
-Wextra
|
||||
-Og
|
||||
-D CONFIG_ARDUHAL_LOG_COLORS
|
||||
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
|
||||
|
||||
[env:arduino2]
|
||||
platform = espressif32@6.8.1
|
||||
|
||||
[env:arduino2-ssl]
|
||||
platform = espressif32@6.8.1
|
||||
build_flags = -DPSY_ENABLE_SSL
|
||||
|
||||
[env:arduino2-regex]
|
||||
platform = espressif32@6.8.1
|
||||
build_flags = -DPSY_ENABLE_REGEX
|
||||
|
||||
[env:arduino3]
|
||||
; platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc1/platform-espressif32.zip
|
||||
|
||||
[env:arduino3-ssl]
|
||||
; platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc1/platform-espressif32.zip
|
||||
build_flags = -DPSY_ENABLE_SSL
|
||||
|
||||
[env:arduino3-regex]
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
build_flags = -D PSY_ENABLE_REGEX
|
||||
|
||||
; [env:waveshare-4-3-touchscreen]
|
||||
; platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
; lib_deps = ${env.lib_deps}
|
||||
; https://github.com/esp-arduino-libs/ESP32_IO_Expander
|
||||
; build_flags =
|
||||
; -D PSY_ENABLE_SDCARD
|
||||
; -D WAVESHARE_43_TOUCH
|
||||
|
||||
[env:pioarduino-c6]
|
||||
; platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc1/platform-espressif32.zip
|
||||
board = esp32-c6-devkitc-1
|
||||
|
||||
[env:mathieu]
|
||||
; platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc1/platform-espressif32.zip
|
||||
board = esp32dev
|
||||
lib_deps = ${env.lib_deps}
|
||||
; bblanchon/ArduinoTrace@^1.2.0
|
||||
build_flags = ${env.build_flags}
|
||||
; -D PSY_DEVMODE
|
||||
; -D PSY_ENABLE_REGEX
|
||||
-D PSY_ENABLE_SSL
|
||||
|
||||
[env:hoeken]
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
|
||||
; platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc1/platform-espressif32.zip
|
||||
board = esp32-s3-devkitc-1
|
||||
lib_deps = ${env.lib_deps}
|
||||
bblanchon/ArduinoTrace@^1.2.0
|
||||
build_flags = ${env.build_flags}
|
||||
-D PSY_DEVMODE
|
||||
|
||||
[env:ci]
|
||||
platform = ${sysenv.PIO_PLATFORM}
|
||||
board = ${sysenv.PIO_BOARD}
|
||||
@@ -1,12 +1,12 @@
|
||||
|
||||
#include "ChunkPrinter.h"
|
||||
|
||||
ChunkPrinter::ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len) :
|
||||
_response(response),
|
||||
_buffer(buffer),
|
||||
_length(len),
|
||||
_pos(0)
|
||||
{}
|
||||
ChunkPrinter::ChunkPrinter(PsychicResponse* response, uint8_t* buffer, size_t len) : _response(response),
|
||||
_buffer(buffer),
|
||||
_length(len),
|
||||
_pos(0)
|
||||
{
|
||||
}
|
||||
|
||||
ChunkPrinter::~ChunkPrinter()
|
||||
{
|
||||
@@ -16,34 +16,34 @@ ChunkPrinter::~ChunkPrinter()
|
||||
size_t ChunkPrinter::write(uint8_t c)
|
||||
{
|
||||
esp_err_t err;
|
||||
|
||||
//if we're full, send a chunk
|
||||
|
||||
// if we're full, send a chunk
|
||||
if (_pos == _length)
|
||||
{
|
||||
_pos = 0;
|
||||
err = _response->sendChunk(_buffer, _length);
|
||||
|
||||
|
||||
if (err != ESP_OK)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
_buffer[_pos] = c;
|
||||
_pos++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t ChunkPrinter::write(const uint8_t *buffer, size_t size)
|
||||
size_t ChunkPrinter::write(const uint8_t* buffer, size_t size)
|
||||
{
|
||||
size_t written = 0;
|
||||
|
||||
|
||||
while (written < size)
|
||||
{
|
||||
size_t space = _length - _pos;
|
||||
size_t blockSize = std::min(space, size - written);
|
||||
|
||||
|
||||
memcpy(_buffer + _pos, buffer + written, blockSize);
|
||||
_pos += blockSize;
|
||||
|
||||
|
||||
if (_pos == _length)
|
||||
{
|
||||
_pos = 0;
|
||||
@@ -51,7 +51,7 @@ size_t ChunkPrinter::write(const uint8_t *buffer, size_t size)
|
||||
if (_response->sendChunk(_buffer, _length) != ESP_OK)
|
||||
return written;
|
||||
}
|
||||
written += blockSize; //Update if sent correctly.
|
||||
written += blockSize; // Update if sent correctly.
|
||||
}
|
||||
return written;
|
||||
}
|
||||
@@ -65,18 +65,19 @@ void ChunkPrinter::flush()
|
||||
}
|
||||
}
|
||||
|
||||
size_t ChunkPrinter::copyFrom(Stream &stream)
|
||||
size_t ChunkPrinter::copyFrom(Stream& stream)
|
||||
{
|
||||
size_t count = 0;
|
||||
|
||||
while (stream.available()){
|
||||
|
||||
while (stream.available())
|
||||
{
|
||||
|
||||
if (_pos == _length)
|
||||
{
|
||||
_response->sendChunk(_buffer, _length);
|
||||
_pos = 0;
|
||||
}
|
||||
|
||||
|
||||
size_t readBytes = stream.readBytes(_buffer + _pos, _length - _pos);
|
||||
_pos += readBytes;
|
||||
count += readBytes;
|
||||
|
||||
@@ -7,19 +7,19 @@
|
||||
class ChunkPrinter : public Print
|
||||
{
|
||||
private:
|
||||
PsychicResponse *_response;
|
||||
uint8_t *_buffer;
|
||||
PsychicResponse* _response;
|
||||
uint8_t* _buffer;
|
||||
size_t _length;
|
||||
size_t _pos;
|
||||
|
||||
public:
|
||||
ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len);
|
||||
ChunkPrinter(PsychicResponse* response, uint8_t* buffer, size_t len);
|
||||
~ChunkPrinter();
|
||||
|
||||
size_t write(uint8_t c) override;
|
||||
size_t write(const uint8_t *buffer, size_t size) override;
|
||||
|
||||
size_t copyFrom(Stream &stream);
|
||||
size_t write(uint8_t c) override;
|
||||
size_t write(const uint8_t* buffer, size_t size) override;
|
||||
|
||||
size_t copyFrom(Stream& stream);
|
||||
|
||||
void flush() override;
|
||||
};
|
||||
|
||||
426
lib/PsychicHttp/src/MultipartProcessor.cpp
Normal file
426
lib/PsychicHttp/src/MultipartProcessor.cpp
Normal file
@@ -0,0 +1,426 @@
|
||||
#include "MultipartProcessor.h"
|
||||
#include "PsychicRequest.h"
|
||||
|
||||
enum
|
||||
{
|
||||
EXPECT_BOUNDARY,
|
||||
PARSE_HEADERS,
|
||||
WAIT_FOR_RETURN1,
|
||||
EXPECT_FEED1,
|
||||
EXPECT_DASH1,
|
||||
EXPECT_DASH2,
|
||||
BOUNDARY_OR_DATA,
|
||||
DASH3_OR_RETURN2,
|
||||
EXPECT_FEED2,
|
||||
PARSING_FINISHED,
|
||||
PARSE_ERROR
|
||||
};
|
||||
|
||||
MultipartProcessor::MultipartProcessor(PsychicRequest* request, PsychicUploadCallback uploadCallback) : _request(request),
|
||||
_uploadCallback(uploadCallback),
|
||||
_temp(),
|
||||
_parsedLength(0),
|
||||
_multiParseState(EXPECT_BOUNDARY),
|
||||
_boundaryPosition(0),
|
||||
_itemStartIndex(0),
|
||||
_itemSize(0),
|
||||
_itemName(),
|
||||
_itemFilename(),
|
||||
_itemType(),
|
||||
_itemValue(),
|
||||
_itemBuffer(0),
|
||||
_itemBufferIndex(0),
|
||||
_itemIsFile(false)
|
||||
{
|
||||
}
|
||||
MultipartProcessor::~MultipartProcessor() {}
|
||||
|
||||
esp_err_t MultipartProcessor::process()
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
|
||||
_parsedLength = 0;
|
||||
|
||||
String value = _request->header("Content-Type");
|
||||
if (value.startsWith("multipart/"))
|
||||
{
|
||||
_boundary = value.substring(value.indexOf('=') + 1);
|
||||
_boundary.replace("\"", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(PH_TAG, "No multipart boundary found.");
|
||||
return ESP_ERR_HTTPD_INVALID_REQ;
|
||||
}
|
||||
|
||||
char* buf = (char*)malloc(FILE_CHUNK_SIZE);
|
||||
int received;
|
||||
unsigned long index = 0;
|
||||
|
||||
/* Content length of the request gives the size of the file being uploaded */
|
||||
int remaining = _request->contentLength();
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
#ifdef ENABLE_ASYNC
|
||||
httpd_sess_update_lru_counter(request->server()->server, request->client()->socket());
|
||||
#endif
|
||||
|
||||
/* Receive the file part by part into a buffer */
|
||||
if ((received = httpd_req_recv(_request->request(), buf, min(remaining, FILE_CHUNK_SIZE))) <= 0)
|
||||
{
|
||||
/* Retry if timeout occurred */
|
||||
if (received == HTTPD_SOCK_ERR_TIMEOUT)
|
||||
continue;
|
||||
// bail if we got an error
|
||||
else if (received == HTTPD_SOCK_ERR_FAIL)
|
||||
{
|
||||
ESP_LOGE(PH_TAG, "Socket error");
|
||||
err = ESP_FAIL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// parse it 1 byte at a time.
|
||||
for (int i = 0; i < received; i++)
|
||||
{
|
||||
/* Keep track of remaining size of the file left to be uploaded */
|
||||
remaining--;
|
||||
index++;
|
||||
|
||||
// send it to our parser
|
||||
_parseMultipartPostByte(buf[i], !remaining);
|
||||
_parsedLength++;
|
||||
}
|
||||
}
|
||||
|
||||
// dont forget to free our buffer
|
||||
free(buf);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t MultipartProcessor::process(const char* body)
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
_parsedLength = 0;
|
||||
|
||||
String value = _request->header("Content-Type");
|
||||
if (value.startsWith("multipart/"))
|
||||
{
|
||||
_boundary = value.substring(value.indexOf('=') + 1);
|
||||
_boundary.replace("\"", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(PH_TAG, "No multipart boundary found.");
|
||||
return ESP_ERR_HTTPD_INVALID_REQ;
|
||||
}
|
||||
|
||||
// loop over the whole string
|
||||
unsigned int size = strlen(body);
|
||||
for (unsigned i = 0; i < size; i++)
|
||||
{
|
||||
// send it to our parser
|
||||
_parseMultipartPostByte(body[i], i == size - 1);
|
||||
_parsedLength++;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
void MultipartProcessor::_handleUploadByte(uint8_t data, bool last)
|
||||
{
|
||||
_itemBuffer[_itemBufferIndex++] = data;
|
||||
|
||||
if (last || _itemBufferIndex == FILE_CHUNK_SIZE)
|
||||
{
|
||||
if (_uploadCallback)
|
||||
_uploadCallback(_request, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, last);
|
||||
|
||||
_itemBufferIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#define itemWriteByte(b) \
|
||||
do \
|
||||
{ \
|
||||
_itemSize++; \
|
||||
if (_itemIsFile) \
|
||||
_handleUploadByte(b, last); \
|
||||
else \
|
||||
_itemValue += (char)(b); \
|
||||
} while (0)
|
||||
|
||||
void MultipartProcessor::_parseMultipartPostByte(uint8_t data, bool last)
|
||||
{
|
||||
if (_multiParseState == PARSE_ERROR)
|
||||
{
|
||||
// not sure we can end up with an error during buffer fill, but jsut to be safe
|
||||
if (_itemBuffer != NULL)
|
||||
{
|
||||
free(_itemBuffer);
|
||||
_itemBuffer = NULL;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_parsedLength)
|
||||
{
|
||||
_multiParseState = EXPECT_BOUNDARY;
|
||||
_temp = String();
|
||||
_itemName = String();
|
||||
_itemFilename = String();
|
||||
_itemType = String();
|
||||
}
|
||||
|
||||
if (_multiParseState == WAIT_FOR_RETURN1)
|
||||
{
|
||||
if (data != '\r')
|
||||
{
|
||||
itemWriteByte(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiParseState = EXPECT_FEED1;
|
||||
}
|
||||
}
|
||||
else if (_multiParseState == EXPECT_BOUNDARY)
|
||||
{
|
||||
if (_parsedLength < 2 && data != '-')
|
||||
{
|
||||
ESP_LOGE(PH_TAG, "Multipart: No boundary");
|
||||
_multiParseState = PARSE_ERROR;
|
||||
return;
|
||||
}
|
||||
else if (_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data)
|
||||
{
|
||||
ESP_LOGE(PH_TAG, "Multipart: Multipart malformed");
|
||||
_multiParseState = PARSE_ERROR;
|
||||
return;
|
||||
}
|
||||
else if (_parsedLength - 2 == _boundary.length() && data != '\r')
|
||||
{
|
||||
ESP_LOGE(PH_TAG, "Multipart: Multipart missing carriage return");
|
||||
_multiParseState = PARSE_ERROR;
|
||||
return;
|
||||
}
|
||||
else if (_parsedLength - 3 == _boundary.length())
|
||||
{
|
||||
if (data != '\n')
|
||||
{
|
||||
ESP_LOGE(PH_TAG, "Multipart: Multipart missing newline");
|
||||
_multiParseState = PARSE_ERROR;
|
||||
return;
|
||||
}
|
||||
_multiParseState = PARSE_HEADERS;
|
||||
_itemIsFile = false;
|
||||
}
|
||||
}
|
||||
else if (_multiParseState == PARSE_HEADERS)
|
||||
{
|
||||
if ((char)data != '\r' && (char)data != '\n')
|
||||
_temp += (char)data;
|
||||
if ((char)data == '\n')
|
||||
{
|
||||
if (_temp.length())
|
||||
{
|
||||
if (_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase("Content-Type"))
|
||||
{
|
||||
_itemType = _temp.substring(14);
|
||||
_itemIsFile = true;
|
||||
}
|
||||
else if (_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase("Content-Disposition"))
|
||||
{
|
||||
_temp = _temp.substring(_temp.indexOf(';') + 2);
|
||||
while (_temp.indexOf(';') > 0)
|
||||
{
|
||||
String name = _temp.substring(0, _temp.indexOf('='));
|
||||
String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1);
|
||||
if (name == "name")
|
||||
{
|
||||
_itemName = nameVal;
|
||||
}
|
||||
else if (name == "filename")
|
||||
{
|
||||
_itemFilename = nameVal;
|
||||
_itemIsFile = true;
|
||||
}
|
||||
_temp = _temp.substring(_temp.indexOf(';') + 2);
|
||||
}
|
||||
String name = _temp.substring(0, _temp.indexOf('='));
|
||||
String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1);
|
||||
if (name == "name")
|
||||
{
|
||||
_itemName = nameVal;
|
||||
}
|
||||
else if (name == "filename")
|
||||
{
|
||||
_itemFilename = nameVal;
|
||||
_itemIsFile = true;
|
||||
}
|
||||
}
|
||||
_temp = String();
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiParseState = WAIT_FOR_RETURN1;
|
||||
// value starts from here
|
||||
_itemSize = 0;
|
||||
_itemStartIndex = _parsedLength;
|
||||
_itemValue = String();
|
||||
if (_itemIsFile)
|
||||
{
|
||||
if (_itemBuffer)
|
||||
free(_itemBuffer);
|
||||
_itemBuffer = (uint8_t*)malloc(FILE_CHUNK_SIZE);
|
||||
if (_itemBuffer == NULL)
|
||||
{
|
||||
ESP_LOGE(PH_TAG, "Multipart: Failed to allocate buffer");
|
||||
_multiParseState = PARSE_ERROR;
|
||||
return;
|
||||
}
|
||||
_itemBufferIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_multiParseState == EXPECT_FEED1)
|
||||
{
|
||||
if (data != '\n')
|
||||
{
|
||||
_multiParseState = WAIT_FOR_RETURN1;
|
||||
itemWriteByte('\r');
|
||||
_parseMultipartPostByte(data, last);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiParseState = EXPECT_DASH1;
|
||||
}
|
||||
}
|
||||
else if (_multiParseState == EXPECT_DASH1)
|
||||
{
|
||||
if (data != '-')
|
||||
{
|
||||
_multiParseState = WAIT_FOR_RETURN1;
|
||||
itemWriteByte('\r');
|
||||
itemWriteByte('\n');
|
||||
_parseMultipartPostByte(data, last);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiParseState = EXPECT_DASH2;
|
||||
}
|
||||
}
|
||||
else if (_multiParseState == EXPECT_DASH2)
|
||||
{
|
||||
if (data != '-')
|
||||
{
|
||||
_multiParseState = WAIT_FOR_RETURN1;
|
||||
itemWriteByte('\r');
|
||||
itemWriteByte('\n');
|
||||
itemWriteByte('-');
|
||||
_parseMultipartPostByte(data, last);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiParseState = BOUNDARY_OR_DATA;
|
||||
_boundaryPosition = 0;
|
||||
}
|
||||
}
|
||||
else if (_multiParseState == BOUNDARY_OR_DATA)
|
||||
{
|
||||
if (_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data)
|
||||
{
|
||||
_multiParseState = WAIT_FOR_RETURN1;
|
||||
itemWriteByte('\r');
|
||||
itemWriteByte('\n');
|
||||
itemWriteByte('-');
|
||||
itemWriteByte('-');
|
||||
uint8_t i;
|
||||
for (i = 0; i < _boundaryPosition; i++)
|
||||
itemWriteByte(_boundary.c_str()[i]);
|
||||
_parseMultipartPostByte(data, last);
|
||||
}
|
||||
else if (_boundaryPosition == _boundary.length() - 1)
|
||||
{
|
||||
_multiParseState = DASH3_OR_RETURN2;
|
||||
if (!_itemIsFile)
|
||||
{
|
||||
// External - Add parameter!
|
||||
_request->addParam(_itemName, _itemValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_itemSize)
|
||||
{
|
||||
if (_uploadCallback)
|
||||
{
|
||||
_uploadCallback(_request, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true);
|
||||
}
|
||||
_itemBufferIndex = 0;
|
||||
|
||||
// External - Add parameter!
|
||||
_request->addParam(new PsychicWebParameter(_itemName, _itemFilename, true, true, _itemSize));
|
||||
}
|
||||
free(_itemBuffer);
|
||||
_itemBuffer = NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_boundaryPosition++;
|
||||
}
|
||||
}
|
||||
else if (_multiParseState == DASH3_OR_RETURN2)
|
||||
{
|
||||
if (data == '-' && (_request->contentLength() - _parsedLength - 4) != 0)
|
||||
{
|
||||
ESP_LOGE(PH_TAG, "ERROR: The parser got to the end of the POST but is expecting more bytes!");
|
||||
_multiParseState = PARSE_ERROR;
|
||||
return;
|
||||
}
|
||||
if (data == '\r')
|
||||
{
|
||||
_multiParseState = EXPECT_FEED2;
|
||||
}
|
||||
else if (data == '-' && _request->contentLength() == (_parsedLength + 4))
|
||||
{
|
||||
_multiParseState = PARSING_FINISHED;
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiParseState = WAIT_FOR_RETURN1;
|
||||
itemWriteByte('\r');
|
||||
itemWriteByte('\n');
|
||||
itemWriteByte('-');
|
||||
itemWriteByte('-');
|
||||
uint8_t i;
|
||||
for (i = 0; i < _boundary.length(); i++)
|
||||
itemWriteByte(_boundary.c_str()[i]);
|
||||
_parseMultipartPostByte(data, last);
|
||||
}
|
||||
}
|
||||
else if (_multiParseState == EXPECT_FEED2)
|
||||
{
|
||||
if (data == '\n')
|
||||
{
|
||||
_multiParseState = PARSE_HEADERS;
|
||||
_itemIsFile = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiParseState = WAIT_FOR_RETURN1;
|
||||
itemWriteByte('\r');
|
||||
itemWriteByte('\n');
|
||||
itemWriteByte('-');
|
||||
itemWriteByte('-');
|
||||
uint8_t i;
|
||||
for (i = 0; i < _boundary.length(); i++)
|
||||
itemWriteByte(_boundary.c_str()[i]);
|
||||
itemWriteByte('\r');
|
||||
_parseMultipartPostByte(data, last);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
lib/PsychicHttp/src/MultipartProcessor.h
Normal file
42
lib/PsychicHttp/src/MultipartProcessor.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef MULTIPART_PROCESSOR_H
|
||||
#define MULTIPART_PROCESSOR_H
|
||||
|
||||
#include "PsychicCore.h"
|
||||
|
||||
/*
|
||||
* MultipartProcessor - handle parsing and processing a multipart form.
|
||||
* */
|
||||
|
||||
class MultipartProcessor
|
||||
{
|
||||
protected:
|
||||
PsychicRequest* _request;
|
||||
PsychicUploadCallback _uploadCallback;
|
||||
|
||||
String _temp;
|
||||
size_t _parsedLength;
|
||||
uint8_t _multiParseState;
|
||||
String _boundary;
|
||||
uint8_t _boundaryPosition;
|
||||
size_t _itemStartIndex;
|
||||
size_t _itemSize;
|
||||
String _itemName;
|
||||
String _itemFilename;
|
||||
String _itemType;
|
||||
String _itemValue;
|
||||
uint8_t* _itemBuffer;
|
||||
size_t _itemBufferIndex;
|
||||
bool _itemIsFile;
|
||||
|
||||
void _handleUploadByte(uint8_t data, bool last);
|
||||
void _parseMultipartPostByte(uint8_t data, bool last);
|
||||
|
||||
public:
|
||||
MultipartProcessor(PsychicRequest* request, PsychicUploadCallback uploadCallback = nullptr);
|
||||
~MultipartProcessor();
|
||||
|
||||
esp_err_t process();
|
||||
esp_err_t process(const char* body);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -2,21 +2,24 @@
|
||||
#include "PsychicHttpServer.h"
|
||||
#include <lwip/sockets.h>
|
||||
|
||||
PsychicClient::PsychicClient(httpd_handle_t server, int socket) :
|
||||
_server(server),
|
||||
_socket(socket),
|
||||
_friend(NULL),
|
||||
isNew(false)
|
||||
{}
|
||||
|
||||
PsychicClient::~PsychicClient() {
|
||||
PsychicClient::PsychicClient(httpd_handle_t server, int socket) : _server(server),
|
||||
_socket(socket),
|
||||
_friend(NULL),
|
||||
isNew(false)
|
||||
{
|
||||
}
|
||||
|
||||
httpd_handle_t PsychicClient::server() {
|
||||
PsychicClient::~PsychicClient()
|
||||
{
|
||||
}
|
||||
|
||||
httpd_handle_t PsychicClient::server()
|
||||
{
|
||||
return _server;
|
||||
}
|
||||
|
||||
int PsychicClient::socket() {
|
||||
int PsychicClient::socket()
|
||||
{
|
||||
return _socket;
|
||||
}
|
||||
|
||||
@@ -24,49 +27,67 @@ int PsychicClient::socket() {
|
||||
esp_err_t PsychicClient::close()
|
||||
{
|
||||
esp_err_t err = httpd_sess_trigger_close(_server, _socket);
|
||||
//PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list.
|
||||
// PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list.
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
IPAddress PsychicClient::localIP()
|
||||
{
|
||||
IPAddress address(0,0,0,0);
|
||||
IPAddress address(0, 0, 0, 0);
|
||||
|
||||
char ipstr[INET6_ADDRSTRLEN];
|
||||
struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing
|
||||
struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing
|
||||
socklen_t addr_size = sizeof(addr);
|
||||
|
||||
if (getsockname(_socket, (struct sockaddr *)&addr, &addr_size) < 0) {
|
||||
if (getsockname(_socket, (struct sockaddr*)&addr, &addr_size) < 0) {
|
||||
ESP_LOGE(PH_TAG, "Error getting client IP");
|
||||
return address;
|
||||
}
|
||||
|
||||
// Convert to IPv4 string
|
||||
inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr));
|
||||
//ESP_LOGD(PH_TAG, "Client Local IP => %s", ipstr);
|
||||
// ESP_LOGD(PH_TAG, "Client Local IP => %s", ipstr);
|
||||
address.fromString(ipstr);
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
uint16_t PsychicClient::localPort() const
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof addr;
|
||||
getsockname(_socket, (struct sockaddr*)&addr, &len);
|
||||
struct sockaddr_in* s = (struct sockaddr_in*)&addr;
|
||||
return ntohs(s->sin_port);
|
||||
}
|
||||
|
||||
IPAddress PsychicClient::remoteIP()
|
||||
{
|
||||
IPAddress address(0,0,0,0);
|
||||
IPAddress address(0, 0, 0, 0);
|
||||
|
||||
char ipstr[INET6_ADDRSTRLEN];
|
||||
struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing
|
||||
struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing
|
||||
socklen_t addr_size = sizeof(addr);
|
||||
|
||||
if (getpeername(_socket, (struct sockaddr *)&addr, &addr_size) < 0) {
|
||||
if (getpeername(_socket, (struct sockaddr*)&addr, &addr_size) < 0) {
|
||||
ESP_LOGE(PH_TAG, "Error getting client IP");
|
||||
return address;
|
||||
}
|
||||
|
||||
// Convert to IPv4 string
|
||||
inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr));
|
||||
//ESP_LOGD(PH_TAG, "Client Remote IP => %s", ipstr);
|
||||
// ESP_LOGD(PH_TAG, "Client Remote IP => %s", ipstr);
|
||||
address.fromString(ipstr);
|
||||
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t PsychicClient::remotePort() const
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof addr;
|
||||
getpeername(_socket, (struct sockaddr*)&addr, &len);
|
||||
struct sockaddr_in* s = (struct sockaddr_in*)&addr;
|
||||
return ntohs(s->sin_port);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
#include "PsychicCore.h"
|
||||
|
||||
/*
|
||||
* PsychicClient :: Generic wrapper around the ESP-IDF socket
|
||||
*/
|
||||
* PsychicClient :: Generic wrapper around the ESP-IDF socket
|
||||
*/
|
||||
|
||||
class PsychicClient
|
||||
{
|
||||
|
||||
class PsychicClient {
|
||||
protected:
|
||||
httpd_handle_t _server;
|
||||
int _socket;
|
||||
@@ -16,9 +18,9 @@ class PsychicClient {
|
||||
PsychicClient(httpd_handle_t server, int socket);
|
||||
~PsychicClient();
|
||||
|
||||
//no idea if this is the right way to do it or not, but lets see.
|
||||
//pointer to our derived class (eg. PsychicWebSocketConnection)
|
||||
void *_friend;
|
||||
// no idea if this is the right way to do it or not, but lets see.
|
||||
// pointer to our derived class (eg. PsychicWebSocketConnection)
|
||||
void* _friend;
|
||||
|
||||
bool isNew = false;
|
||||
|
||||
@@ -29,7 +31,9 @@ class PsychicClient {
|
||||
esp_err_t close();
|
||||
|
||||
IPAddress localIP();
|
||||
uint16_t localPort() const;
|
||||
IPAddress remoteIP();
|
||||
uint16_t remotePort() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -3,17 +3,8 @@
|
||||
|
||||
#define PH_TAG "psychic"
|
||||
|
||||
//version numbers
|
||||
#define PSYCHIC_HTTP_VERSION_MAJOR 1
|
||||
#define PSYCHIC_HTTP_VERSION_MINOR 1
|
||||
#define PSYCHIC_HTTP_VERSION_PATCH 0
|
||||
|
||||
#ifndef MAX_COOKIE_SIZE
|
||||
#define MAX_COOKIE_SIZE 512
|
||||
#endif
|
||||
|
||||
#ifndef FILE_CHUNK_SIZE
|
||||
#define FILE_CHUNK_SIZE 8*1024
|
||||
#define FILE_CHUNK_SIZE 8 * 1024
|
||||
#endif
|
||||
|
||||
#ifndef STREAM_CHUNK_SIZE
|
||||
@@ -21,87 +12,93 @@
|
||||
#endif
|
||||
|
||||
#ifndef MAX_UPLOAD_SIZE
|
||||
#define MAX_UPLOAD_SIZE (2048*1024) // 2MB
|
||||
#define MAX_UPLOAD_SIZE (2048 * 1024) // 2MB
|
||||
#endif
|
||||
|
||||
#ifndef MAX_REQUEST_BODY_SIZE
|
||||
#define MAX_REQUEST_BODY_SIZE (16*1024) //16K
|
||||
#define MAX_REQUEST_BODY_SIZE (16 * 1024) // 16K
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
#include <esp_http_server.h>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <libb64/cencode.h>
|
||||
#include "esp_random.h"
|
||||
#include "MD5Builder.h"
|
||||
#include <UrlEncode.h>
|
||||
#include "FS.h"
|
||||
#include "MD5Builder.h"
|
||||
#include "esp_random.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <UrlEncode.h>
|
||||
#include <esp_http_server.h>
|
||||
#include <libb64/cencode.h>
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
|
||||
#ifdef PSY_DEVMODE
|
||||
#include "ArduinoTrace.h"
|
||||
#endif
|
||||
|
||||
enum HTTPAuthMethod {
|
||||
BASIC_AUTH,
|
||||
DIGEST_AUTH
|
||||
};
|
||||
|
||||
String urlDecode(const char* encoded);
|
||||
|
||||
class PsychicHttpServer;
|
||||
class PsychicRequest;
|
||||
class PsychicResponse;
|
||||
class PsychicWebSocketRequest;
|
||||
class PsychicClient;
|
||||
|
||||
//filter function definition
|
||||
typedef std::function<bool(PsychicRequest *request)> PsychicRequestFilterFunction;
|
||||
// filter function definition
|
||||
typedef std::function<bool(PsychicRequest* request)> PsychicRequestFilterFunction;
|
||||
|
||||
//client connect callback
|
||||
typedef std::function<void(PsychicClient *client)> PsychicClientCallback;
|
||||
// middleware function definition
|
||||
typedef std::function<esp_err_t()> PsychicMiddlewareNext;
|
||||
typedef std::function<esp_err_t(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next)> PsychicMiddlewareCallback;
|
||||
|
||||
//callback definitions
|
||||
typedef std::function<esp_err_t(PsychicRequest *request)> PsychicHttpRequestCallback;
|
||||
typedef std::function<esp_err_t(PsychicRequest *request, JsonVariant &json)> PsychicJsonRequestCallback;
|
||||
// client connect callback
|
||||
typedef std::function<void(PsychicClient* client)> PsychicClientCallback;
|
||||
|
||||
// callback definitions
|
||||
typedef std::function<esp_err_t(PsychicRequest* request, PsychicResponse* response)> PsychicHttpRequestCallback;
|
||||
typedef std::function<esp_err_t(PsychicRequest* request, PsychicResponse* response, JsonVariant& json)> PsychicJsonRequestCallback;
|
||||
typedef std::function<esp_err_t(PsychicRequest* request, const String& filename, uint64_t index, uint8_t* data, size_t len, bool final)> PsychicUploadCallback;
|
||||
|
||||
struct HTTPHeader {
|
||||
char * field;
|
||||
char * value;
|
||||
String field;
|
||||
String value;
|
||||
};
|
||||
|
||||
class DefaultHeaders {
|
||||
std::list<HTTPHeader> _headers;
|
||||
class DefaultHeaders
|
||||
{
|
||||
std::list<HTTPHeader> _headers;
|
||||
|
||||
public:
|
||||
DefaultHeaders() {}
|
||||
public:
|
||||
DefaultHeaders() {}
|
||||
|
||||
void addHeader(const String& field, const String& value)
|
||||
{
|
||||
addHeader(field.c_str(), value.c_str());
|
||||
}
|
||||
void addHeader(const String& field, const String& value)
|
||||
{
|
||||
_headers.push_back({field, value});
|
||||
}
|
||||
|
||||
void addHeader(const char * field, const char * value)
|
||||
{
|
||||
HTTPHeader header;
|
||||
void addHeader(const char* field, const char* value)
|
||||
{
|
||||
_headers.push_back({field, value});
|
||||
}
|
||||
|
||||
//these are just going to stick around forever.
|
||||
header.field =(char *)malloc(strlen(field)+1);
|
||||
header.value = (char *)malloc(strlen(value)+1);
|
||||
const std::list<HTTPHeader>& getHeaders() { return _headers; }
|
||||
|
||||
strlcpy(header.field, field, strlen(field)+1);
|
||||
strlcpy(header.value, value, strlen(value)+1);
|
||||
// delete the copy constructor, singleton class
|
||||
DefaultHeaders(DefaultHeaders const&) = delete;
|
||||
DefaultHeaders& operator=(DefaultHeaders const&) = delete;
|
||||
|
||||
_headers.push_back(header);
|
||||
}
|
||||
|
||||
const std::list<HTTPHeader>& getHeaders() { return _headers; }
|
||||
|
||||
//delete the copy constructor, singleton class
|
||||
DefaultHeaders(DefaultHeaders const &) = delete;
|
||||
DefaultHeaders &operator=(DefaultHeaders const &) = delete;
|
||||
|
||||
//single static class interface
|
||||
static DefaultHeaders &Instance() {
|
||||
static DefaultHeaders instance;
|
||||
return instance;
|
||||
}
|
||||
// single static class interface
|
||||
static DefaultHeaders& Instance()
|
||||
{
|
||||
static DefaultHeaders instance;
|
||||
return instance;
|
||||
}
|
||||
};
|
||||
|
||||
#endif //PsychicCore_h
|
||||
#endif // PsychicCore_h
|
||||
@@ -1,90 +1,140 @@
|
||||
#include "PsychicEndpoint.h"
|
||||
#include "PsychicHttpServer.h"
|
||||
|
||||
PsychicEndpoint::PsychicEndpoint() :
|
||||
_server(NULL),
|
||||
_uri(""),
|
||||
_method(HTTP_GET),
|
||||
_handler(NULL)
|
||||
PsychicEndpoint::PsychicEndpoint() : _server(NULL),
|
||||
_uri(""),
|
||||
_method(HTTP_GET),
|
||||
_handler(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
PsychicEndpoint::PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri) :
|
||||
_server(server),
|
||||
_uri(uri),
|
||||
_method(method),
|
||||
_handler(NULL)
|
||||
PsychicEndpoint::PsychicEndpoint(PsychicHttpServer* server, int method, const char* uri) : _server(server),
|
||||
_uri(uri),
|
||||
_method(method),
|
||||
_handler(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
PsychicEndpoint * PsychicEndpoint::setHandler(PsychicHandler *handler)
|
||||
PsychicEndpoint* PsychicEndpoint::setHandler(PsychicHandler* handler)
|
||||
{
|
||||
//clean up old / default handler
|
||||
// clean up old / default handler
|
||||
if (_handler != NULL)
|
||||
delete _handler;
|
||||
|
||||
//get our new pointer
|
||||
// get our new pointer
|
||||
_handler = handler;
|
||||
|
||||
//keep a pointer to the server
|
||||
// keep a pointer to the server
|
||||
_handler->_server = _server;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
PsychicHandler * PsychicEndpoint::handler()
|
||||
PsychicHandler* PsychicEndpoint::handler()
|
||||
{
|
||||
return _handler;
|
||||
}
|
||||
|
||||
String PsychicEndpoint::uri() {
|
||||
String PsychicEndpoint::uri()
|
||||
{
|
||||
return _uri;
|
||||
}
|
||||
|
||||
esp_err_t PsychicEndpoint::requestCallback(httpd_req_t *req)
|
||||
esp_err_t PsychicEndpoint::requestCallback(httpd_req_t* req)
|
||||
{
|
||||
#ifdef ENABLE_ASYNC
|
||||
if (is_on_async_worker_thread() == false) {
|
||||
if (submit_async_req(req, PsychicEndpoint::requestCallback) == ESP_OK) {
|
||||
return ESP_OK;
|
||||
} else {
|
||||
httpd_resp_set_status(req, "503 Busy");
|
||||
httpd_resp_sendstr(req, "No workers available. Server busy.</div>");
|
||||
return ESP_OK;
|
||||
}
|
||||
#ifdef ENABLE_ASYNC
|
||||
if (is_on_async_worker_thread() == false) {
|
||||
if (submit_async_req(req, PsychicEndpoint::requestCallback) == ESP_OK) {
|
||||
return ESP_OK;
|
||||
} else {
|
||||
httpd_resp_set_status(req, "503 Busy");
|
||||
httpd_resp_sendstr(req, "No workers available. Server busy.</div>");
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
PsychicEndpoint *self = (PsychicEndpoint *)req->user_ctx;
|
||||
PsychicHandler *handler = self->handler();
|
||||
PsychicEndpoint* self = (PsychicEndpoint*)req->user_ctx;
|
||||
PsychicRequest request(self->_server, req);
|
||||
|
||||
//make sure we have a handler
|
||||
if (handler != NULL)
|
||||
{
|
||||
if (handler->filter(&request) && handler->canHandle(&request))
|
||||
{
|
||||
//check our credentials
|
||||
if (handler->needsAuthentication(&request))
|
||||
return handler->authenticate(&request);
|
||||
esp_err_t err = self->process(&request);
|
||||
|
||||
//pass it to our handler
|
||||
return handler->handleRequest(&request);
|
||||
}
|
||||
//pass it to our generic handlers
|
||||
else
|
||||
return PsychicHttpServer::notFoundHandler(req, HTTPD_500_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
if (err == HTTPD_404_NOT_FOUND)
|
||||
return PsychicHttpServer::requestHandler(req);
|
||||
|
||||
if (err == ESP_ERR_HTTPD_INVALID_REQ)
|
||||
return request.response()->error(HTTPD_500_INTERNAL_SERVER_ERROR, "No handler registered.");
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
bool PsychicEndpoint::matches(const char* uri)
|
||||
{
|
||||
// we only want to match the path, no GET strings
|
||||
char* ptr;
|
||||
size_t position = 0;
|
||||
|
||||
// look for a ? and set our path length to that,
|
||||
ptr = strchr(uri, '?');
|
||||
if (ptr != NULL)
|
||||
position = (size_t)(int)(ptr - uri);
|
||||
// or use the whole uri if not found
|
||||
else
|
||||
return request.reply(500, "text/html", "No handler registered.");
|
||||
position = strlen(uri);
|
||||
|
||||
// do we have a per-endpoint match function
|
||||
if (this->getURIMatchFunction() != NULL) {
|
||||
// ESP_LOGD(PH_TAG, "Match? %s == %s (%d)", _uri.c_str(), uri, position);
|
||||
return this->getURIMatchFunction()(_uri.c_str(), uri, (size_t)position);
|
||||
}
|
||||
// do we have a global match function
|
||||
if (_server->getURIMatchFunction() != NULL) {
|
||||
// ESP_LOGD(PH_TAG, "Match? %s == %s (%d)", _uri.c_str(), uri, position);
|
||||
return _server->getURIMatchFunction()(_uri.c_str(), uri, (size_t)position);
|
||||
} else {
|
||||
ESP_LOGE(PH_TAG, "No uri matching function set");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
PsychicEndpoint* PsychicEndpoint::setFilter(PsychicRequestFilterFunction fn) {
|
||||
_handler->setFilter(fn);
|
||||
httpd_uri_match_func_t PsychicEndpoint::getURIMatchFunction()
|
||||
{
|
||||
return _uri_match_fn;
|
||||
}
|
||||
|
||||
void PsychicEndpoint::setURIMatchFunction(httpd_uri_match_func_t match_fn)
|
||||
{
|
||||
_uri_match_fn = match_fn;
|
||||
}
|
||||
|
||||
PsychicEndpoint* PsychicEndpoint::addFilter(PsychicRequestFilterFunction fn)
|
||||
{
|
||||
_handler->addFilter(fn);
|
||||
return this;
|
||||
}
|
||||
|
||||
PsychicEndpoint* PsychicEndpoint::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) {
|
||||
_handler->setAuthentication(username, password, method, realm, authFailMsg);
|
||||
PsychicEndpoint* PsychicEndpoint::addMiddleware(PsychicMiddleware* middleware)
|
||||
{
|
||||
_handler->addMiddleware(middleware);
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
PsychicEndpoint* PsychicEndpoint::addMiddleware(PsychicMiddlewareCallback fn)
|
||||
{
|
||||
_handler->addMiddleware(fn);
|
||||
return this;
|
||||
}
|
||||
|
||||
void PsychicEndpoint::removeMiddleware(PsychicMiddleware* middleware)
|
||||
{
|
||||
_handler->removeMiddleware(middleware);
|
||||
}
|
||||
|
||||
esp_err_t PsychicEndpoint::process(PsychicRequest* request)
|
||||
{
|
||||
esp_err_t ret = ESP_ERR_HTTPD_INVALID_REQ;
|
||||
if (_handler != NULL)
|
||||
ret = _handler->process(request);
|
||||
ESP_LOGD(PH_TAG, "Endpoint %s processed %s: %s", _uri.c_str(), request->uri().c_str(), esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "PsychicCore.h"
|
||||
|
||||
class PsychicHandler;
|
||||
class PsychicMiddleware;
|
||||
|
||||
#ifdef ENABLE_ASYNC
|
||||
#include "async_worker.h"
|
||||
@@ -11,27 +12,39 @@ class PsychicHandler;
|
||||
|
||||
class PsychicEndpoint
|
||||
{
|
||||
friend PsychicHttpServer;
|
||||
friend PsychicHttpServer;
|
||||
|
||||
private:
|
||||
PsychicHttpServer *_server;
|
||||
PsychicHttpServer* _server;
|
||||
String _uri;
|
||||
http_method _method;
|
||||
PsychicHandler *_handler;
|
||||
int _method;
|
||||
PsychicHandler* _handler;
|
||||
httpd_uri_match_func_t _uri_match_fn = nullptr; // use this change the endpoint matching function.
|
||||
|
||||
public:
|
||||
PsychicEndpoint();
|
||||
PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri);
|
||||
PsychicEndpoint(PsychicHttpServer* server, int method, const char* uri);
|
||||
|
||||
PsychicEndpoint *setHandler(PsychicHandler *handler);
|
||||
PsychicHandler *handler();
|
||||
PsychicEndpoint* setHandler(PsychicHandler* handler);
|
||||
PsychicHandler* handler();
|
||||
|
||||
PsychicEndpoint* setFilter(PsychicRequestFilterFunction fn);
|
||||
PsychicEndpoint* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = "");
|
||||
httpd_uri_match_func_t getURIMatchFunction();
|
||||
void setURIMatchFunction(httpd_uri_match_func_t match_fn);
|
||||
|
||||
bool matches(const char* uri);
|
||||
|
||||
// called to process this endpoint with its middleware chain
|
||||
esp_err_t process(PsychicRequest* request);
|
||||
|
||||
PsychicEndpoint* addFilter(PsychicRequestFilterFunction fn);
|
||||
|
||||
PsychicEndpoint* addMiddleware(PsychicMiddleware* middleware);
|
||||
PsychicEndpoint* addMiddleware(PsychicMiddlewareCallback fn);
|
||||
void removeMiddleware(PsychicMiddleware* middleware);
|
||||
|
||||
String uri();
|
||||
|
||||
static esp_err_t requestCallback(httpd_req_t *req);
|
||||
static esp_err_t requestCallback(httpd_req_t* req);
|
||||
};
|
||||
|
||||
#endif // PsychicEndpoint_h
|
||||
@@ -19,83 +19,88 @@
|
||||
*/
|
||||
|
||||
#include "PsychicEventSource.h"
|
||||
#include <string.h>
|
||||
|
||||
/*****************************************/
|
||||
// PsychicEventSource - Handler
|
||||
/*****************************************/
|
||||
|
||||
PsychicEventSource::PsychicEventSource() :
|
||||
PsychicHandler(),
|
||||
_onOpen(NULL),
|
||||
_onClose(NULL)
|
||||
{}
|
||||
|
||||
PsychicEventSource::~PsychicEventSource() {
|
||||
PsychicEventSource::PsychicEventSource() : PsychicHandler(),
|
||||
_onOpen(NULL),
|
||||
_onClose(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
PsychicEventSourceClient * PsychicEventSource::getClient(int socket)
|
||||
PsychicEventSource::~PsychicEventSource()
|
||||
{
|
||||
PsychicClient *client = PsychicHandler::getClient(socket);
|
||||
}
|
||||
|
||||
PsychicEventSourceClient* PsychicEventSource::getClient(int socket)
|
||||
{
|
||||
PsychicClient* client = PsychicHandler::getClient(socket);
|
||||
|
||||
if (client == NULL)
|
||||
return NULL;
|
||||
|
||||
return (PsychicEventSourceClient *)client->_friend;
|
||||
return (PsychicEventSourceClient*)client->_friend;
|
||||
}
|
||||
|
||||
PsychicEventSourceClient * PsychicEventSource::getClient(PsychicClient *client) {
|
||||
PsychicEventSourceClient* PsychicEventSource::getClient(PsychicClient* client)
|
||||
{
|
||||
return getClient(client->socket());
|
||||
}
|
||||
|
||||
esp_err_t PsychicEventSource::handleRequest(PsychicRequest *request)
|
||||
esp_err_t PsychicEventSource::handleRequest(PsychicRequest* request, PsychicResponse* resp)
|
||||
{
|
||||
//start our open ended HTTP response
|
||||
PsychicEventSourceResponse response(request);
|
||||
// start our open ended HTTP response
|
||||
PsychicEventSourceResponse response(resp);
|
||||
esp_err_t err = response.send();
|
||||
|
||||
//lookup our client
|
||||
PsychicClient *client = checkForNewClient(request->client());
|
||||
if (client->isNew)
|
||||
{
|
||||
//did we get our last id?
|
||||
if(request->hasHeader("Last-Event-ID"))
|
||||
{
|
||||
PsychicEventSourceClient *buddy = getClient(client);
|
||||
// lookup our client
|
||||
PsychicClient* client = checkForNewClient(request->client());
|
||||
if (client->isNew) {
|
||||
// did we get our last id?
|
||||
if (request->hasHeader("Last-Event-ID")) {
|
||||
PsychicEventSourceClient* buddy = getClient(client);
|
||||
buddy->_lastId = atoi(request->header("Last-Event-ID").c_str());
|
||||
}
|
||||
|
||||
//let our handler know.
|
||||
// let our handler know.
|
||||
openCallback(client);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
PsychicEventSource * PsychicEventSource::onOpen(PsychicEventSourceClientCallback fn) {
|
||||
PsychicEventSource* PsychicEventSource::onOpen(PsychicEventSourceClientCallback fn)
|
||||
{
|
||||
_onOpen = fn;
|
||||
return this;
|
||||
}
|
||||
|
||||
PsychicEventSource * PsychicEventSource::onClose(PsychicEventSourceClientCallback fn) {
|
||||
PsychicEventSource* PsychicEventSource::onClose(PsychicEventSourceClientCallback fn)
|
||||
{
|
||||
_onClose = fn;
|
||||
return this;
|
||||
}
|
||||
|
||||
void PsychicEventSource::addClient(PsychicClient *client) {
|
||||
void PsychicEventSource::addClient(PsychicClient* client)
|
||||
{
|
||||
client->_friend = new PsychicEventSourceClient(client);
|
||||
PsychicHandler::addClient(client);
|
||||
}
|
||||
|
||||
void PsychicEventSource::removeClient(PsychicClient *client) {
|
||||
void PsychicEventSource::removeClient(PsychicClient* client)
|
||||
{
|
||||
PsychicHandler::removeClient(client);
|
||||
delete (PsychicEventSourceClient*)client->_friend;
|
||||
client->_friend = NULL;
|
||||
}
|
||||
|
||||
void PsychicEventSource::openCallback(PsychicClient *client) {
|
||||
PsychicEventSourceClient *buddy = getClient(client);
|
||||
if (buddy == NULL)
|
||||
{
|
||||
void PsychicEventSource::openCallback(PsychicClient* client)
|
||||
{
|
||||
PsychicEventSourceClient* buddy = getClient(client);
|
||||
if (buddy == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,10 +108,10 @@ void PsychicEventSource::openCallback(PsychicClient *client) {
|
||||
_onOpen(buddy);
|
||||
}
|
||||
|
||||
void PsychicEventSource::closeCallback(PsychicClient *client) {
|
||||
PsychicEventSourceClient *buddy = getClient(client);
|
||||
if (buddy == NULL)
|
||||
{
|
||||
void PsychicEventSource::closeCallback(PsychicClient* client)
|
||||
{
|
||||
PsychicEventSourceClient* buddy = getClient(client);
|
||||
if (buddy == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,11 +119,13 @@ void PsychicEventSource::closeCallback(PsychicClient *client) {
|
||||
_onClose(getClient(buddy));
|
||||
}
|
||||
|
||||
void PsychicEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect)
|
||||
void PsychicEventSource::send(const char* message, const char* event, uint32_t id, uint32_t reconnect)
|
||||
{
|
||||
String ev = generateEventMessage(message, event, id, reconnect);
|
||||
for(PsychicClient *c : _clients) {
|
||||
((PsychicEventSourceClient*)c->_friend)->sendEvent(ev.c_str());
|
||||
if (c && c->_friend) {
|
||||
((PsychicEventSourceClient*)c->_friend)->sendEvent(ev.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,58 +133,135 @@ void PsychicEventSource::send(const char *message, const char *event, uint32_t i
|
||||
// PsychicEventSourceClient
|
||||
/*****************************************/
|
||||
|
||||
PsychicEventSourceClient::PsychicEventSourceClient(PsychicClient *client) :
|
||||
PsychicClient(client->server(), client->socket()),
|
||||
_lastId(0)
|
||||
PsychicEventSourceClient::PsychicEventSourceClient(PsychicClient* client) : PsychicClient(client->server(), client->socket()),
|
||||
_lastId(0)
|
||||
{
|
||||
}
|
||||
|
||||
PsychicEventSourceClient::~PsychicEventSourceClient(){
|
||||
PsychicEventSourceClient::~PsychicEventSourceClient()
|
||||
{
|
||||
}
|
||||
|
||||
void PsychicEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){
|
||||
void PsychicEventSourceClient::send(const char* message, const char* event, uint32_t id, uint32_t reconnect)
|
||||
{
|
||||
String ev = generateEventMessage(message, event, id, reconnect);
|
||||
sendEvent(ev.c_str());
|
||||
}
|
||||
|
||||
void PsychicEventSourceClient::sendEvent(const char *event) {
|
||||
int result;
|
||||
do {
|
||||
result = httpd_socket_send(this->server(), this->socket(), event, strlen(event), 0);
|
||||
} while (result == HTTPD_SOCK_ERR_TIMEOUT);
|
||||
void PsychicEventSourceClient::sendEvent(const char* event)
|
||||
{
|
||||
_sendEventAsync(this->server(), this->socket(), event, strlen(event));
|
||||
}
|
||||
|
||||
//if (result < 0)
|
||||
//error log here
|
||||
esp_err_t PsychicEventSourceClient::_sendEventAsync(httpd_handle_t handle, int socket, const char* event, size_t len)
|
||||
{
|
||||
// create the transfer object
|
||||
async_event_transfer_t* transfer = (async_event_transfer_t*)calloc(1, sizeof(async_event_transfer_t));
|
||||
if (transfer == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// populate it
|
||||
transfer->arg = this;
|
||||
transfer->callback = _sendEventSentCallback;
|
||||
transfer->handle = handle;
|
||||
transfer->socket = socket;
|
||||
transfer->len = len;
|
||||
|
||||
// allocate for event text
|
||||
transfer->event = (char*)malloc(len);
|
||||
if (transfer->event == NULL) {
|
||||
free(transfer);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// copy over the event data
|
||||
memcpy(transfer->event, event, len);
|
||||
|
||||
// queue it.
|
||||
esp_err_t err = httpd_queue_work(handle, _sendEventWorkCallback, transfer);
|
||||
|
||||
// cleanup
|
||||
if (err) {
|
||||
free(transfer->event);
|
||||
free(transfer);
|
||||
return err;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void PsychicEventSourceClient::_sendEventWorkCallback(void* arg)
|
||||
{
|
||||
async_event_transfer_t* trans = (async_event_transfer_t*)arg;
|
||||
|
||||
// omg the error is overloaded with the number of bytes sent!
|
||||
esp_err_t err = httpd_socket_send(trans->handle, trans->socket, trans->event, trans->len, 0);
|
||||
if (err == trans->len)
|
||||
err = ESP_OK;
|
||||
|
||||
if (trans->callback)
|
||||
trans->callback(err, trans->socket, trans->arg);
|
||||
|
||||
// free our memory
|
||||
free(trans->event);
|
||||
free(trans);
|
||||
}
|
||||
|
||||
void PsychicEventSourceClient::_sendEventSentCallback(esp_err_t err, int socket, void* arg)
|
||||
{
|
||||
// PsychicEventSourceClient* client = (PsychicEventSourceClient*)arg;
|
||||
|
||||
if (err == ESP_OK)
|
||||
return;
|
||||
else if (err == ESP_FAIL)
|
||||
ESP_LOGE(PH_TAG, "EventSource: send - socket error (#%d)", socket);
|
||||
else if (err == ESP_ERR_INVALID_STATE)
|
||||
ESP_LOGE(PH_TAG, "EventSource: Handshake was already done beforehand (#%d)", socket);
|
||||
else if (err == ESP_ERR_INVALID_ARG)
|
||||
ESP_LOGE(PH_TAG, "EventSource: Argument is invalid (#%d)", socket);
|
||||
else if (err == HTTPD_SOCK_ERR_TIMEOUT)
|
||||
ESP_LOGE(PH_TAG, "EventSource: Socket timeout (#%d)", socket);
|
||||
else if (err == HTTPD_SOCK_ERR_INVALID)
|
||||
ESP_LOGE(PH_TAG, "EventSource: Invalid socket (#%d)", socket);
|
||||
else if (err == HTTPD_SOCK_ERR_FAIL)
|
||||
ESP_LOGE(PH_TAG, "EventSource: Socket fail (#%d)", socket);
|
||||
else
|
||||
ESP_LOGE(PH_TAG, "EventSource: %#06x %s (#%d)", (int)err, esp_err_to_name(err), socket);
|
||||
}
|
||||
|
||||
/*****************************************/
|
||||
// PsychicEventSourceResponse
|
||||
/*****************************************/
|
||||
|
||||
PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicRequest *request)
|
||||
: PsychicResponse(request)
|
||||
PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicResponse* response) : PsychicResponseDelegate(response)
|
||||
{
|
||||
}
|
||||
|
||||
esp_err_t PsychicEventSourceResponse::send() {
|
||||
esp_err_t PsychicEventSourceResponse::send()
|
||||
{
|
||||
_response->addHeader("Content-Type", "text/event-stream");
|
||||
_response->addHeader("Cache-Control", "no-cache");
|
||||
_response->addHeader("Connection", "keep-alive");
|
||||
|
||||
//build our main header
|
||||
// build our main header
|
||||
String out = String();
|
||||
out.concat("HTTP/1.1 200 OK\r\n");
|
||||
out.concat("Content-Type: text/event-stream\r\n");
|
||||
out.concat("Cache-Control: no-cache\r\n");
|
||||
out.concat("Connection: keep-alive\r\n");
|
||||
|
||||
//get our global headers out of the way first
|
||||
for (HTTPHeader header : DefaultHeaders::Instance().getHeaders())
|
||||
out.concat(String(header.field) + ": " + String(header.value) + "\r\n");
|
||||
// get our global headers out of the way first
|
||||
for (auto& header : DefaultHeaders::Instance().getHeaders())
|
||||
out.concat(header.field + ": " + header.value + "\r\n");
|
||||
|
||||
//separator
|
||||
// now do our individual headers
|
||||
for (auto& header : _response->headers())
|
||||
out.concat(header.field + ": " + header.value + "\r\n");
|
||||
|
||||
// separator
|
||||
out.concat("\r\n");
|
||||
|
||||
int result;
|
||||
do {
|
||||
result = httpd_send(_request->request(), out.c_str(), out.length());
|
||||
result = httpd_send(request(), out.c_str(), out.length());
|
||||
} while (result == HTTPD_SOCK_ERR_TIMEOUT);
|
||||
|
||||
if (result < 0)
|
||||
@@ -193,28 +277,29 @@ esp_err_t PsychicEventSourceResponse::send() {
|
||||
// Event Message Generator
|
||||
/*****************************************/
|
||||
|
||||
String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
|
||||
String generateEventMessage(const char* message, const char* event, uint32_t id, uint32_t reconnect)
|
||||
{
|
||||
String ev = "";
|
||||
|
||||
if(reconnect){
|
||||
if (reconnect) {
|
||||
ev += "retry: ";
|
||||
ev += String(reconnect);
|
||||
ev += "\r\n";
|
||||
}
|
||||
|
||||
if(id){
|
||||
if (id) {
|
||||
ev += "id: ";
|
||||
ev += String(id);
|
||||
ev += "\r\n";
|
||||
}
|
||||
|
||||
if(event != NULL){
|
||||
if (event != NULL) {
|
||||
ev += "event: ";
|
||||
ev += String(event);
|
||||
ev += "\r\n";
|
||||
}
|
||||
|
||||
if(message != NULL){
|
||||
if (message != NULL) {
|
||||
ev += "data: ";
|
||||
ev += String(message);
|
||||
ev += "\r\n";
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
#ifndef PsychicEventSource_H_
|
||||
#define PsychicEventSource_H_
|
||||
|
||||
#include "PsychicClient.h"
|
||||
#include "PsychicCore.h"
|
||||
#include "PsychicHandler.h"
|
||||
#include "PsychicClient.h"
|
||||
#include "PsychicResponse.h"
|
||||
|
||||
class PsychicEventSource;
|
||||
@@ -30,24 +30,38 @@ class PsychicEventSourceResponse;
|
||||
class PsychicEventSourceClient;
|
||||
class PsychicResponse;
|
||||
|
||||
typedef std::function<void(PsychicEventSourceClient *client)> PsychicEventSourceClientCallback;
|
||||
typedef std::function<void(PsychicEventSourceClient* client)> PsychicEventSourceClientCallback;
|
||||
|
||||
class PsychicEventSourceClient : public PsychicClient {
|
||||
friend PsychicEventSource;
|
||||
typedef struct {
|
||||
httpd_handle_t handle;
|
||||
int socket;
|
||||
char* event;
|
||||
size_t len;
|
||||
transfer_complete_cb callback;
|
||||
void* arg;
|
||||
} async_event_transfer_t;
|
||||
|
||||
class PsychicEventSourceClient : public PsychicClient
|
||||
{
|
||||
friend PsychicEventSource;
|
||||
|
||||
protected:
|
||||
uint32_t _lastId;
|
||||
esp_err_t _sendEventAsync(httpd_handle_t handle, int socket, const char* event, size_t len);
|
||||
static void _sendEventWorkCallback(void* arg);
|
||||
static void _sendEventSentCallback(esp_err_t err, int socket, void* arg);
|
||||
|
||||
public:
|
||||
PsychicEventSourceClient(PsychicClient *client);
|
||||
PsychicEventSourceClient(PsychicClient* client);
|
||||
~PsychicEventSourceClient();
|
||||
|
||||
uint32_t lastId() const { return _lastId; }
|
||||
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
||||
void sendEvent(const char *event);
|
||||
void send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
|
||||
void sendEvent(const char* event);
|
||||
};
|
||||
|
||||
class PsychicEventSource : public PsychicHandler {
|
||||
class PsychicEventSource : public PsychicHandler
|
||||
{
|
||||
private:
|
||||
PsychicEventSourceClientCallback _onOpen;
|
||||
PsychicEventSourceClientCallback _onClose;
|
||||
@@ -56,27 +70,28 @@ class PsychicEventSource : public PsychicHandler {
|
||||
PsychicEventSource();
|
||||
~PsychicEventSource();
|
||||
|
||||
PsychicEventSourceClient * getClient(int socket) override;
|
||||
PsychicEventSourceClient * getClient(PsychicClient *client) override;
|
||||
void addClient(PsychicClient *client) override;
|
||||
void removeClient(PsychicClient *client) override;
|
||||
void openCallback(PsychicClient *client) override;
|
||||
void closeCallback(PsychicClient *client) override;
|
||||
PsychicEventSourceClient* getClient(int socket) override;
|
||||
PsychicEventSourceClient* getClient(PsychicClient* client) override;
|
||||
void addClient(PsychicClient* client) override;
|
||||
void removeClient(PsychicClient* client) override;
|
||||
void openCallback(PsychicClient* client) override;
|
||||
void closeCallback(PsychicClient* client) override;
|
||||
|
||||
PsychicEventSource *onOpen(PsychicEventSourceClientCallback fn);
|
||||
PsychicEventSource *onClose(PsychicEventSourceClientCallback fn);
|
||||
PsychicEventSource* onOpen(PsychicEventSourceClientCallback fn);
|
||||
PsychicEventSource* onClose(PsychicEventSourceClientCallback fn);
|
||||
|
||||
esp_err_t handleRequest(PsychicRequest *request) override final;
|
||||
esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override final;
|
||||
|
||||
void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
|
||||
void send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
|
||||
};
|
||||
|
||||
class PsychicEventSourceResponse: public PsychicResponse {
|
||||
class PsychicEventSourceResponse : public PsychicResponseDelegate
|
||||
{
|
||||
public:
|
||||
PsychicEventSourceResponse(PsychicRequest *request);
|
||||
virtual esp_err_t send() override;
|
||||
PsychicEventSourceResponse(PsychicResponse* response);
|
||||
esp_err_t send();
|
||||
};
|
||||
|
||||
String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect);
|
||||
String generateEventMessage(const char* message, const char* event, uint32_t id, uint32_t reconnect);
|
||||
|
||||
#endif /* PsychicEventSource_H_ */
|
||||
@@ -1,97 +1,116 @@
|
||||
#include "PsychicFileResponse.h"
|
||||
#include "PsychicResponse.h"
|
||||
#include "PsychicRequest.h"
|
||||
#include "PsychicResponse.h"
|
||||
|
||||
|
||||
PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path, const String& contentType, bool download)
|
||||
: PsychicResponse(request) {
|
||||
PsychicFileResponse::PsychicFileResponse(PsychicResponse* response, FS& fs, const String& path, const String& contentType, bool download) : PsychicResponseDelegate(response)
|
||||
{
|
||||
//_code = 200;
|
||||
String _path(path);
|
||||
|
||||
if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){
|
||||
_path = _path+".gz";
|
||||
if (!download && !fs.exists(_path) && fs.exists(_path + ".gz")) {
|
||||
_path = _path + ".gz";
|
||||
addHeader("Content-Encoding", "gzip");
|
||||
}
|
||||
|
||||
_content = fs.open(_path, "r");
|
||||
_contentLength = _content.size();
|
||||
setContentLength(_content.size());
|
||||
|
||||
if(contentType == "")
|
||||
_setContentType(path);
|
||||
if (contentType == "")
|
||||
_setContentTypeFromPath(path);
|
||||
else
|
||||
setContentType(contentType.c_str());
|
||||
|
||||
int filenameStart = path.lastIndexOf('/') + 1;
|
||||
char buf[26+path.length()-filenameStart];
|
||||
char buf[26 + path.length() - filenameStart];
|
||||
char* filename = (char*)path.c_str() + filenameStart;
|
||||
|
||||
if(download) {
|
||||
if (download) {
|
||||
// set filename and force download
|
||||
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
|
||||
snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", filename);
|
||||
} else {
|
||||
// set filename and force rendering
|
||||
snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
|
||||
snprintf(buf, sizeof(buf), "inline; filename=\"%s\"", filename);
|
||||
}
|
||||
addHeader("Content-Disposition", buf);
|
||||
}
|
||||
|
||||
PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, File content, const String& path, const String& contentType, bool download)
|
||||
: PsychicResponse(request) {
|
||||
PsychicFileResponse::PsychicFileResponse(PsychicResponse* response, File content, const String& path, const String& contentType, bool download) : PsychicResponseDelegate(response)
|
||||
{
|
||||
String _path(path);
|
||||
|
||||
if(!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")){
|
||||
if (!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")) {
|
||||
addHeader("Content-Encoding", "gzip");
|
||||
}
|
||||
|
||||
_content = content;
|
||||
_contentLength = _content.size();
|
||||
setContentLength(_content.size());
|
||||
|
||||
if(contentType == "")
|
||||
_setContentType(path);
|
||||
if (contentType == "")
|
||||
_setContentTypeFromPath(path);
|
||||
else
|
||||
setContentType(contentType.c_str());
|
||||
|
||||
int filenameStart = path.lastIndexOf('/') + 1;
|
||||
char buf[26+path.length()-filenameStart];
|
||||
char buf[26 + path.length() - filenameStart];
|
||||
char* filename = (char*)path.c_str() + filenameStart;
|
||||
|
||||
if(download) {
|
||||
snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename);
|
||||
if (download) {
|
||||
snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", filename);
|
||||
} else {
|
||||
snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename);
|
||||
snprintf(buf, sizeof(buf), "inline; filename=\"%s\"", filename);
|
||||
}
|
||||
addHeader("Content-Disposition", buf);
|
||||
}
|
||||
|
||||
PsychicFileResponse::~PsychicFileResponse()
|
||||
{
|
||||
if(_content)
|
||||
if (_content)
|
||||
_content.close();
|
||||
}
|
||||
|
||||
void PsychicFileResponse::_setContentType(const String& path){
|
||||
const char *_contentType;
|
||||
|
||||
if (path.endsWith(".html")) _contentType = "text/html";
|
||||
else if (path.endsWith(".htm")) _contentType = "text/html";
|
||||
else if (path.endsWith(".css")) _contentType = "text/css";
|
||||
else if (path.endsWith(".json")) _contentType = "application/json";
|
||||
else if (path.endsWith(".js")) _contentType = "application/javascript";
|
||||
else if (path.endsWith(".png")) _contentType = "image/png";
|
||||
else if (path.endsWith(".gif")) _contentType = "image/gif";
|
||||
else if (path.endsWith(".jpg")) _contentType = "image/jpeg";
|
||||
else if (path.endsWith(".ico")) _contentType = "image/x-icon";
|
||||
else if (path.endsWith(".svg")) _contentType = "image/svg+xml";
|
||||
else if (path.endsWith(".eot")) _contentType = "font/eot";
|
||||
else if (path.endsWith(".woff")) _contentType = "font/woff";
|
||||
else if (path.endsWith(".woff2")) _contentType = "font/woff2";
|
||||
else if (path.endsWith(".ttf")) _contentType = "font/ttf";
|
||||
else if (path.endsWith(".xml")) _contentType = "text/xml";
|
||||
else if (path.endsWith(".pdf")) _contentType = "application/pdf";
|
||||
else if (path.endsWith(".zip")) _contentType = "application/zip";
|
||||
else if(path.endsWith(".gz")) _contentType = "application/x-gzip";
|
||||
else _contentType = "text/plain";
|
||||
|
||||
void PsychicFileResponse::_setContentTypeFromPath(const String& path)
|
||||
{
|
||||
const char* _contentType;
|
||||
|
||||
if (path.endsWith(".html"))
|
||||
_contentType = "text/html";
|
||||
else if (path.endsWith(".htm"))
|
||||
_contentType = "text/html";
|
||||
else if (path.endsWith(".css"))
|
||||
_contentType = "text/css";
|
||||
else if (path.endsWith(".json"))
|
||||
_contentType = "application/json";
|
||||
else if (path.endsWith(".js"))
|
||||
_contentType = "application/javascript";
|
||||
else if (path.endsWith(".png"))
|
||||
_contentType = "image/png";
|
||||
else if (path.endsWith(".gif"))
|
||||
_contentType = "image/gif";
|
||||
else if (path.endsWith(".jpg"))
|
||||
_contentType = "image/jpeg";
|
||||
else if (path.endsWith(".ico"))
|
||||
_contentType = "image/x-icon";
|
||||
else if (path.endsWith(".svg"))
|
||||
_contentType = "image/svg+xml";
|
||||
else if (path.endsWith(".eot"))
|
||||
_contentType = "font/eot";
|
||||
else if (path.endsWith(".woff"))
|
||||
_contentType = "font/woff";
|
||||
else if (path.endsWith(".woff2"))
|
||||
_contentType = "font/woff2";
|
||||
else if (path.endsWith(".ttf"))
|
||||
_contentType = "font/ttf";
|
||||
else if (path.endsWith(".xml"))
|
||||
_contentType = "text/xml";
|
||||
else if (path.endsWith(".pdf"))
|
||||
_contentType = "application/pdf";
|
||||
else if (path.endsWith(".zip"))
|
||||
_contentType = "application/zip";
|
||||
else if (path.endsWith(".gz"))
|
||||
_contentType = "application/x-gzip";
|
||||
else
|
||||
_contentType = "text/plain";
|
||||
|
||||
setContentType(_contentType);
|
||||
}
|
||||
|
||||
@@ -99,59 +118,52 @@ esp_err_t PsychicFileResponse::send()
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
|
||||
//just send small files directly
|
||||
// just send small files directly
|
||||
size_t size = getContentLength();
|
||||
if (size < FILE_CHUNK_SIZE)
|
||||
{
|
||||
uint8_t *buffer = (uint8_t *)malloc(size);
|
||||
if (buffer == NULL)
|
||||
{
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
|
||||
if (size < FILE_CHUNK_SIZE) {
|
||||
uint8_t* buffer = (uint8_t*)malloc(size);
|
||||
if (buffer == NULL) {
|
||||
ESP_LOGE(PH_TAG, "Unable to allocate %" PRIu32 " bytes to send chunk", size);
|
||||
httpd_resp_send_err(request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
size_t readSize = _content.readBytes((char *)buffer, size);
|
||||
size_t readSize = _content.readBytes((char*)buffer, size);
|
||||
|
||||
setContent(buffer, readSize);
|
||||
err = _response->send();
|
||||
|
||||
this->setContent(buffer, readSize);
|
||||
err = PsychicResponse::send();
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
/* Retrieve the pointer to scratch buffer for temporary storage */
|
||||
char *chunk = (char *)malloc(FILE_CHUNK_SIZE);
|
||||
if (chunk == NULL)
|
||||
{
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
|
||||
char* chunk = (char*)malloc(FILE_CHUNK_SIZE);
|
||||
if (chunk == NULL) {
|
||||
ESP_LOGE(PH_TAG, "Unable to allocate %" PRIu32 " bytes to send chunk", FILE_CHUNK_SIZE);
|
||||
httpd_resp_send_err(request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
this->sendHeaders();
|
||||
sendHeaders();
|
||||
|
||||
size_t chunksize;
|
||||
do {
|
||||
/* Read file in chunks into the scratch buffer */
|
||||
chunksize = _content.readBytes(chunk, FILE_CHUNK_SIZE);
|
||||
if (chunksize > 0)
|
||||
{
|
||||
err = this->sendChunk((uint8_t *)chunk, chunksize);
|
||||
if (err != ESP_OK)
|
||||
break;
|
||||
}
|
||||
/* Read file in chunks into the scratch buffer */
|
||||
chunksize = _content.readBytes(chunk, FILE_CHUNK_SIZE);
|
||||
if (chunksize > 0) {
|
||||
err = sendChunk((uint8_t*)chunk, chunksize);
|
||||
if (err != ESP_OK)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Keep looping till the whole file is sent */
|
||||
/* Keep looping till the whole file is sent */
|
||||
} while (chunksize != 0);
|
||||
|
||||
//keep track of our memory
|
||||
// keep track of our memory
|
||||
free(chunk);
|
||||
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGD(PH_TAG, "File sending complete");
|
||||
this->finishChunking();
|
||||
finishChunking();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,16 +6,18 @@
|
||||
|
||||
class PsychicRequest;
|
||||
|
||||
class PsychicFileResponse: public PsychicResponse
|
||||
class PsychicFileResponse : public PsychicResponseDelegate
|
||||
{
|
||||
using File = fs::File;
|
||||
using FS = fs::FS;
|
||||
private:
|
||||
using File = fs::File;
|
||||
using FS = fs::FS;
|
||||
|
||||
protected:
|
||||
File _content;
|
||||
void _setContentType(const String& path);
|
||||
void _setContentTypeFromPath(const String& path);
|
||||
|
||||
public:
|
||||
PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path, const String& contentType=String(), bool download=false);
|
||||
PsychicFileResponse(PsychicRequest *request, File content, const String& path, const String& contentType=String(), bool download=false);
|
||||
PsychicFileResponse(PsychicResponse* response, FS& fs, const String& path, const String& contentType = String(), bool download = false);
|
||||
PsychicFileResponse(PsychicResponse* response, File content, const String& path, const String& contentType = String(), bool download = false);
|
||||
~PsychicFileResponse();
|
||||
esp_err_t send();
|
||||
};
|
||||
|
||||
@@ -1,111 +1,148 @@
|
||||
#include "PsychicHandler.h"
|
||||
|
||||
PsychicHandler::PsychicHandler() :
|
||||
_filter(NULL),
|
||||
_server(NULL),
|
||||
_username(""),
|
||||
_password(""),
|
||||
_method(DIGEST_AUTH),
|
||||
_realm(""),
|
||||
_authFailMsg(""),
|
||||
_subprotocol("")
|
||||
{}
|
||||
PsychicHandler::PsychicHandler()
|
||||
{
|
||||
}
|
||||
|
||||
PsychicHandler::~PsychicHandler() {
|
||||
PsychicHandler::~PsychicHandler()
|
||||
{
|
||||
delete _chain;
|
||||
// actual PsychicClient deletion handled by PsychicServer
|
||||
// for (PsychicClient *client : _clients)
|
||||
// delete(client);
|
||||
_clients.clear();
|
||||
}
|
||||
|
||||
PsychicHandler* PsychicHandler::setFilter(PsychicRequestFilterFunction fn) {
|
||||
_filter = fn;
|
||||
return this;
|
||||
}
|
||||
|
||||
bool PsychicHandler::filter(PsychicRequest *request){
|
||||
return _filter == NULL || _filter(request);
|
||||
}
|
||||
|
||||
void PsychicHandler::setSubprotocol(const String& subprotocol) {
|
||||
this->_subprotocol = subprotocol;
|
||||
}
|
||||
const char* PsychicHandler::getSubprotocol() const {
|
||||
return _subprotocol.c_str();
|
||||
}
|
||||
|
||||
PsychicHandler* PsychicHandler::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) {
|
||||
_username = String(username);
|
||||
_password = String(password);
|
||||
_method = method;
|
||||
_realm = String(realm);
|
||||
_authFailMsg = String(authFailMsg);
|
||||
return this;
|
||||
};
|
||||
|
||||
bool PsychicHandler::needsAuthentication(PsychicRequest *request) {
|
||||
return (_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str());
|
||||
}
|
||||
|
||||
esp_err_t PsychicHandler::authenticate(PsychicRequest *request) {
|
||||
return request->requestAuthentication(_method, _realm.c_str(), _authFailMsg.c_str());
|
||||
}
|
||||
|
||||
PsychicClient * PsychicHandler::checkForNewClient(PsychicClient *client)
|
||||
PsychicHandler* PsychicHandler::addFilter(PsychicRequestFilterFunction fn)
|
||||
{
|
||||
PsychicClient *c = PsychicHandler::getClient(client);
|
||||
if (c == NULL)
|
||||
{
|
||||
_filters.push_back(fn);
|
||||
return this;
|
||||
}
|
||||
|
||||
bool PsychicHandler::filter(PsychicRequest* request)
|
||||
{
|
||||
// run through our filter chain.
|
||||
for (auto& filter : _filters) {
|
||||
if (!filter(request)) {
|
||||
ESP_LOGD(PH_TAG, "Request %s refused by filter from handler", request->uri().c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PsychicHandler::setSubprotocol(const String& subprotocol)
|
||||
{
|
||||
this->_subprotocol = subprotocol;
|
||||
}
|
||||
const char* PsychicHandler::getSubprotocol() const
|
||||
{
|
||||
return _subprotocol.c_str();
|
||||
}
|
||||
|
||||
PsychicClient* PsychicHandler::checkForNewClient(PsychicClient* client)
|
||||
{
|
||||
PsychicClient* c = PsychicHandler::getClient(client);
|
||||
if (c == NULL) {
|
||||
c = client;
|
||||
addClient(c);
|
||||
c->isNew = true;
|
||||
}
|
||||
else
|
||||
} else
|
||||
c->isNew = false;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
void PsychicHandler::checkForClosedClient(PsychicClient *client)
|
||||
void PsychicHandler::checkForClosedClient(PsychicClient* client)
|
||||
{
|
||||
if (hasClient(client))
|
||||
{
|
||||
if (hasClient(client)) {
|
||||
closeCallback(client);
|
||||
removeClient(client);
|
||||
}
|
||||
}
|
||||
|
||||
void PsychicHandler::addClient(PsychicClient *client) {
|
||||
void PsychicHandler::addClient(PsychicClient* client)
|
||||
{
|
||||
_clients.push_back(client);
|
||||
}
|
||||
|
||||
void PsychicHandler::removeClient(PsychicClient *client) {
|
||||
void PsychicHandler::removeClient(PsychicClient* client)
|
||||
{
|
||||
_clients.remove(client);
|
||||
}
|
||||
|
||||
PsychicClient * PsychicHandler::getClient(int socket)
|
||||
PsychicClient* PsychicHandler::getClient(int socket)
|
||||
{
|
||||
//make sure the server has it too.
|
||||
// make sure the server has it too.
|
||||
if (!_server->hasClient(socket))
|
||||
return NULL;
|
||||
|
||||
//what about us?
|
||||
for (PsychicClient *client : _clients)
|
||||
// what about us?
|
||||
for (PsychicClient* client : _clients)
|
||||
if (client->socket() == socket)
|
||||
return client;
|
||||
|
||||
//nothing found.
|
||||
// nothing found.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PsychicClient * PsychicHandler::getClient(PsychicClient *client) {
|
||||
PsychicClient* PsychicHandler::getClient(PsychicClient* client)
|
||||
{
|
||||
return PsychicHandler::getClient(client->socket());
|
||||
}
|
||||
|
||||
bool PsychicHandler::hasClient(PsychicClient *socket) {
|
||||
bool PsychicHandler::hasClient(PsychicClient* socket)
|
||||
{
|
||||
return PsychicHandler::getClient(socket) != NULL;
|
||||
}
|
||||
|
||||
const std::list<PsychicClient*>& PsychicHandler::getClientList() {
|
||||
const std::list<PsychicClient*>& PsychicHandler::getClientList()
|
||||
{
|
||||
return _clients;
|
||||
}
|
||||
|
||||
PsychicHandler* PsychicHandler::addMiddleware(PsychicMiddleware* middleware)
|
||||
{
|
||||
if (!_chain) {
|
||||
_chain = new PsychicMiddlewareChain();
|
||||
}
|
||||
_chain->addMiddleware(middleware);
|
||||
return this;
|
||||
}
|
||||
|
||||
PsychicHandler* PsychicHandler::addMiddleware(PsychicMiddlewareCallback fn)
|
||||
{
|
||||
if (!_chain) {
|
||||
_chain = new PsychicMiddlewareChain();
|
||||
}
|
||||
_chain->addMiddleware(fn);
|
||||
return this;
|
||||
}
|
||||
|
||||
void PsychicHandler::removeMiddleware(PsychicMiddleware* middleware)
|
||||
{
|
||||
if (_chain) {
|
||||
_chain->removeMiddleware(middleware);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t PsychicHandler::process(PsychicRequest* request)
|
||||
{
|
||||
if (!filter(request)) {
|
||||
return HTTPD_404_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (!canHandle(request)) {
|
||||
ESP_LOGD(PH_TAG, "Request %s refused by handler", request->uri().c_str());
|
||||
return HTTPD_404_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (_chain) {
|
||||
return _chain->runChain(request, [this, request]() {
|
||||
return handleRequest(request, request->response());
|
||||
});
|
||||
|
||||
} else {
|
||||
return handleRequest(request, request->response());
|
||||
}
|
||||
}
|
||||
@@ -6,23 +6,21 @@
|
||||
|
||||
class PsychicEndpoint;
|
||||
class PsychicHttpServer;
|
||||
class PsychicMiddleware;
|
||||
class PsychicMiddlewareChain;
|
||||
|
||||
/*
|
||||
* HANDLER :: Can be attached to any endpoint or as a generic request handler.
|
||||
*/
|
||||
* HANDLER :: Can be attached to any endpoint or as a generic request handler.
|
||||
*/
|
||||
|
||||
class PsychicHandler {
|
||||
friend PsychicEndpoint;
|
||||
class PsychicHandler
|
||||
{
|
||||
friend PsychicEndpoint;
|
||||
|
||||
protected:
|
||||
PsychicRequestFilterFunction _filter;
|
||||
PsychicHttpServer *_server;
|
||||
|
||||
String _username;
|
||||
String _password;
|
||||
HTTPAuthMethod _method;
|
||||
String _realm;
|
||||
String _authFailMsg;
|
||||
PsychicHttpServer* _server = nullptr;
|
||||
PsychicMiddlewareChain* _chain = nullptr;
|
||||
std::list<PsychicRequestFilterFunction> _filters;
|
||||
|
||||
String _subprotocol;
|
||||
|
||||
@@ -32,35 +30,39 @@ class PsychicHandler {
|
||||
PsychicHandler();
|
||||
virtual ~PsychicHandler();
|
||||
|
||||
PsychicHandler* setFilter(PsychicRequestFilterFunction fn);
|
||||
bool filter(PsychicRequest *request);
|
||||
|
||||
PsychicHandler* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = "");
|
||||
bool needsAuthentication(PsychicRequest *request);
|
||||
esp_err_t authenticate(PsychicRequest *request);
|
||||
|
||||
virtual bool isWebSocket() { return false; };
|
||||
|
||||
void setSubprotocol(const String& subprotocol);
|
||||
const char* getSubprotocol() const;
|
||||
|
||||
PsychicClient * checkForNewClient(PsychicClient *client);
|
||||
void checkForClosedClient(PsychicClient *client);
|
||||
PsychicClient* checkForNewClient(PsychicClient* client);
|
||||
void checkForClosedClient(PsychicClient* client);
|
||||
|
||||
virtual void addClient(PsychicClient *client);
|
||||
virtual void removeClient(PsychicClient *client);
|
||||
virtual PsychicClient * getClient(int socket);
|
||||
virtual PsychicClient * getClient(PsychicClient *client);
|
||||
virtual void openCallback(PsychicClient *client) {};
|
||||
virtual void closeCallback(PsychicClient *client) {};
|
||||
virtual void addClient(PsychicClient* client);
|
||||
virtual void removeClient(PsychicClient* client);
|
||||
virtual PsychicClient* getClient(int socket);
|
||||
virtual PsychicClient* getClient(PsychicClient* client);
|
||||
virtual void openCallback(PsychicClient* client) {};
|
||||
virtual void closeCallback(PsychicClient* client) {};
|
||||
|
||||
bool hasClient(PsychicClient *client);
|
||||
bool hasClient(PsychicClient* client);
|
||||
int count() { return _clients.size(); };
|
||||
const std::list<PsychicClient*>& getClientList();
|
||||
|
||||
//derived classes must implement these functions
|
||||
virtual bool canHandle(PsychicRequest *request) { return true; };
|
||||
virtual esp_err_t handleRequest(PsychicRequest *request) = 0;
|
||||
// called to process this handler with its middleware chain and filers
|
||||
esp_err_t process(PsychicRequest* request);
|
||||
|
||||
//bool filter(PsychicRequest* request);
|
||||
PsychicHandler* addFilter(PsychicRequestFilterFunction fn);
|
||||
bool filter(PsychicRequest* request);
|
||||
|
||||
PsychicHandler* addMiddleware(PsychicMiddleware* middleware);
|
||||
PsychicHandler* addMiddleware(PsychicMiddlewareCallback fn);
|
||||
void removeMiddleware(PsychicMiddleware *middleware);
|
||||
|
||||
// derived classes must implement these functions
|
||||
virtual bool canHandle(PsychicRequest* request) { return true; };
|
||||
virtual esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) { return HTTPD_404_NOT_FOUND; };
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,24 +1,33 @@
|
||||
#ifndef PsychicHttp_h
|
||||
#define PsychicHttp_h
|
||||
|
||||
//#define ENABLE_ASYNC // This is something added in ESP-IDF 5.1.x where each request can be handled in its own thread
|
||||
// #define ENABLE_ASYNC // This is something added in ESP-IDF 5.1.x where each request can be handled in its own thread
|
||||
|
||||
#include <http_status.h>
|
||||
#include "PsychicEndpoint.h"
|
||||
#include "PsychicEventSource.h"
|
||||
#include "PsychicFileResponse.h"
|
||||
#include "PsychicHandler.h"
|
||||
#include "PsychicHttpServer.h"
|
||||
#include "PsychicJson.h"
|
||||
#include "PsychicMiddleware.h"
|
||||
#include "PsychicMiddlewareChain.h"
|
||||
#include "PsychicMiddlewares.h"
|
||||
#include "PsychicRequest.h"
|
||||
#include "PsychicResponse.h"
|
||||
#include "PsychicEndpoint.h"
|
||||
#include "PsychicHandler.h"
|
||||
#include "PsychicStaticFileHandler.h"
|
||||
#include "PsychicFileResponse.h"
|
||||
#include "PsychicStreamResponse.h"
|
||||
#include "PsychicUploadHandler.h"
|
||||
#include "PsychicVersion.h"
|
||||
#include "PsychicWebSocket.h"
|
||||
#include "PsychicEventSource.h"
|
||||
#include "PsychicJson.h"
|
||||
#include <http_status.h>
|
||||
|
||||
#ifdef ENABLE_ASYNC
|
||||
#include "async_worker.h"
|
||||
#endif
|
||||
|
||||
// debugging library
|
||||
#ifdef PSY_USE_ARDUINO_TRACE
|
||||
#include <ArduinoTrace.h>
|
||||
#endif
|
||||
|
||||
#endif /* PsychicHttp_h */
|
||||
@@ -1,155 +1,305 @@
|
||||
#include "PsychicHttpServer.h"
|
||||
#include "PsychicEndpoint.h"
|
||||
#include "PsychicHandler.h"
|
||||
#include "PsychicWebHandler.h"
|
||||
#include "PsychicStaticFileHandler.h"
|
||||
#include "PsychicWebSocket.h"
|
||||
#include "PsychicJson.h"
|
||||
#include "PsychicStaticFileHandler.h"
|
||||
#include "PsychicWebHandler.h"
|
||||
#include "PsychicWebSocket.h"
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||
#include "WiFi.h"
|
||||
|
||||
PsychicHttpServer::PsychicHttpServer() :
|
||||
_onOpen(NULL),
|
||||
_onClose(NULL)
|
||||
#endif
|
||||
PsychicHttpServer::PsychicHttpServer(uint16_t port)
|
||||
{
|
||||
maxRequestBodySize = MAX_REQUEST_BODY_SIZE;
|
||||
maxUploadSize = MAX_UPLOAD_SIZE;
|
||||
|
||||
defaultEndpoint = new PsychicEndpoint(this, HTTP_GET, "");
|
||||
onNotFound(PsychicHttpServer::defaultNotFoundHandler);
|
||||
|
||||
//for a regular server
|
||||
|
||||
// for a regular server
|
||||
config = HTTPD_DEFAULT_CONFIG();
|
||||
config.open_fn = PsychicHttpServer::openCallback;
|
||||
config.close_fn = PsychicHttpServer::closeCallback;
|
||||
config.uri_match_fn = httpd_uri_match_wildcard;
|
||||
config.global_user_ctx = this;
|
||||
config.global_user_ctx_free_fn = destroy;
|
||||
config.max_uri_handlers = 20;
|
||||
config.global_user_ctx_free_fn = PsychicHttpServer::destroy;
|
||||
config.uri_match_fn = MATCH_WILDCARD; // new internal endpoint matching - do not change this!!!
|
||||
config.stack_size = 4608; // default stack is just a little bit too small.
|
||||
|
||||
#ifdef ENABLE_ASYNC
|
||||
// It is advisable that httpd_config_t->max_open_sockets > MAX_ASYNC_REQUESTS
|
||||
// Why? This leaves at least one socket still available to handle
|
||||
// quick synchronous requests. Otherwise, all the sockets will
|
||||
// get taken by the long async handlers, and your server will no
|
||||
// longer be responsive.
|
||||
config.max_open_sockets = ASYNC_WORKER_COUNT + 1;
|
||||
config.lru_purge_enable = true;
|
||||
#endif
|
||||
// our internal matching function for endpoints
|
||||
_uri_match_fn = MATCH_WILDCARD; // use this change the endpoint matching function.
|
||||
|
||||
#ifdef ENABLE_ASYNC
|
||||
// It is advisable that httpd_config_t->max_open_sockets > MAX_ASYNC_REQUESTS
|
||||
// Why? This leaves at least one socket still available to handle
|
||||
// quick synchronous requests. Otherwise, all the sockets will
|
||||
// get taken by the long async handlers, and your server will no
|
||||
// longer be responsive.
|
||||
config.max_open_sockets = ASYNC_WORKER_COUNT + 1;
|
||||
config.lru_purge_enable = true;
|
||||
#endif
|
||||
|
||||
setPort(port);
|
||||
}
|
||||
|
||||
PsychicHttpServer::~PsychicHttpServer()
|
||||
{
|
||||
for (auto *client : _clients)
|
||||
delete(client);
|
||||
_esp_idf_endpoints.clear();
|
||||
|
||||
for (auto* client : _clients)
|
||||
delete (client);
|
||||
_clients.clear();
|
||||
|
||||
for (auto *endpoint : _endpoints)
|
||||
delete(endpoint);
|
||||
for (auto* endpoint : _endpoints)
|
||||
delete (endpoint);
|
||||
_endpoints.clear();
|
||||
|
||||
for (auto *handler : _handlers)
|
||||
delete(handler);
|
||||
for (auto* handler : _handlers)
|
||||
delete (handler);
|
||||
_handlers.clear();
|
||||
|
||||
for (auto* rewrite : _rewrites)
|
||||
delete (rewrite);
|
||||
_rewrites.clear();
|
||||
|
||||
delete defaultEndpoint;
|
||||
delete _chain;
|
||||
}
|
||||
|
||||
void PsychicHttpServer::destroy(void *ctx)
|
||||
void PsychicHttpServer::destroy(void* ctx)
|
||||
{
|
||||
// do not release any resource for PsychicHttpServer in order to be able to restart it after stopping
|
||||
}
|
||||
|
||||
esp_err_t PsychicHttpServer::listen(uint16_t port)
|
||||
void PsychicHttpServer::setPort(uint16_t port)
|
||||
{
|
||||
this->_use_ssl = false;
|
||||
this->config.server_port = port;
|
||||
|
||||
return this->_start();
|
||||
}
|
||||
|
||||
esp_err_t PsychicHttpServer::_start()
|
||||
uint16_t PsychicHttpServer::getPort()
|
||||
{
|
||||
return this->config.server_port;
|
||||
}
|
||||
|
||||
esp_err_t PsychicHttpServer::start()
|
||||
{
|
||||
if (_running)
|
||||
return ESP_OK;
|
||||
|
||||
esp_err_t ret;
|
||||
|
||||
#ifdef ENABLE_ASYNC
|
||||
// start workers
|
||||
start_async_req_workers();
|
||||
#endif
|
||||
#ifdef ENABLE_ASYNC
|
||||
// start workers
|
||||
start_async_req_workers();
|
||||
#endif
|
||||
|
||||
//fire it up.
|
||||
// one URI handler for each http_method
|
||||
config.max_uri_handlers = supported_methods.size() + _esp_idf_endpoints.size();
|
||||
|
||||
// fire it up.
|
||||
ret = _startServer();
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(PH_TAG, "Server start failed (%s)", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// some handlers (aka websockets) need actual endpoints in esp-idf http_server
|
||||
for (auto& endpoint : _esp_idf_endpoints) {
|
||||
ESP_LOGD(PH_TAG, "Adding endpoint %s | %s", endpoint.uri, http_method_str((http_method)endpoint.method));
|
||||
|
||||
// Register endpoint with ESP-IDF server
|
||||
esp_err_t ret = httpd_register_uri_handler(this->server, &endpoint);
|
||||
if (ret != ESP_OK)
|
||||
ESP_LOGE(PH_TAG, "Add endpoint failed (%s)", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
// Register a handler for each http_method method - it will match all requests with that URI/method
|
||||
for (auto& method : supported_methods) {
|
||||
ESP_LOGD(PH_TAG, "Adding %s meta endpoint", http_method_str((http_method)method));
|
||||
|
||||
httpd_uri_t my_uri;
|
||||
my_uri.uri = "*";
|
||||
my_uri.method = method;
|
||||
my_uri.handler = PsychicHttpServer::requestHandler;
|
||||
my_uri.is_websocket = false;
|
||||
my_uri.supported_subprotocol = "";
|
||||
|
||||
// Register endpoint with ESP-IDF server
|
||||
esp_err_t ret = httpd_register_uri_handler(this->server, &my_uri);
|
||||
if (ret != ESP_OK)
|
||||
ESP_LOGE(PH_TAG, "Add endpoint failed (%s)", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
// Register handler
|
||||
ret = httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, PsychicHttpServer::notFoundHandler);
|
||||
if (ret != ESP_OK)
|
||||
ESP_LOGE(PH_TAG, "Add 404 handler failed (%s)", esp_err_to_name(ret));
|
||||
ESP_LOGE(PH_TAG, "Add 404 handler failed (%s)", esp_err_to_name(ret));
|
||||
|
||||
ESP_LOGI(PH_TAG, "Server started on port %" PRIu16, getPort());
|
||||
|
||||
_running = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t PsychicHttpServer::_startServer() {
|
||||
esp_err_t PsychicHttpServer::_startServer()
|
||||
{
|
||||
return httpd_start(&this->server, &this->config);
|
||||
}
|
||||
|
||||
void PsychicHttpServer::stop()
|
||||
esp_err_t PsychicHttpServer::stop()
|
||||
{
|
||||
httpd_stop(this->server);
|
||||
if (!_running)
|
||||
return ESP_OK;
|
||||
|
||||
// some handlers (aka websockets) need actual endpoints in esp-idf http_server
|
||||
for (auto& endpoint : _esp_idf_endpoints) {
|
||||
ESP_LOGD(PH_TAG, "Removing endpoint %s | %s", endpoint.uri, http_method_str((http_method)endpoint.method));
|
||||
|
||||
// Unregister endpoint with ESP-IDF server
|
||||
esp_err_t ret = httpd_unregister_uri_handler(this->server, endpoint.uri, endpoint.method);
|
||||
if (ret != ESP_OK)
|
||||
ESP_LOGE(PH_TAG, "Removal of endpoint failed (%s)", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
// Unregister a handler for each http_method method - it will match all requests with that URI/method
|
||||
for (auto& method : supported_methods) {
|
||||
ESP_LOGD(PH_TAG, "Removing %s meta endpoint", http_method_str((http_method)method));
|
||||
|
||||
// Unregister endpoint with ESP-IDF server
|
||||
esp_err_t ret = httpd_unregister_uri_handler(this->server, "*", method);
|
||||
if (ret != ESP_OK)
|
||||
ESP_LOGE(PH_TAG, "Removal of endpoint failed (%s)", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
esp_err_t ret = _stopServer();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(PH_TAG, "Server stop failed (%s)", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGI(PH_TAG, "Server stopped");
|
||||
_running = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
PsychicHandler& PsychicHttpServer::addHandler(PsychicHandler* handler){
|
||||
esp_err_t PsychicHttpServer::_stopServer()
|
||||
{
|
||||
return httpd_stop(this->server);
|
||||
}
|
||||
|
||||
void PsychicHttpServer::reset()
|
||||
{
|
||||
if (_running)
|
||||
stop();
|
||||
|
||||
for (auto* client : _clients)
|
||||
delete (client);
|
||||
_clients.clear();
|
||||
|
||||
for (auto* endpoint : _endpoints)
|
||||
delete (endpoint);
|
||||
_endpoints.clear();
|
||||
|
||||
for (auto* handler : _handlers)
|
||||
delete (handler);
|
||||
_handlers.clear();
|
||||
|
||||
for (auto* rewrite : _rewrites)
|
||||
delete (rewrite);
|
||||
_rewrites.clear();
|
||||
|
||||
_esp_idf_endpoints.clear();
|
||||
|
||||
onNotFound(PsychicHttpServer::defaultNotFoundHandler);
|
||||
_onOpen = nullptr;
|
||||
_onClose = nullptr;
|
||||
}
|
||||
|
||||
httpd_uri_match_func_t PsychicHttpServer::getURIMatchFunction()
|
||||
{
|
||||
return _uri_match_fn;
|
||||
}
|
||||
|
||||
void PsychicHttpServer::setURIMatchFunction(httpd_uri_match_func_t match_fn)
|
||||
{
|
||||
_uri_match_fn = match_fn;
|
||||
}
|
||||
|
||||
PsychicHandler* PsychicHttpServer::addHandler(PsychicHandler* handler)
|
||||
{
|
||||
_handlers.push_back(handler);
|
||||
return *handler;
|
||||
return handler;
|
||||
}
|
||||
|
||||
void PsychicHttpServer::removeHandler(PsychicHandler *handler){
|
||||
void PsychicHttpServer::removeHandler(PsychicHandler* handler)
|
||||
{
|
||||
_handlers.remove(handler);
|
||||
}
|
||||
|
||||
PsychicEndpoint* PsychicHttpServer::on(const char* uri) {
|
||||
PsychicRewrite* PsychicHttpServer::addRewrite(PsychicRewrite* rewrite)
|
||||
{
|
||||
_rewrites.push_back(rewrite);
|
||||
return rewrite;
|
||||
}
|
||||
|
||||
void PsychicHttpServer::removeRewrite(PsychicRewrite* rewrite)
|
||||
{
|
||||
_rewrites.remove(rewrite);
|
||||
}
|
||||
|
||||
PsychicRewrite* PsychicHttpServer::rewrite(const char* from, const char* to)
|
||||
{
|
||||
return addRewrite(new PsychicRewrite(from, to));
|
||||
}
|
||||
|
||||
PsychicEndpoint* PsychicHttpServer::on(const char* uri)
|
||||
{
|
||||
return on(uri, HTTP_GET);
|
||||
}
|
||||
|
||||
PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method)
|
||||
PsychicEndpoint* PsychicHttpServer::on(const char* uri, int method)
|
||||
{
|
||||
PsychicWebHandler *handler = new PsychicWebHandler();
|
||||
PsychicWebHandler* handler = new PsychicWebHandler();
|
||||
|
||||
return on(uri, method, handler);
|
||||
}
|
||||
|
||||
PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHandler *handler)
|
||||
PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHandler* handler)
|
||||
{
|
||||
return on(uri, HTTP_GET, handler);
|
||||
}
|
||||
|
||||
PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicHandler *handler)
|
||||
PsychicEndpoint* PsychicHttpServer::on(const char* uri, int method, PsychicHandler* handler)
|
||||
{
|
||||
//make our endpoint
|
||||
PsychicEndpoint *endpoint = new PsychicEndpoint(this, method, uri);
|
||||
// make our endpoint
|
||||
PsychicEndpoint* endpoint = new PsychicEndpoint(this, method, uri);
|
||||
|
||||
//set our handler
|
||||
// set our handler
|
||||
endpoint->setHandler(handler);
|
||||
|
||||
// URI handler structure
|
||||
httpd_uri_t my_uri {
|
||||
.uri = uri,
|
||||
.method = method,
|
||||
.handler = PsychicEndpoint::requestCallback,
|
||||
.user_ctx = endpoint,
|
||||
.is_websocket = handler->isWebSocket(),
|
||||
.supported_subprotocol = handler->getSubprotocol()
|
||||
};
|
||||
|
||||
// Register endpoint with ESP-IDF server
|
||||
esp_err_t ret = httpd_register_uri_handler(this->server, &my_uri);
|
||||
if (ret != ESP_OK)
|
||||
ESP_LOGE(PH_TAG, "Add endpoint failed (%s)", esp_err_to_name(ret));
|
||||
// websockets need a real endpoint in esp-idf
|
||||
if (handler->isWebSocket()) {
|
||||
// URI handler structure
|
||||
httpd_uri_t my_uri;
|
||||
my_uri.uri = uri;
|
||||
my_uri.method = HTTP_GET;
|
||||
my_uri.handler = PsychicEndpoint::requestCallback;
|
||||
my_uri.user_ctx = endpoint;
|
||||
my_uri.is_websocket = handler->isWebSocket();
|
||||
my_uri.supported_subprotocol = handler->getSubprotocol();
|
||||
|
||||
//save it for later
|
||||
// save it to our 'real' handlers for later.
|
||||
_esp_idf_endpoints.push_back(my_uri);
|
||||
}
|
||||
|
||||
// if this is a method we haven't added yet, do it.
|
||||
if (method != HTTP_ANY) {
|
||||
if (!(std::find(supported_methods.begin(), supported_methods.end(), (http_method)method) != supported_methods.end())) {
|
||||
ESP_LOGD(PH_TAG, "Adding %s to server.supported_methods", http_method_str((http_method)method));
|
||||
supported_methods.push_back((http_method)method);
|
||||
}
|
||||
}
|
||||
|
||||
// add it to our meta endpoints
|
||||
_endpoints.push_back(endpoint);
|
||||
|
||||
return endpoint;
|
||||
@@ -160,10 +310,10 @@ PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHttpRequestCallba
|
||||
return on(uri, HTTP_GET, fn);
|
||||
}
|
||||
|
||||
PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicHttpRequestCallback fn)
|
||||
PsychicEndpoint* PsychicHttpServer::on(const char* uri, int method, PsychicHttpRequestCallback fn)
|
||||
{
|
||||
//these basic requests need a basic web handler
|
||||
PsychicWebHandler *handler = new PsychicWebHandler();
|
||||
// these basic requests need a basic web handler
|
||||
PsychicWebHandler* handler = new PsychicWebHandler();
|
||||
handler->onRequest(fn);
|
||||
|
||||
return on(uri, method, handler);
|
||||
@@ -174,59 +324,187 @@ PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicJsonRequestCallba
|
||||
return on(uri, HTTP_GET, fn);
|
||||
}
|
||||
|
||||
PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicJsonRequestCallback fn)
|
||||
PsychicEndpoint* PsychicHttpServer::on(const char* uri, int method, PsychicJsonRequestCallback fn)
|
||||
{
|
||||
//these basic requests need a basic web handler
|
||||
PsychicJsonHandler *handler = new PsychicJsonHandler();
|
||||
// these basic requests need a basic web handler
|
||||
PsychicJsonHandler* handler = new PsychicJsonHandler();
|
||||
handler->onRequest(fn);
|
||||
|
||||
return on(uri, method, handler);
|
||||
}
|
||||
|
||||
bool PsychicHttpServer::removeEndpoint(const char* uri, int method)
|
||||
{
|
||||
// some handlers (aka websockets) need actual endpoints in esp-idf http_server
|
||||
// don't return from here, because its added to the _endpoints list too.
|
||||
for (auto& endpoint : _esp_idf_endpoints) {
|
||||
if (!strcmp(endpoint.uri, uri) && method == endpoint.method) {
|
||||
ESP_LOGD(PH_TAG, "Unregistering endpoint %s | %s", endpoint.uri, http_method_str((http_method)endpoint.method));
|
||||
|
||||
// Register endpoint with ESP-IDF server
|
||||
esp_err_t ret = httpd_register_uri_handler(this->server, &endpoint);
|
||||
if (ret != ESP_OK)
|
||||
ESP_LOGE(PH_TAG, "Add endpoint failed (%s)", esp_err_to_name(ret));
|
||||
}
|
||||
}
|
||||
|
||||
// loop through our endpoints and see if anyone matches
|
||||
for (auto* endpoint : _endpoints) {
|
||||
if (endpoint->uri().equals(uri) && method == endpoint->_method)
|
||||
return removeEndpoint(endpoint);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PsychicHttpServer::removeEndpoint(PsychicEndpoint* endpoint)
|
||||
{
|
||||
_endpoints.remove(endpoint);
|
||||
return true;
|
||||
}
|
||||
|
||||
PsychicHttpServer* PsychicHttpServer::addFilter(PsychicRequestFilterFunction fn)
|
||||
{
|
||||
_filters.push_back(fn);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
bool PsychicHttpServer::_filter(PsychicRequest* request)
|
||||
{
|
||||
// run through our filter chain.
|
||||
for (auto& filter : _filters) {
|
||||
if (!filter(request))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PsychicHttpServer* PsychicHttpServer::addMiddleware(PsychicMiddleware* middleware)
|
||||
{
|
||||
if (!_chain) {
|
||||
_chain = new PsychicMiddlewareChain();
|
||||
}
|
||||
_chain->addMiddleware(middleware);
|
||||
return this;
|
||||
}
|
||||
|
||||
PsychicHttpServer* PsychicHttpServer::addMiddleware(PsychicMiddlewareCallback fn)
|
||||
{
|
||||
if (!_chain) {
|
||||
_chain = new PsychicMiddlewareChain();
|
||||
}
|
||||
_chain->addMiddleware(fn);
|
||||
return this;
|
||||
}
|
||||
|
||||
void PsychicHttpServer::removeMiddleware(PsychicMiddleware* middleware)
|
||||
{
|
||||
if (_chain) {
|
||||
_chain->removeMiddleware(middleware);
|
||||
}
|
||||
}
|
||||
|
||||
void PsychicHttpServer::onNotFound(PsychicHttpRequestCallback fn)
|
||||
{
|
||||
PsychicWebHandler *handler = new PsychicWebHandler();
|
||||
PsychicWebHandler* handler = new PsychicWebHandler();
|
||||
handler->onRequest(fn == nullptr ? PsychicHttpServer::defaultNotFoundHandler : fn);
|
||||
|
||||
this->defaultEndpoint->setHandler(handler);
|
||||
}
|
||||
|
||||
esp_err_t PsychicHttpServer::notFoundHandler(httpd_req_t *req, httpd_err_code_t err)
|
||||
bool PsychicHttpServer::_rewriteRequest(PsychicRequest* request)
|
||||
{
|
||||
PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(req->handle);
|
||||
PsychicRequest request(server, req);
|
||||
|
||||
//loop through our global handlers and see if anyone wants it
|
||||
for(auto *handler: server->_handlers)
|
||||
{
|
||||
//are we capable of handling this?
|
||||
if (handler->filter(&request) && handler->canHandle(&request))
|
||||
{
|
||||
//check our credentials
|
||||
if (handler->needsAuthentication(&request))
|
||||
return handler->authenticate(&request);
|
||||
else
|
||||
return handler->handleRequest(&request);
|
||||
for (auto* r : _rewrites) {
|
||||
if (r->match(request)) {
|
||||
request->_setUri(r->toUrl().c_str());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//nothing found, give it to our defaultEndpoint
|
||||
PsychicHandler *handler = server->defaultEndpoint->handler();
|
||||
if (handler->filter(&request) && handler->canHandle(&request))
|
||||
return handler->handleRequest(&request);
|
||||
|
||||
//not sure how we got this far.
|
||||
return ESP_ERR_HTTPD_INVALID_REQ;
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest *request)
|
||||
esp_err_t PsychicHttpServer::requestHandler(httpd_req_t* req)
|
||||
{
|
||||
request->reply(404, "text/html", "That URI does not exist.");
|
||||
PsychicHttpServer* server = (PsychicHttpServer*)httpd_get_global_user_ctx(req->handle);
|
||||
PsychicRequest request(server, req);
|
||||
|
||||
return ESP_OK;
|
||||
// process any URL rewrites
|
||||
server->_rewriteRequest(&request);
|
||||
|
||||
// run it through our global server filter list
|
||||
if (!server->_filter(&request)) {
|
||||
ESP_LOGD(PH_TAG, "Request %s refused by global filter", request.uri().c_str());
|
||||
return request.response()->send(400);
|
||||
}
|
||||
|
||||
// then runs the request through the filter chain
|
||||
esp_err_t ret;
|
||||
if (server->_chain) {
|
||||
ret = server->_chain->runChain(&request, [server, &request]() {
|
||||
return server->_process(&request);
|
||||
});
|
||||
} else {
|
||||
ret = server->_process(&request);
|
||||
}
|
||||
ESP_LOGD(PH_TAG, "Request %s processed by global middleware: %s", request.uri().c_str(), esp_err_to_name(ret));
|
||||
|
||||
if (ret == HTTPD_404_NOT_FOUND) {
|
||||
return PsychicHttpServer::notFoundHandler(req, HTTPD_404_NOT_FOUND);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void PsychicHttpServer::onOpen(PsychicClientCallback handler) {
|
||||
esp_err_t PsychicHttpServer::_process(PsychicRequest* request)
|
||||
{
|
||||
// loop through our endpoints and see if anyone wants it.
|
||||
for (auto* endpoint : _endpoints) {
|
||||
if (endpoint->matches(request->uri().c_str())) {
|
||||
if (endpoint->_method == request->method() || endpoint->_method == HTTP_ANY) {
|
||||
request->setEndpoint(endpoint);
|
||||
return endpoint->process(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loop through our global handlers and see if anyone wants it
|
||||
for (auto* handler : _handlers) {
|
||||
esp_err_t ret = handler->process(request);
|
||||
if (ret != HTTPD_404_NOT_FOUND)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return HTTPD_404_NOT_FOUND;
|
||||
}
|
||||
|
||||
esp_err_t PsychicHttpServer::notFoundHandler(httpd_req_t* req, httpd_err_code_t err)
|
||||
{
|
||||
PsychicHttpServer* server = (PsychicHttpServer*)httpd_get_global_user_ctx(req->handle);
|
||||
PsychicRequest request(server, req);
|
||||
|
||||
// pull up our default handler / endpoint
|
||||
PsychicHandler* handler = server->defaultEndpoint->handler();
|
||||
if (!handler)
|
||||
return request.response()->send(404);
|
||||
|
||||
esp_err_t ret = handler->process(&request);
|
||||
if (ret != HTTPD_404_NOT_FOUND)
|
||||
return ret;
|
||||
|
||||
// not sure how we got this far.
|
||||
return request.response()->send(404);
|
||||
}
|
||||
|
||||
esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest* request, PsychicResponse* response)
|
||||
{
|
||||
return response->send(404, "text/html", "That URI does not exist.");
|
||||
}
|
||||
|
||||
void PsychicHttpServer::onOpen(PsychicClientCallback handler)
|
||||
{
|
||||
this->_onOpen = handler;
|
||||
}
|
||||
|
||||
@@ -234,25 +512,25 @@ esp_err_t PsychicHttpServer::openCallback(httpd_handle_t hd, int sockfd)
|
||||
{
|
||||
ESP_LOGD(PH_TAG, "New client connected %d", sockfd);
|
||||
|
||||
//get our global server reference
|
||||
PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(hd);
|
||||
// get our global server reference
|
||||
PsychicHttpServer* server = (PsychicHttpServer*)httpd_get_global_user_ctx(hd);
|
||||
|
||||
//lookup our client
|
||||
PsychicClient *client = server->getClient(sockfd);
|
||||
if (client == NULL)
|
||||
{
|
||||
// lookup our client
|
||||
PsychicClient* client = server->getClient(sockfd);
|
||||
if (client == NULL) {
|
||||
client = new PsychicClient(hd, sockfd);
|
||||
server->addClient(client);
|
||||
}
|
||||
|
||||
//user callback
|
||||
// user callback
|
||||
if (server->_onOpen != NULL)
|
||||
server->_onOpen(client);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void PsychicHttpServer::onClose(PsychicClientCallback handler) {
|
||||
void PsychicHttpServer::onClose(PsychicClientCallback handler)
|
||||
{
|
||||
this->_onClose = handler;
|
||||
}
|
||||
|
||||
@@ -260,30 +538,27 @@ void PsychicHttpServer::closeCallback(httpd_handle_t hd, int sockfd)
|
||||
{
|
||||
ESP_LOGD(PH_TAG, "Client disconnected %d", sockfd);
|
||||
|
||||
PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(hd);
|
||||
PsychicHttpServer* server = (PsychicHttpServer*)httpd_get_global_user_ctx(hd);
|
||||
|
||||
//lookup our client
|
||||
PsychicClient *client = server->getClient(sockfd);
|
||||
if (client != NULL)
|
||||
{
|
||||
//give our handlers a chance to handle a disconnect first
|
||||
for (PsychicEndpoint * endpoint : server->_endpoints)
|
||||
{
|
||||
PsychicHandler *handler = endpoint->handler();
|
||||
// lookup our client
|
||||
PsychicClient* client = server->getClient(sockfd);
|
||||
if (client != NULL) {
|
||||
// give our handlers a chance to handle a disconnect first
|
||||
for (PsychicEndpoint* endpoint : server->_endpoints) {
|
||||
PsychicHandler* handler = endpoint->handler();
|
||||
handler->checkForClosedClient(client);
|
||||
}
|
||||
|
||||
//do we have a callback attached?
|
||||
// do we have a callback attached?
|
||||
if (server->_onClose != NULL)
|
||||
server->_onClose(client);
|
||||
|
||||
//remove it from our list
|
||||
// remove it from our list
|
||||
server->removeClient(client);
|
||||
}
|
||||
else
|
||||
} else
|
||||
ESP_LOGE(PH_TAG, "No client record %d", sockfd);
|
||||
|
||||
//finally close it out.
|
||||
// finally close it out.
|
||||
close(sockfd);
|
||||
}
|
||||
|
||||
@@ -295,48 +570,56 @@ PsychicStaticFileHandler* PsychicHttpServer::serveStatic(const char* uri, fs::FS
|
||||
return handler;
|
||||
}
|
||||
|
||||
void PsychicHttpServer::addClient(PsychicClient *client) {
|
||||
void PsychicHttpServer::addClient(PsychicClient* client)
|
||||
{
|
||||
_clients.push_back(client);
|
||||
}
|
||||
|
||||
void PsychicHttpServer::removeClient(PsychicClient *client) {
|
||||
void PsychicHttpServer::removeClient(PsychicClient* client)
|
||||
{
|
||||
_clients.remove(client);
|
||||
delete client;
|
||||
}
|
||||
|
||||
PsychicClient * PsychicHttpServer::getClient(int socket) {
|
||||
for (PsychicClient * client : _clients)
|
||||
PsychicClient* PsychicHttpServer::getClient(int socket)
|
||||
{
|
||||
for (PsychicClient* client : _clients)
|
||||
if (client->socket() == socket)
|
||||
return client;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PsychicClient * PsychicHttpServer::getClient(httpd_req_t *req) {
|
||||
PsychicClient* PsychicHttpServer::getClient(httpd_req_t* req)
|
||||
{
|
||||
return getClient(httpd_req_to_sockfd(req));
|
||||
}
|
||||
|
||||
bool PsychicHttpServer::hasClient(int socket) {
|
||||
bool PsychicHttpServer::hasClient(int socket)
|
||||
{
|
||||
return getClient(socket) != NULL;
|
||||
}
|
||||
|
||||
const std::list<PsychicClient*>& PsychicHttpServer::getClientList() {
|
||||
const std::list<PsychicClient*>& PsychicHttpServer::getClientList()
|
||||
{
|
||||
return _clients;
|
||||
}
|
||||
|
||||
bool ON_STA_FILTER(PsychicRequest *request) {
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||
return WiFi.localIP() == request->client()->localIP();
|
||||
#else
|
||||
bool ON_STA_FILTER(PsychicRequest* request)
|
||||
{
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32H2)
|
||||
return false;
|
||||
#else
|
||||
return WiFi.localIP() == request->client()->localIP();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ON_AP_FILTER(PsychicRequest *request) {
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
||||
return WiFi.softAPIP() == request->client()->localIP();
|
||||
#else
|
||||
bool ON_AP_FILTER(PsychicRequest* request)
|
||||
{
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32H2)
|
||||
return false;
|
||||
#else
|
||||
return WiFi.softAPIP() == request->client()->localIP();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -350,25 +633,46 @@ String urlDecode(const char* encoded)
|
||||
|
||||
size_t i, j = 0;
|
||||
for (i = 0; i < length; ++i) {
|
||||
if (encoded[i] == '%' && isxdigit(encoded[i + 1]) && isxdigit(encoded[i + 2])) {
|
||||
// Valid percent-encoded sequence
|
||||
int hex;
|
||||
sscanf(encoded + i + 1, "%2x", &hex);
|
||||
decoded[j++] = (char)hex;
|
||||
i += 2; // Skip the two hexadecimal characters
|
||||
} else if (encoded[i] == '+') {
|
||||
// Convert '+' to space
|
||||
decoded[j++] = ' ';
|
||||
} else {
|
||||
// Copy other characters as they are
|
||||
decoded[j++] = encoded[i];
|
||||
}
|
||||
if (encoded[i] == '%' && isxdigit(encoded[i + 1]) && isxdigit(encoded[i + 2])) {
|
||||
// Valid percent-encoded sequence
|
||||
int hex;
|
||||
sscanf(encoded + i + 1, "%2x", &hex);
|
||||
decoded[j++] = (char)hex;
|
||||
i += 2; // Skip the two hexadecimal characters
|
||||
} else if (encoded[i] == '+') {
|
||||
// Convert '+' to space
|
||||
decoded[j++] = ' ';
|
||||
} else {
|
||||
// Copy other characters as they are
|
||||
decoded[j++] = encoded[i];
|
||||
}
|
||||
}
|
||||
|
||||
decoded[j] = '\0'; // Null-terminate the decoded string
|
||||
decoded[j] = '\0'; // Null-terminate the decoded string
|
||||
|
||||
String output(decoded);
|
||||
free(decoded);
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
bool psychic_uri_match_simple(const char* uri1, const char* uri2, size_t len2)
|
||||
{
|
||||
return strlen(uri1) == len2 && // First match lengths
|
||||
(strncmp(uri1, uri2, len2) == 0); // Then match actual URIs
|
||||
}
|
||||
|
||||
#ifdef PSY_ENABLE_REGEX
|
||||
bool psychic_uri_match_regex(const char* uri1, const char* uri2, size_t len2)
|
||||
{
|
||||
std::regex pattern(uri1);
|
||||
std::smatch matches;
|
||||
std::string s(uri2);
|
||||
|
||||
// len2 is passed in to tell us to match up to a point.
|
||||
if (s.length() > len2)
|
||||
s = s.substr(0, len2);
|
||||
|
||||
return std::regex_search(s, matches, pattern);
|
||||
}
|
||||
#endif
|
||||
@@ -1,9 +1,20 @@
|
||||
#ifndef PsychicHttpServer_h
|
||||
#define PsychicHttpServer_h
|
||||
|
||||
#include "PsychicCore.h"
|
||||
#include "PsychicClient.h"
|
||||
#include "PsychicCore.h"
|
||||
#include "PsychicHandler.h"
|
||||
#include "PsychicMiddleware.h"
|
||||
#include "PsychicMiddlewareChain.h"
|
||||
#include "PsychicRewrite.h"
|
||||
|
||||
#ifdef PSY_ENABLE_REGEX
|
||||
#include <regex>
|
||||
#endif
|
||||
|
||||
#ifndef HTTP_ANY
|
||||
#define HTTP_ANY INT_MAX
|
||||
#endif
|
||||
|
||||
class PsychicEndpoint;
|
||||
class PsychicHandler;
|
||||
@@ -12,70 +23,123 @@ class PsychicStaticFileHandler;
|
||||
class PsychicHttpServer
|
||||
{
|
||||
protected:
|
||||
bool _use_ssl = false;
|
||||
std::list<httpd_uri_t> _esp_idf_endpoints;
|
||||
std::list<PsychicEndpoint*> _endpoints;
|
||||
std::list<PsychicHandler*> _handlers;
|
||||
std::list<PsychicClient*> _clients;
|
||||
std::list<PsychicRewrite*> _rewrites;
|
||||
std::list<PsychicRequestFilterFunction> _filters;
|
||||
|
||||
PsychicClientCallback _onOpen;
|
||||
PsychicClientCallback _onClose;
|
||||
PsychicClientCallback _onOpen = nullptr;
|
||||
PsychicClientCallback _onClose = nullptr;
|
||||
PsychicMiddlewareChain* _chain = nullptr;
|
||||
|
||||
esp_err_t _start();
|
||||
virtual esp_err_t _startServer();
|
||||
virtual esp_err_t _stopServer();
|
||||
bool _running = false;
|
||||
httpd_uri_match_func_t _uri_match_fn = nullptr;
|
||||
|
||||
bool _rewriteRequest(PsychicRequest* request);
|
||||
esp_err_t _process(PsychicRequest* request);
|
||||
bool _filter(PsychicRequest* request);
|
||||
|
||||
public:
|
||||
PsychicHttpServer();
|
||||
PsychicHttpServer(uint16_t port = 80);
|
||||
virtual ~PsychicHttpServer();
|
||||
|
||||
//esp-idf specific stuff
|
||||
// what methods to support
|
||||
std::list<http_method> supported_methods = {
|
||||
HTTP_GET,
|
||||
HTTP_POST,
|
||||
HTTP_DELETE,
|
||||
HTTP_HEAD,
|
||||
HTTP_PUT,
|
||||
HTTP_OPTIONS
|
||||
};
|
||||
|
||||
// esp-idf specific stuff
|
||||
httpd_handle_t server;
|
||||
httpd_config_t config;
|
||||
|
||||
//some limits on what we will accept
|
||||
// some limits on what we will accept
|
||||
unsigned long maxUploadSize;
|
||||
unsigned long maxRequestBodySize;
|
||||
|
||||
PsychicEndpoint *defaultEndpoint;
|
||||
PsychicEndpoint* defaultEndpoint;
|
||||
|
||||
static void destroy(void *ctx);
|
||||
static void destroy(void* ctx);
|
||||
|
||||
esp_err_t listen(uint16_t port);
|
||||
virtual void setPort(uint16_t port);
|
||||
virtual uint16_t getPort();
|
||||
|
||||
virtual void stop();
|
||||
bool isRunning() { return _running; }
|
||||
esp_err_t begin() { return start(); }
|
||||
esp_err_t end() { return stop(); }
|
||||
esp_err_t start();
|
||||
esp_err_t stop();
|
||||
void reset();
|
||||
|
||||
PsychicHandler& addHandler(PsychicHandler* handler);
|
||||
httpd_uri_match_func_t getURIMatchFunction();
|
||||
void setURIMatchFunction(httpd_uri_match_func_t match_fn);
|
||||
|
||||
PsychicRewrite* addRewrite(PsychicRewrite* rewrite);
|
||||
void removeRewrite(PsychicRewrite* rewrite);
|
||||
PsychicRewrite* rewrite(const char* from, const char* to);
|
||||
|
||||
PsychicHandler* addHandler(PsychicHandler* handler);
|
||||
void removeHandler(PsychicHandler* handler);
|
||||
|
||||
void addClient(PsychicClient *client);
|
||||
void removeClient(PsychicClient *client);
|
||||
void addClient(PsychicClient* client);
|
||||
void removeClient(PsychicClient* client);
|
||||
PsychicClient* getClient(int socket);
|
||||
PsychicClient* getClient(httpd_req_t *req);
|
||||
PsychicClient* getClient(httpd_req_t* req);
|
||||
bool hasClient(int socket);
|
||||
int count() { return _clients.size(); };
|
||||
const std::list<PsychicClient*>& getClientList();
|
||||
|
||||
PsychicEndpoint* on(const char* uri);
|
||||
PsychicEndpoint* on(const char* uri, http_method method);
|
||||
PsychicEndpoint* on(const char* uri, PsychicHandler *handler);
|
||||
PsychicEndpoint* on(const char* uri, http_method method, PsychicHandler *handler);
|
||||
PsychicEndpoint* on(const char* uri, int method);
|
||||
PsychicEndpoint* on(const char* uri, PsychicHandler* handler);
|
||||
PsychicEndpoint* on(const char* uri, int method, PsychicHandler* handler);
|
||||
PsychicEndpoint* on(const char* uri, PsychicHttpRequestCallback onRequest);
|
||||
PsychicEndpoint* on(const char* uri, http_method method, PsychicHttpRequestCallback onRequest);
|
||||
PsychicEndpoint* on(const char* uri, int method, PsychicHttpRequestCallback onRequest);
|
||||
PsychicEndpoint* on(const char* uri, PsychicJsonRequestCallback onRequest);
|
||||
PsychicEndpoint* on(const char* uri, http_method method, PsychicJsonRequestCallback onRequest);
|
||||
PsychicEndpoint* on(const char* uri, int method, PsychicJsonRequestCallback onRequest);
|
||||
|
||||
static esp_err_t notFoundHandler(httpd_req_t *req, httpd_err_code_t err);
|
||||
static esp_err_t defaultNotFoundHandler(PsychicRequest *request);
|
||||
void onNotFound(PsychicHttpRequestCallback fn);
|
||||
bool removeEndpoint(const char* uri, int method);
|
||||
bool removeEndpoint(PsychicEndpoint* endpoint);
|
||||
|
||||
void onOpen(PsychicClientCallback handler);
|
||||
void onClose(PsychicClientCallback handler);
|
||||
PsychicHttpServer* addFilter(PsychicRequestFilterFunction fn);
|
||||
|
||||
PsychicHttpServer* addMiddleware(PsychicMiddleware* middleware);
|
||||
PsychicHttpServer* addMiddleware(PsychicMiddlewareCallback fn);
|
||||
void removeMiddleware(PsychicMiddleware *middleware);
|
||||
|
||||
static esp_err_t requestHandler(httpd_req_t* req);
|
||||
static esp_err_t notFoundHandler(httpd_req_t* req, httpd_err_code_t err);
|
||||
static esp_err_t defaultNotFoundHandler(PsychicRequest* request, PsychicResponse* response);
|
||||
static esp_err_t openCallback(httpd_handle_t hd, int sockfd);
|
||||
static void closeCallback(httpd_handle_t hd, int sockfd);
|
||||
|
||||
void onNotFound(PsychicHttpRequestCallback fn);
|
||||
void onOpen(PsychicClientCallback handler);
|
||||
void onClose(PsychicClientCallback handler);
|
||||
|
||||
PsychicStaticFileHandler* serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL);
|
||||
};
|
||||
|
||||
bool ON_STA_FILTER(PsychicRequest *request);
|
||||
bool ON_AP_FILTER(PsychicRequest *request);
|
||||
bool ON_STA_FILTER(PsychicRequest* request);
|
||||
bool ON_AP_FILTER(PsychicRequest* request);
|
||||
|
||||
// URI matching functions
|
||||
bool psychic_uri_match_simple(const char* uri1, const char* uri2, size_t len2);
|
||||
#define MATCH_SIMPLE psychic_uri_match_simple
|
||||
#define MATCH_WILDCARD httpd_uri_match_wildcard
|
||||
|
||||
#ifdef PSY_ENABLE_REGEX
|
||||
bool psychic_uri_match_regex(const char* uri1, const char* uri2, size_t len2);
|
||||
#define MATCH_REGEX psychic_uri_match_regex
|
||||
#endif
|
||||
|
||||
#endif // PsychicHttpServer_h
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
|
||||
|
||||
PsychicHttpsServer::PsychicHttpsServer() : PsychicHttpServer()
|
||||
PsychicHttpsServer::PsychicHttpsServer(uint16_t port) : PsychicHttpServer(port)
|
||||
{
|
||||
//for a SSL server
|
||||
// for a SSL server
|
||||
ssl_config = HTTPD_SSL_CONFIG_DEFAULT();
|
||||
ssl_config.httpd.open_fn = PsychicHttpServer::openCallback;
|
||||
ssl_config.httpd.close_fn = PsychicHttpServer::closeCallback;
|
||||
@@ -18,44 +18,53 @@ PsychicHttpsServer::PsychicHttpsServer() : PsychicHttpServer()
|
||||
// if we set it higher than 2 and use all the connections, we get lots of memory errors.
|
||||
// not to mention there is no heap left over for the program itself.
|
||||
ssl_config.httpd.max_open_sockets = 2;
|
||||
|
||||
setPort(port);
|
||||
}
|
||||
|
||||
PsychicHttpsServer::~PsychicHttpsServer() {}
|
||||
|
||||
esp_err_t PsychicHttpsServer::listen(uint16_t port, const char *cert, const char *private_key)
|
||||
void PsychicHttpsServer::setPort(uint16_t port)
|
||||
{
|
||||
this->_use_ssl = true;
|
||||
|
||||
this->ssl_config.port_secure = port;
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2)
|
||||
this->ssl_config.servercert = (uint8_t *)cert;
|
||||
this->ssl_config.servercert_len = strlen(cert)+1;
|
||||
#else
|
||||
this->ssl_config.cacert_pem = (uint8_t *)cert;
|
||||
this->ssl_config.cacert_len = strlen(cert)+1;
|
||||
#endif
|
||||
uint16_t PsychicHttpsServer::getPort()
|
||||
{
|
||||
return this->ssl_config.port_secure;
|
||||
}
|
||||
|
||||
this->ssl_config.prvtkey_pem = (uint8_t *)private_key;
|
||||
this->ssl_config.prvtkey_len = strlen(private_key)+1;
|
||||
void PsychicHttpsServer::setCertificate(const uint8_t* cert, size_t cert_size, const uint8_t* private_key, size_t private_key_size)
|
||||
{
|
||||
if (cert) {
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2)
|
||||
this->ssl_config.servercert = cert;
|
||||
this->ssl_config.servercert_len = cert_size;
|
||||
#else
|
||||
this->ssl_config.cacert_pem = cert;
|
||||
this->ssl_config.cacert_len = cert_size;
|
||||
#endif
|
||||
}
|
||||
|
||||
return this->_start();
|
||||
if (private_key) {
|
||||
this->ssl_config.prvtkey_pem = private_key;
|
||||
this->ssl_config.prvtkey_len = private_key_size;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t PsychicHttpsServer::_startServer()
|
||||
{
|
||||
if (this->_use_ssl)
|
||||
return httpd_ssl_start(&this->server, &this->ssl_config);
|
||||
else
|
||||
return httpd_start(&this->server, &this->config);
|
||||
return httpd_ssl_start(&this->server, &this->ssl_config);
|
||||
}
|
||||
|
||||
void PsychicHttpsServer::stop()
|
||||
esp_err_t PsychicHttpsServer::_stopServer()
|
||||
{
|
||||
if (this->_use_ssl)
|
||||
httpd_ssl_stop(this->server);
|
||||
else
|
||||
httpd_stop(this->server);
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2)
|
||||
return httpd_ssl_stop(this->server);
|
||||
#else
|
||||
httpd_ssl_stop(this->server);
|
||||
return ESP_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE
|
||||
@@ -1,38 +1,44 @@
|
||||
#ifndef PsychicHttpsServer_h
|
||||
#define PsychicHttpsServer_h
|
||||
#define PsychicHttpsServer_h
|
||||
|
||||
#include <sdkconfig.h>
|
||||
#include <sdkconfig.h>
|
||||
|
||||
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
|
||||
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
|
||||
|
||||
#include "PsychicCore.h"
|
||||
#include "PsychicHttpServer.h"
|
||||
#include <esp_https_server.h>
|
||||
#if !CONFIG_HTTPD_WS_SUPPORT
|
||||
#error PsychicHttpsServer cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration
|
||||
#endif
|
||||
#include "PsychicCore.h"
|
||||
#include "PsychicHttpServer.h"
|
||||
#include <esp_https_server.h>
|
||||
#if !CONFIG_HTTPD_WS_SUPPORT
|
||||
#error PsychicHttpsServer cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration
|
||||
#endif
|
||||
|
||||
#define PSY_ENABLE_SSL //you can use this define in your code to enable/disable these features
|
||||
#ifndef PSY_ENABLE_SSL
|
||||
#define PSY_ENABLE_SSL // you can use this define in your code to enable/disable these features
|
||||
#endif
|
||||
|
||||
class PsychicHttpsServer : public PsychicHttpServer
|
||||
{
|
||||
protected:
|
||||
bool _use_ssl = false;
|
||||
virtual esp_err_t _startServer() override final;
|
||||
virtual esp_err_t _stopServer() override final;
|
||||
|
||||
public:
|
||||
PsychicHttpsServer();
|
||||
PsychicHttpsServer(uint16_t port = 443);
|
||||
~PsychicHttpsServer();
|
||||
|
||||
httpd_ssl_config_t ssl_config;
|
||||
|
||||
using PsychicHttpServer::listen; //keep the regular version
|
||||
esp_err_t listen(uint16_t port, const char *cert, const char *private_key);
|
||||
|
||||
virtual esp_err_t _startServer() override final;
|
||||
virtual void stop() override final;
|
||||
// using PsychicHttpServer::listen; // keep the regular version
|
||||
virtual void setPort(uint16_t port) override final;
|
||||
virtual uint16_t getPort() override final;
|
||||
// Pointer to certificate data in PEM format
|
||||
void setCertificate(const char* cert, const char* private_key) { setCertificate((const uint8_t*)cert, strlen(cert) + 1, (const uint8_t*)private_key, private_key ? strlen(private_key) + 1 : 0); }
|
||||
// Pointer to certificate data in PEM or DER format. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in certSize and keySize.
|
||||
void setCertificate(const uint8_t* cert, size_t cert_size, const uint8_t* private_key, size_t private_key_size);
|
||||
};
|
||||
|
||||
#endif // PsychicHttpsServer_h
|
||||
|
||||
#else
|
||||
#warning ESP-IDF https server support not enabled.
|
||||
#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE
|
||||
|
||||
#endif // PsychicHttpsServer_h
|
||||
#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE
|
||||
@@ -1,28 +1,30 @@
|
||||
#include "PsychicJson.h"
|
||||
|
||||
#ifdef ARDUINOJSON_6_COMPATIBILITY
|
||||
PsychicJsonResponse::PsychicJsonResponse(PsychicRequest *request, bool isArray, size_t maxJsonBufferSize) :
|
||||
PsychicResponse(request),
|
||||
_jsonBuffer(maxJsonBufferSize)
|
||||
{
|
||||
setContentType(JSON_MIMETYPE);
|
||||
if (isArray)
|
||||
_root = _jsonBuffer.createNestedArray();
|
||||
else
|
||||
_root = _jsonBuffer.createNestedObject();
|
||||
}
|
||||
PsychicJsonResponse::PsychicJsonResponse(PsychicResponse* response, bool isArray, size_t maxJsonBufferSize) : __response(response),
|
||||
_jsonBuffer(maxJsonBufferSize)
|
||||
{
|
||||
response->setContentType(JSON_MIMETYPE);
|
||||
if (isArray)
|
||||
_root = _jsonBuffer.createNestedArray();
|
||||
else
|
||||
_root = _jsonBuffer.createNestedObject();
|
||||
}
|
||||
#else
|
||||
PsychicJsonResponse::PsychicJsonResponse(PsychicRequest *request, bool isArray) : PsychicResponse(request)
|
||||
{
|
||||
setContentType(JSON_MIMETYPE);
|
||||
if (isArray)
|
||||
_root = _jsonBuffer.add<JsonArray>();
|
||||
else
|
||||
_root = _jsonBuffer.add<JsonObject>();
|
||||
}
|
||||
PsychicJsonResponse::PsychicJsonResponse(PsychicResponse* response, bool isArray) : PsychicResponseDelegate(response)
|
||||
{
|
||||
setContentType(JSON_MIMETYPE);
|
||||
if (isArray)
|
||||
_root = _jsonBuffer.add<JsonArray>();
|
||||
else
|
||||
_root = _jsonBuffer.add<JsonObject>();
|
||||
}
|
||||
#endif
|
||||
|
||||
JsonVariant &PsychicJsonResponse::getRoot() { return _root; }
|
||||
JsonVariant& PsychicJsonResponse::getRoot()
|
||||
{
|
||||
return _root;
|
||||
}
|
||||
|
||||
size_t PsychicJsonResponse::getLength()
|
||||
{
|
||||
@@ -34,100 +36,93 @@ esp_err_t PsychicJsonResponse::send()
|
||||
esp_err_t err = ESP_OK;
|
||||
size_t length = getLength();
|
||||
size_t buffer_size;
|
||||
char *buffer;
|
||||
char* buffer;
|
||||
|
||||
//how big of a buffer do we want?
|
||||
// how big of a buffer do we want?
|
||||
if (length < JSON_BUFFER_SIZE)
|
||||
buffer_size = length+1;
|
||||
buffer_size = length + 1;
|
||||
else
|
||||
buffer_size = JSON_BUFFER_SIZE;
|
||||
|
||||
buffer = (char *)malloc(buffer_size);
|
||||
buffer = (char*)malloc(buffer_size);
|
||||
if (buffer == NULL) {
|
||||
httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
|
||||
return ESP_FAIL;
|
||||
return error(HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
|
||||
}
|
||||
|
||||
//send it in one shot or no?
|
||||
if (length < JSON_BUFFER_SIZE)
|
||||
{
|
||||
// send it in one shot or no?
|
||||
if (length < JSON_BUFFER_SIZE) {
|
||||
serializeJson(_root, buffer, buffer_size);
|
||||
|
||||
this->setContent((uint8_t *)buffer, length);
|
||||
this->setContentType(JSON_MIMETYPE);
|
||||
setContent((uint8_t*)buffer, length);
|
||||
setContentType(JSON_MIMETYPE);
|
||||
|
||||
err = PsychicResponse::send();
|
||||
}
|
||||
else
|
||||
{
|
||||
//helper class that acts as a stream to print chunked responses
|
||||
ChunkPrinter dest(this, (uint8_t *)buffer, buffer_size);
|
||||
err = send();
|
||||
} else {
|
||||
// helper class that acts as a stream to print chunked responses
|
||||
ChunkPrinter dest(_response, (uint8_t*)buffer, buffer_size);
|
||||
|
||||
//keep our headers
|
||||
this->sendHeaders();
|
||||
// keep our headers
|
||||
sendHeaders();
|
||||
|
||||
serializeJson(_root, dest);
|
||||
|
||||
//send the last bits
|
||||
// send the last bits
|
||||
dest.flush();
|
||||
|
||||
//done with our chunked response too
|
||||
err = this->finishChunking();
|
||||
// done with our chunked response too
|
||||
err = finishChunking();
|
||||
}
|
||||
|
||||
//let the buffer go
|
||||
// let the buffer go
|
||||
free(buffer);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef ARDUINOJSON_6_COMPATIBILITY
|
||||
PsychicJsonHandler::PsychicJsonHandler(size_t maxJsonBufferSize) :
|
||||
_onRequest(NULL),
|
||||
_maxJsonBufferSize(maxJsonBufferSize)
|
||||
{};
|
||||
PsychicJsonHandler::PsychicJsonHandler(size_t maxJsonBufferSize) : _onRequest(NULL),
|
||||
_maxJsonBufferSize(maxJsonBufferSize) {};
|
||||
|
||||
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize) :
|
||||
_onRequest(onRequest),
|
||||
_maxJsonBufferSize(maxJsonBufferSize)
|
||||
{}
|
||||
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize) : _onRequest(onRequest),
|
||||
_maxJsonBufferSize(maxJsonBufferSize)
|
||||
{
|
||||
}
|
||||
#else
|
||||
PsychicJsonHandler::PsychicJsonHandler() :
|
||||
_onRequest(NULL)
|
||||
{};
|
||||
PsychicJsonHandler::PsychicJsonHandler() : _onRequest(NULL) {};
|
||||
|
||||
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest) :
|
||||
_onRequest(onRequest)
|
||||
{}
|
||||
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest) : _onRequest(onRequest)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
void PsychicJsonHandler::onRequest(PsychicJsonRequestCallback fn) { _onRequest = fn; }
|
||||
|
||||
esp_err_t PsychicJsonHandler::handleRequest(PsychicRequest *request)
|
||||
void PsychicJsonHandler::onRequest(PsychicJsonRequestCallback fn)
|
||||
{
|
||||
//process basic stuff
|
||||
PsychicWebHandler::handleRequest(request);
|
||||
_onRequest = fn;
|
||||
}
|
||||
|
||||
if (_onRequest)
|
||||
{
|
||||
#ifdef ARDUINOJSON_6_COMPATIBILITY
|
||||
DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize);
|
||||
DeserializationError error = deserializeJson(jsonBuffer, request->body());
|
||||
if (error)
|
||||
return request->reply(400);
|
||||
esp_err_t PsychicJsonHandler::handleRequest(PsychicRequest* request, PsychicResponse* response)
|
||||
{
|
||||
// process basic stuff
|
||||
PsychicWebHandler::handleRequest(request, response);
|
||||
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
#else
|
||||
JsonDocument jsonBuffer;
|
||||
DeserializationError error = deserializeJson(jsonBuffer, request->body());
|
||||
if (error)
|
||||
return request->reply(400);
|
||||
if (_onRequest) {
|
||||
#ifdef ARDUINOJSON_6_COMPATIBILITY
|
||||
DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize);
|
||||
DeserializationError error = deserializeJson(jsonBuffer, request->body());
|
||||
if (error)
|
||||
return response->send(400);
|
||||
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
#endif
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
#else
|
||||
JsonDocument jsonBuffer;
|
||||
DeserializationError error = deserializeJson(jsonBuffer, request->body());
|
||||
if (error)
|
||||
return response->send(400);
|
||||
|
||||
return _onRequest(request, json);
|
||||
}
|
||||
else
|
||||
return request->reply(500);
|
||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||
#endif
|
||||
|
||||
return _onRequest(request, response, json);
|
||||
} else
|
||||
return response->send(500);
|
||||
}
|
||||
@@ -8,9 +8,9 @@
|
||||
#ifndef PSYCHIC_JSON_H_
|
||||
#define PSYCHIC_JSON_H_
|
||||
|
||||
#include "ChunkPrinter.h"
|
||||
#include "PsychicRequest.h"
|
||||
#include "PsychicWebHandler.h"
|
||||
#include "ChunkPrinter.h"
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
@@ -20,70 +20,71 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef JSON_BUFFER_SIZE
|
||||
#define JSON_BUFFER_SIZE 4*1024
|
||||
#define JSON_BUFFER_SIZE 4 * 1024
|
||||
#endif
|
||||
|
||||
constexpr const char *JSON_MIMETYPE = "application/json";
|
||||
constexpr const char* JSON_MIMETYPE = "application/json";
|
||||
|
||||
/*
|
||||
* Json Response
|
||||
* */
|
||||
|
||||
class PsychicJsonResponse : public PsychicResponse
|
||||
class PsychicJsonResponse : public PsychicResponseDelegate
|
||||
{
|
||||
protected:
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
DynamicJsonBuffer _jsonBuffer;
|
||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||
DynamicJsonDocument _jsonBuffer;
|
||||
#else
|
||||
JsonDocument _jsonBuffer;
|
||||
#endif
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
DynamicJsonBuffer _jsonBuffer;
|
||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||
DynamicJsonDocument _jsonBuffer;
|
||||
#else
|
||||
JsonDocument _jsonBuffer;
|
||||
#endif
|
||||
|
||||
JsonVariant _root;
|
||||
size_t _contentLength;
|
||||
|
||||
public:
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
PsychicJsonResponse(PsychicRequest *request, bool isArray = false);
|
||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||
PsychicJsonResponse(PsychicRequest *request, bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||
#else
|
||||
PsychicJsonResponse(PsychicRequest *request, bool isArray = false);
|
||||
#endif
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
PsychicJsonResponse(PsychicResponse* response, bool isArray = false);
|
||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||
PsychicJsonResponse(PsychicResponse* response, bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||
#else
|
||||
PsychicJsonResponse(PsychicResponse* response, bool isArray = false);
|
||||
#endif
|
||||
|
||||
~PsychicJsonResponse() {}
|
||||
~PsychicJsonResponse()
|
||||
{
|
||||
}
|
||||
|
||||
JsonVariant &getRoot();
|
||||
JsonVariant& getRoot();
|
||||
size_t getLength();
|
||||
|
||||
virtual esp_err_t send() override;
|
||||
|
||||
esp_err_t send();
|
||||
};
|
||||
|
||||
class PsychicJsonHandler : public PsychicWebHandler
|
||||
{
|
||||
protected:
|
||||
PsychicJsonRequestCallback _onRequest;
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
const size_t _maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE;
|
||||
#endif
|
||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||
const size_t _maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE;
|
||||
#endif
|
||||
|
||||
public:
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
PsychicJsonHandler();
|
||||
PsychicJsonHandler(PsychicJsonRequestCallback onRequest);
|
||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||
PsychicJsonHandler(size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||
PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||
#else
|
||||
PsychicJsonHandler();
|
||||
PsychicJsonHandler(PsychicJsonRequestCallback onRequest);
|
||||
#endif
|
||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||
PsychicJsonHandler();
|
||||
PsychicJsonHandler(PsychicJsonRequestCallback onRequest);
|
||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||
PsychicJsonHandler(size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||
PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||
#else
|
||||
PsychicJsonHandler();
|
||||
PsychicJsonHandler(PsychicJsonRequestCallback onRequest);
|
||||
#endif
|
||||
|
||||
void onRequest(PsychicJsonRequestCallback fn);
|
||||
virtual esp_err_t handleRequest(PsychicRequest *request) override;
|
||||
virtual esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
6
lib/PsychicHttp/src/PsychicMiddleware.cpp
Normal file
6
lib/PsychicHttp/src/PsychicMiddleware.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#include "PsychicMiddleware.h"
|
||||
|
||||
esp_err_t PsychicMiddlewareFunction::run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next)
|
||||
{
|
||||
return _fn(request, request->response(), next);
|
||||
}
|
||||
37
lib/PsychicHttp/src/PsychicMiddleware.h
Normal file
37
lib/PsychicHttp/src/PsychicMiddleware.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef PsychicMiddleware_h
|
||||
#define PsychicMiddleware_h
|
||||
|
||||
#include "PsychicCore.h"
|
||||
#include "PsychicRequest.h"
|
||||
#include "PsychicResponse.h"
|
||||
|
||||
class PsychicMiddlewareChain;
|
||||
/*
|
||||
* PsychicMiddleware :: fancy callback wrapper for handling requests and responses.
|
||||
* */
|
||||
|
||||
class PsychicMiddleware
|
||||
{
|
||||
public:
|
||||
virtual ~PsychicMiddleware() {}
|
||||
virtual esp_err_t run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next)
|
||||
{
|
||||
return next();
|
||||
}
|
||||
|
||||
private:
|
||||
friend PsychicMiddlewareChain;
|
||||
bool _freeOnRemoval = false;
|
||||
};
|
||||
|
||||
class PsychicMiddlewareFunction : public PsychicMiddleware
|
||||
{
|
||||
public:
|
||||
PsychicMiddlewareFunction(PsychicMiddlewareCallback fn) : _fn(fn) { assert(_fn); }
|
||||
esp_err_t run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next) override;
|
||||
|
||||
protected:
|
||||
PsychicMiddlewareCallback _fn;
|
||||
};
|
||||
|
||||
#endif
|
||||
47
lib/PsychicHttp/src/PsychicMiddlewareChain.cpp
Normal file
47
lib/PsychicHttp/src/PsychicMiddlewareChain.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "PsychicMiddlewareChain.h"
|
||||
|
||||
PsychicMiddlewareChain::~PsychicMiddlewareChain()
|
||||
{
|
||||
for (auto middleware : _middleware)
|
||||
if (middleware->_freeOnRemoval)
|
||||
delete middleware;
|
||||
_middleware.clear();
|
||||
}
|
||||
|
||||
void PsychicMiddlewareChain::addMiddleware(PsychicMiddleware* middleware)
|
||||
{
|
||||
_middleware.push_back(middleware);
|
||||
}
|
||||
|
||||
void PsychicMiddlewareChain::addMiddleware(PsychicMiddlewareCallback fn)
|
||||
{
|
||||
PsychicMiddlewareFunction* closure = new PsychicMiddlewareFunction(fn);
|
||||
closure->_freeOnRemoval = true;
|
||||
_middleware.push_back(closure);
|
||||
}
|
||||
|
||||
void PsychicMiddlewareChain::removeMiddleware(PsychicMiddleware* middleware)
|
||||
{
|
||||
_middleware.remove(middleware);
|
||||
if (middleware->_freeOnRemoval)
|
||||
delete middleware;
|
||||
}
|
||||
|
||||
esp_err_t PsychicMiddlewareChain::runChain(PsychicRequest* request, PsychicMiddlewareNext finalizer)
|
||||
{
|
||||
if (_middleware.size() == 0)
|
||||
return finalizer();
|
||||
|
||||
PsychicMiddlewareNext next;
|
||||
std::list<PsychicMiddleware*>::iterator it = _middleware.begin();
|
||||
|
||||
next = [this, &next, &it, request, finalizer]() {
|
||||
if (it == _middleware.end())
|
||||
return finalizer();
|
||||
PsychicMiddleware* m = *it;
|
||||
it++;
|
||||
return m->run(request, request->response(), next);
|
||||
};
|
||||
|
||||
return next();
|
||||
}
|
||||
28
lib/PsychicHttp/src/PsychicMiddlewareChain.h
Normal file
28
lib/PsychicHttp/src/PsychicMiddlewareChain.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef PsychicMiddlewareChain_h
|
||||
#define PsychicMiddlewareChain_h
|
||||
|
||||
#include "PsychicCore.h"
|
||||
#include "PsychicMiddleware.h"
|
||||
#include "PsychicRequest.h"
|
||||
#include "PsychicResponse.h"
|
||||
|
||||
/*
|
||||
* PsychicMiddlewareChain - handle tracking and executing our chain of middleware objects
|
||||
* */
|
||||
|
||||
class PsychicMiddlewareChain
|
||||
{
|
||||
public:
|
||||
virtual ~PsychicMiddlewareChain();
|
||||
|
||||
void addMiddleware(PsychicMiddleware* middleware);
|
||||
void addMiddleware(PsychicMiddlewareCallback fn);
|
||||
void removeMiddleware(PsychicMiddleware* middleware);
|
||||
|
||||
esp_err_t runChain(PsychicRequest* request, PsychicMiddlewareNext finalizer);
|
||||
|
||||
protected:
|
||||
std::list<PsychicMiddleware*> _middleware;
|
||||
};
|
||||
|
||||
#endif
|
||||
170
lib/PsychicHttp/src/PsychicMiddlewares.cpp
Normal file
170
lib/PsychicHttp/src/PsychicMiddlewares.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
#include "PsychicMiddlewares.h"
|
||||
|
||||
void LoggingMiddleware::setOutput(Print &output) {
|
||||
_out = &output;
|
||||
}
|
||||
|
||||
esp_err_t LoggingMiddleware::run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next)
|
||||
{
|
||||
_out->print("* Connection from ");
|
||||
_out->print(request->client()->remoteIP().toString());
|
||||
_out->print(":");
|
||||
_out->println(request->client()->remotePort());
|
||||
|
||||
_out->print("> ");
|
||||
_out->print(request->methodStr());
|
||||
_out->print(" ");
|
||||
_out->print(request->uri());
|
||||
_out->print(" ");
|
||||
_out->println(request->version());
|
||||
|
||||
// TODO: find a way to collect all headers
|
||||
// int n = request->headerCount();
|
||||
// for (int i = 0; i < n; i++) {
|
||||
// String v = server.header(i);
|
||||
// if (!v.isEmpty()) {
|
||||
// // because these 2 are always there, eventually empty: "Authorization", "If-None-Match"
|
||||
// _out->print("< ");
|
||||
// _out->print(server.headerName(i));
|
||||
// _out->print(": ");
|
||||
// _out->println(server.header(i));
|
||||
// }
|
||||
// }
|
||||
|
||||
_out->println(">");
|
||||
|
||||
esp_err_t ret = next();
|
||||
|
||||
if (ret != HTTPD_404_NOT_FOUND) {
|
||||
_out->println("* Processed!");
|
||||
|
||||
_out->print("< ");
|
||||
_out->print(response->version());
|
||||
_out->print(" ");
|
||||
_out->print(response->getCode());
|
||||
_out->print(" ");
|
||||
_out->println(http_status_reason(response->getCode()));
|
||||
|
||||
// iterate over response->headers()
|
||||
std::list<HTTPHeader>::iterator it = response->headers().begin();
|
||||
while (it != response->headers().end()) {
|
||||
HTTPHeader h = *it;
|
||||
_out->print("< ");
|
||||
_out->print(h.field);
|
||||
_out->print(": ");
|
||||
_out->println(h.value);
|
||||
it++;
|
||||
}
|
||||
|
||||
_out->println("<");
|
||||
|
||||
} else {
|
||||
_out->println("* Not processed!");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
AuthenticationMiddleware& AuthenticationMiddleware::setUsername(const char* username)
|
||||
{
|
||||
_username = username;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuthenticationMiddleware& AuthenticationMiddleware::setPassword(const char* password)
|
||||
{
|
||||
_password = password;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuthenticationMiddleware& AuthenticationMiddleware::setRealm(const char* realm)
|
||||
{
|
||||
_realm = realm;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuthenticationMiddleware& AuthenticationMiddleware::setAuthMethod(HTTPAuthMethod method)
|
||||
{
|
||||
_method = method;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AuthenticationMiddleware& AuthenticationMiddleware::setAuthFailureMessage(const char* message)
|
||||
{
|
||||
_authFailMsg = message;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool AuthenticationMiddleware::isAllowed(PsychicRequest* request) const
|
||||
{
|
||||
if (!_username.isEmpty() && !_password.isEmpty()) {
|
||||
return request->authenticate(_username.c_str(), _password.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t AuthenticationMiddleware::run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next)
|
||||
{
|
||||
bool authenticationRequired = false;
|
||||
|
||||
if (!_username.isEmpty() && !_password.isEmpty()) {
|
||||
authenticationRequired = !request->authenticate(_username.c_str(), _password.c_str());
|
||||
}
|
||||
|
||||
if (authenticationRequired) {
|
||||
return request->requestAuthentication(_method, _realm.c_str(), _authFailMsg.c_str());
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
CorsMiddleware& CorsMiddleware::setOrigin(const char* origin)
|
||||
{
|
||||
_origin = origin;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CorsMiddleware& CorsMiddleware::setMethods(const char* methods)
|
||||
{
|
||||
_methods = methods;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CorsMiddleware& CorsMiddleware::setHeaders(const char* headers)
|
||||
{
|
||||
_headers = headers;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CorsMiddleware& CorsMiddleware::setAllowCredentials(bool credentials)
|
||||
{
|
||||
_credentials = credentials;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CorsMiddleware& CorsMiddleware::setMaxAge(uint32_t seconds)
|
||||
{
|
||||
_maxAge = seconds;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void CorsMiddleware::addCORSHeaders(PsychicResponse* response)
|
||||
{
|
||||
response->addHeader("Access-Control-Allow-Origin", _origin.c_str());
|
||||
response->addHeader("Access-Control-Allow-Methods", _methods.c_str());
|
||||
response->addHeader("Access-Control-Allow-Headers", _headers.c_str());
|
||||
response->addHeader("Access-Control-Allow-Credentials", _credentials ? "true" : "false");
|
||||
response->addHeader("Access-Control-Max-Age", String(_maxAge).c_str());
|
||||
}
|
||||
|
||||
esp_err_t CorsMiddleware::run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next)
|
||||
{
|
||||
if (request->hasHeader("Origin")) {
|
||||
addCORSHeaders(response);
|
||||
if (request->method() == HTTP_OPTIONS) {
|
||||
return response->send(200);
|
||||
}
|
||||
}
|
||||
return next();
|
||||
}
|
||||
78
lib/PsychicHttp/src/PsychicMiddlewares.h
Normal file
78
lib/PsychicHttp/src/PsychicMiddlewares.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef PsychicMiddlewares_h
|
||||
#define PsychicMiddlewares_h
|
||||
|
||||
#include "PsychicMiddleware.h"
|
||||
|
||||
#include <Stream.h>
|
||||
#include <http_status.h>
|
||||
|
||||
// curl-like logging middleware
|
||||
class LoggingMiddleware : public PsychicMiddleware
|
||||
{
|
||||
public:
|
||||
void setOutput(Print& output);
|
||||
|
||||
esp_err_t run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next) override;
|
||||
|
||||
private:
|
||||
Print* _out;
|
||||
};
|
||||
|
||||
class AuthenticationMiddleware : public PsychicMiddleware
|
||||
{
|
||||
public:
|
||||
AuthenticationMiddleware& setUsername(const char* username);
|
||||
AuthenticationMiddleware& setPassword(const char* password);
|
||||
|
||||
AuthenticationMiddleware& setRealm(const char* realm);
|
||||
AuthenticationMiddleware& setAuthMethod(HTTPAuthMethod method);
|
||||
AuthenticationMiddleware& setAuthFailureMessage(const char* message);
|
||||
|
||||
const String& getUsername() const { return _username; }
|
||||
const String& getPassword() const { return _password; }
|
||||
|
||||
const String& getRealm() const { return _realm; }
|
||||
HTTPAuthMethod getAuthMethod() const { return _method; }
|
||||
const String& getAuthFailureMessage() const { return _authFailMsg; }
|
||||
|
||||
bool isAllowed(PsychicRequest* request) const;
|
||||
|
||||
esp_err_t run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next) override;
|
||||
|
||||
private:
|
||||
String _username;
|
||||
String _password;
|
||||
|
||||
String _realm;
|
||||
HTTPAuthMethod _method = BASIC_AUTH;
|
||||
String _authFailMsg;
|
||||
};
|
||||
|
||||
class CorsMiddleware : public PsychicMiddleware
|
||||
{
|
||||
public:
|
||||
CorsMiddleware& setOrigin(const char* origin);
|
||||
CorsMiddleware& setMethods(const char* methods);
|
||||
CorsMiddleware& setHeaders(const char* headers);
|
||||
CorsMiddleware& setAllowCredentials(bool credentials);
|
||||
CorsMiddleware& setMaxAge(uint32_t seconds);
|
||||
|
||||
const String& getOrigin() const { return _origin; }
|
||||
const String& getMethods() const { return _methods; }
|
||||
const String& getHeaders() const { return _headers; }
|
||||
bool getAllowCredentials() const { return _credentials; }
|
||||
uint32_t getMaxAge() const { return _maxAge; }
|
||||
|
||||
void addCORSHeaders(PsychicResponse* response);
|
||||
|
||||
esp_err_t run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next) override;
|
||||
|
||||
private:
|
||||
String _origin = "*";
|
||||
String _methods = "*";
|
||||
String _headers = "*";
|
||||
bool _credentials = true;
|
||||
uint32_t _maxAge = 86400;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,91 +1,122 @@
|
||||
#include "PsychicRequest.h"
|
||||
#include "http_status.h"
|
||||
#include "MultipartProcessor.h"
|
||||
#include "PsychicHttpServer.h"
|
||||
#include "http_status.h"
|
||||
|
||||
|
||||
PsychicRequest::PsychicRequest(PsychicHttpServer *server, httpd_req_t *req) :
|
||||
_server(server),
|
||||
_req(req),
|
||||
_method(HTTP_GET),
|
||||
_query(""),
|
||||
_body(""),
|
||||
_tempObject(NULL)
|
||||
PsychicRequest::PsychicRequest(PsychicHttpServer* server, httpd_req_t* req) : _server(server),
|
||||
_req(req),
|
||||
_endpoint(nullptr),
|
||||
_method(HTTP_GET),
|
||||
_uri(""),
|
||||
_query(""),
|
||||
_body(""),
|
||||
_tempObject(nullptr)
|
||||
{
|
||||
//load up our client.
|
||||
// load up our client.
|
||||
this->_client = server->getClient(req);
|
||||
|
||||
//handle our session data
|
||||
// handle our session data
|
||||
if (req->sess_ctx != NULL)
|
||||
this->_session = (SessionData *)req->sess_ctx;
|
||||
else
|
||||
{
|
||||
this->_session = (SessionData*)req->sess_ctx;
|
||||
else {
|
||||
this->_session = new SessionData();
|
||||
req->sess_ctx = this->_session;
|
||||
}
|
||||
|
||||
//callback for freeing the session later
|
||||
// callback for freeing the session later
|
||||
req->free_ctx = this->freeSession;
|
||||
|
||||
//load up some data
|
||||
this->_uri = String(this->_req->uri);
|
||||
// load and parse our uri.
|
||||
this->_setUri(this->_req->uri);
|
||||
|
||||
_response = new PsychicResponse(this);
|
||||
}
|
||||
|
||||
PsychicRequest::~PsychicRequest()
|
||||
{
|
||||
//temorary user object
|
||||
// temorary user object
|
||||
if (_tempObject != NULL)
|
||||
free(_tempObject);
|
||||
|
||||
//our web parameters
|
||||
for (auto *param : _params)
|
||||
delete(param);
|
||||
// our web parameters
|
||||
for (auto* param : _params)
|
||||
delete (param);
|
||||
_params.clear();
|
||||
|
||||
delete _response;
|
||||
}
|
||||
|
||||
void PsychicRequest::freeSession(void *ctx)
|
||||
void PsychicRequest::freeSession(void* ctx)
|
||||
{
|
||||
if (ctx != NULL)
|
||||
{
|
||||
SessionData *session = (SessionData*)ctx;
|
||||
if (ctx != NULL) {
|
||||
SessionData* session = (SessionData*)ctx;
|
||||
delete session;
|
||||
}
|
||||
}
|
||||
|
||||
PsychicHttpServer * PsychicRequest::server() {
|
||||
PsychicHttpServer* PsychicRequest::server()
|
||||
{
|
||||
return _server;
|
||||
}
|
||||
|
||||
httpd_req_t * PsychicRequest::request() {
|
||||
httpd_req_t* PsychicRequest::request()
|
||||
{
|
||||
return _req;
|
||||
}
|
||||
|
||||
PsychicClient * PsychicRequest::client() {
|
||||
PsychicClient* PsychicRequest::client()
|
||||
{
|
||||
return _client;
|
||||
}
|
||||
|
||||
PsychicEndpoint* PsychicRequest::endpoint()
|
||||
{
|
||||
return _endpoint;
|
||||
}
|
||||
|
||||
void PsychicRequest::setEndpoint(PsychicEndpoint* endpoint)
|
||||
{
|
||||
_endpoint = endpoint;
|
||||
}
|
||||
|
||||
#ifdef PSY_ENABLE_REGEX
|
||||
bool PsychicRequest::getRegexMatches(std::smatch& matches, bool use_full_uri)
|
||||
{
|
||||
if (_endpoint != nullptr) {
|
||||
std::regex pattern(_endpoint->uri().c_str());
|
||||
std::string s(this->path().c_str());
|
||||
if (use_full_uri)
|
||||
s = this->uri().c_str();
|
||||
|
||||
return std::regex_search(s, matches, pattern);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
const String PsychicRequest::getFilename()
|
||||
{
|
||||
//parse the content-disposition header
|
||||
if (this->hasHeader("Content-Disposition"))
|
||||
{
|
||||
// parse the content-disposition header
|
||||
if (this->hasHeader("Content-Disposition")) {
|
||||
ContentDisposition cd = this->getContentDisposition();
|
||||
if (cd.filename != "")
|
||||
return cd.filename;
|
||||
}
|
||||
|
||||
//fall back to passed in query string
|
||||
PsychicWebParameter *param = getParam("_filename");
|
||||
// fall back to passed in query string
|
||||
PsychicWebParameter* param = getParam("_filename");
|
||||
if (param != NULL)
|
||||
return param->name();
|
||||
|
||||
//fall back to parsing it from url (useful for wildcard uploads)
|
||||
// fall back to parsing it from url (useful for wildcard uploads)
|
||||
String uri = this->uri();
|
||||
int filenameStart = uri.lastIndexOf('/') + 1;
|
||||
String filename = uri.substring(filenameStart);
|
||||
if (filename != "")
|
||||
return filename;
|
||||
|
||||
//finally, unknown.
|
||||
// finally, unknown.
|
||||
ESP_LOGE(PH_TAG, "Did not get a valid filename from the upload.");
|
||||
return "unknown.txt";
|
||||
}
|
||||
@@ -103,21 +134,19 @@ const ContentDisposition PsychicRequest::getContentDisposition()
|
||||
cd.disposition = ATTACHMENT;
|
||||
else if (header.indexOf("inline") == 0)
|
||||
cd.disposition = INLINE;
|
||||
else
|
||||
else
|
||||
cd.disposition = NONE;
|
||||
|
||||
start = header.indexOf("filename=");
|
||||
if (start)
|
||||
{
|
||||
end = header.indexOf('"', start+10);
|
||||
cd.filename = header.substring(start+10, end-1);
|
||||
if (start) {
|
||||
end = header.indexOf('"', start + 10);
|
||||
cd.filename = header.substring(start + 10, end - 1);
|
||||
}
|
||||
|
||||
start = header.indexOf("name=");
|
||||
if (start)
|
||||
{
|
||||
end = header.indexOf('"', start+6);
|
||||
cd.name = header.substring(start+6, end-1);
|
||||
if (start) {
|
||||
end = header.indexOf('"', start + 6);
|
||||
cd.name = header.substring(start + 6, end - 1);
|
||||
}
|
||||
|
||||
return cd;
|
||||
@@ -125,16 +154,23 @@ const ContentDisposition PsychicRequest::getContentDisposition()
|
||||
|
||||
esp_err_t PsychicRequest::loadBody()
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
if (_bodyParsed != ESP_ERR_NOT_FINISHED)
|
||||
return _bodyParsed;
|
||||
|
||||
// quick size check.
|
||||
if (contentLength() > server()->maxRequestBodySize) {
|
||||
ESP_LOGE(PH_TAG, "Body size larger than maxRequestBodySize");
|
||||
return _bodyParsed = ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
this->_body = String();
|
||||
|
||||
size_t remaining = this->_req->content_len;
|
||||
size_t actuallyReceived = 0;
|
||||
char *buf = (char *)malloc(remaining + 1);
|
||||
char* buf = (char*)malloc(remaining + 1);
|
||||
if (buf == NULL) {
|
||||
ESP_LOGE(PH_TAG, "Failed to allocate memory for body");
|
||||
return ESP_FAIL;
|
||||
return _bodyParsed = ESP_FAIL;
|
||||
}
|
||||
|
||||
while (remaining > 0) {
|
||||
@@ -142,10 +178,9 @@ esp_err_t PsychicRequest::loadBody()
|
||||
|
||||
if (received == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||
continue;
|
||||
}
|
||||
else if (received == HTTPD_SOCK_ERR_FAIL) {
|
||||
} else if (received == HTTPD_SOCK_ERR_FAIL) {
|
||||
ESP_LOGE(PH_TAG, "Failed to receive data.");
|
||||
err = ESP_FAIL;
|
||||
_bodyParsed = ESP_FAIL;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -156,30 +191,38 @@ esp_err_t PsychicRequest::loadBody()
|
||||
buf[actuallyReceived] = '\0';
|
||||
this->_body = String(buf);
|
||||
free(buf);
|
||||
return err;
|
||||
|
||||
_bodyParsed = ESP_OK;
|
||||
|
||||
return _bodyParsed;
|
||||
}
|
||||
|
||||
http_method PsychicRequest::method() {
|
||||
http_method PsychicRequest::method()
|
||||
{
|
||||
return (http_method)this->_req->method;
|
||||
}
|
||||
|
||||
const String PsychicRequest::methodStr() {
|
||||
const String PsychicRequest::methodStr()
|
||||
{
|
||||
return String(http_method_str((http_method)this->_req->method));
|
||||
}
|
||||
|
||||
const String PsychicRequest::path() {
|
||||
const String PsychicRequest::path()
|
||||
{
|
||||
int index = _uri.indexOf("?");
|
||||
if(index == -1)
|
||||
if (index == -1)
|
||||
return _uri;
|
||||
else
|
||||
return _uri.substring(0, index);
|
||||
return _uri.substring(0, index);
|
||||
}
|
||||
|
||||
const String& PsychicRequest::uri() {
|
||||
const String& PsychicRequest::uri()
|
||||
{
|
||||
return this->_uri;
|
||||
}
|
||||
|
||||
const String& PsychicRequest::query() {
|
||||
const String& PsychicRequest::query()
|
||||
{
|
||||
return this->_query;
|
||||
}
|
||||
|
||||
@@ -188,35 +231,36 @@ const String& PsychicRequest::query() {
|
||||
// {
|
||||
// }
|
||||
|
||||
const String PsychicRequest::header(const char *name)
|
||||
const String PsychicRequest::header(const char* name)
|
||||
{
|
||||
size_t header_len = httpd_req_get_hdr_value_len(this->_req, name);
|
||||
|
||||
//if we've got one, allocated it and load it
|
||||
if (header_len)
|
||||
{
|
||||
char header[header_len+1];
|
||||
// if we've got one, allocated it and load it
|
||||
if (header_len) {
|
||||
char header[header_len + 1];
|
||||
httpd_req_get_hdr_value_str(this->_req, name, header, sizeof(header));
|
||||
return String(header);
|
||||
}
|
||||
else
|
||||
} else
|
||||
return "";
|
||||
}
|
||||
|
||||
bool PsychicRequest::hasHeader(const char *name)
|
||||
bool PsychicRequest::hasHeader(const char* name)
|
||||
{
|
||||
return httpd_req_get_hdr_value_len(this->_req, name) > 0;
|
||||
}
|
||||
|
||||
const String PsychicRequest::host() {
|
||||
const String PsychicRequest::host()
|
||||
{
|
||||
return this->header("Host");
|
||||
}
|
||||
|
||||
const String PsychicRequest::contentType() {
|
||||
const String PsychicRequest::contentType()
|
||||
{
|
||||
return header("Content-Type");
|
||||
}
|
||||
|
||||
size_t PsychicRequest::contentLength() {
|
||||
size_t PsychicRequest::contentLength()
|
||||
{
|
||||
return this->_req->content_len;
|
||||
}
|
||||
|
||||
@@ -232,90 +276,99 @@ bool PsychicRequest::isMultipart()
|
||||
return (this->contentType().indexOf("multipart/form-data") >= 0);
|
||||
}
|
||||
|
||||
esp_err_t PsychicRequest::redirect(const char *url)
|
||||
bool PsychicRequest::hasCookie(const char* key, size_t* size)
|
||||
{
|
||||
PsychicResponse response(this);
|
||||
response.setCode(301);
|
||||
response.addHeader("Location", url);
|
||||
char buffer;
|
||||
|
||||
return response.send();
|
||||
// this keeps our size for the user.
|
||||
if (size != nullptr) {
|
||||
*size = 1;
|
||||
return getCookie(key, &buffer, size) != ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
// this just checks that it exists.
|
||||
else {
|
||||
size_t mysize = 1;
|
||||
return getCookie(key, &buffer, &mysize) != ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
bool PsychicRequest::hasCookie(const char *key)
|
||||
esp_err_t PsychicRequest::getCookie(const char* key, char* buffer, size_t* size)
|
||||
{
|
||||
char cookie[MAX_COOKIE_SIZE];
|
||||
size_t cookieSize = MAX_COOKIE_SIZE;
|
||||
esp_err_t err = httpd_req_get_cookie_val(this->_req, key, cookie, &cookieSize);
|
||||
|
||||
//did we get anything?
|
||||
if (err == ESP_OK)
|
||||
return true;
|
||||
else if (err == ESP_ERR_HTTPD_RESULT_TRUNC)
|
||||
ESP_LOGE(PH_TAG, "cookie too large (%d bytes).\n", cookieSize);
|
||||
|
||||
return false;
|
||||
return httpd_req_get_cookie_val(this->_req, key, buffer, size);
|
||||
}
|
||||
|
||||
const String PsychicRequest::getCookie(const char *key)
|
||||
String PsychicRequest::getCookie(const char* key)
|
||||
{
|
||||
char cookie[MAX_COOKIE_SIZE];
|
||||
size_t cookieSize = MAX_COOKIE_SIZE;
|
||||
esp_err_t err = httpd_req_get_cookie_val(this->_req, key, cookie, &cookieSize);
|
||||
String cookie = "";
|
||||
|
||||
//did we get anything?
|
||||
// how big is our cookie?
|
||||
size_t size;
|
||||
if (!hasCookie("counter", &size))
|
||||
return cookie;
|
||||
|
||||
// allocate cookie buffer... keep it on the stack
|
||||
char buf[size];
|
||||
|
||||
// load it up.
|
||||
esp_err_t err = getCookie(key, buf, &size);
|
||||
if (err == ESP_OK)
|
||||
return String(cookie);
|
||||
else
|
||||
return "";
|
||||
cookie.concat(buf);
|
||||
|
||||
return cookie;
|
||||
}
|
||||
|
||||
void PsychicRequest::replaceResponse(PsychicResponse* response)
|
||||
{
|
||||
delete _response;
|
||||
_response = response;
|
||||
}
|
||||
|
||||
void PsychicRequest::addResponseHeader(const char* key, const char* value)
|
||||
{
|
||||
_response->addHeader(key, value);
|
||||
}
|
||||
|
||||
std::list<HTTPHeader>& PsychicRequest::getResponseHeaders()
|
||||
{
|
||||
return _response->headers();
|
||||
}
|
||||
|
||||
void PsychicRequest::loadParams()
|
||||
{
|
||||
//did we get a query string?
|
||||
size_t query_len = httpd_req_get_url_query_len(_req);
|
||||
if (query_len)
|
||||
{
|
||||
char query[query_len+1];
|
||||
httpd_req_get_url_query_str(_req, query, sizeof(query));
|
||||
_query.concat(query);
|
||||
if (_paramsParsed != ESP_ERR_NOT_FINISHED)
|
||||
return;
|
||||
|
||||
//parse them.
|
||||
// convenience shortcut to allow calling loadParams()
|
||||
if (_bodyParsed == ESP_ERR_NOT_FINISHED)
|
||||
loadBody();
|
||||
|
||||
// various form data as parameters
|
||||
if (this->method() == HTTP_POST) {
|
||||
if (this->contentType().startsWith("application/x-www-form-urlencoded"))
|
||||
_addParams(_body, true);
|
||||
|
||||
if (this->isMultipart()) {
|
||||
MultipartProcessor mpp(this);
|
||||
_paramsParsed = mpp.process(_body.c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_paramsParsed = ESP_OK;
|
||||
}
|
||||
|
||||
void PsychicRequest::_setUri(const char* uri)
|
||||
{
|
||||
// save it
|
||||
_uri = String(uri);
|
||||
|
||||
// look for our query separator
|
||||
int index = _uri.indexOf('?', 0);
|
||||
if (index) {
|
||||
// parse them.
|
||||
_query = _uri.substring(index + 1);
|
||||
_addParams(_query, false);
|
||||
}
|
||||
|
||||
//did we get form data as body?
|
||||
if (this->method() == HTTP_POST && this->contentType().startsWith("application/x-www-form-urlencoded"))
|
||||
{
|
||||
_addParams(_body, true);
|
||||
}
|
||||
}
|
||||
|
||||
void PsychicRequest::_addParams(const String& params, bool post){
|
||||
size_t start = 0;
|
||||
while (start < params.length()){
|
||||
int end = params.indexOf('&', start);
|
||||
if (end < 0) end = params.length();
|
||||
int equal = params.indexOf('=', start);
|
||||
if (equal < 0 || equal > end) equal = end;
|
||||
String name = params.substring(start, equal);
|
||||
String value = equal + 1 < end ? params.substring(equal + 1, end) : String();
|
||||
addParam(name, value, true, post);
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
|
||||
PsychicWebParameter * PsychicRequest::addParam(const String &name, const String &value, bool decode, bool post)
|
||||
{
|
||||
if (decode)
|
||||
return addParam(new PsychicWebParameter(urlDecode(name.c_str()), urlDecode(value.c_str()), post));
|
||||
else
|
||||
return addParam(new PsychicWebParameter(name, value, post));
|
||||
}
|
||||
|
||||
PsychicWebParameter * PsychicRequest::addParam(PsychicWebParameter *param) {
|
||||
// ESP_LOGD(PH_TAG, "Adding param: '%s' = '%s'", param->name().c_str(), param->value().c_str());
|
||||
_params.push_back(param);
|
||||
return param;
|
||||
}
|
||||
|
||||
int PsychicRequest::params()
|
||||
@@ -323,14 +376,51 @@ int PsychicRequest::params()
|
||||
return _params.size();
|
||||
}
|
||||
|
||||
bool PsychicRequest::hasParam(const char *key)
|
||||
void PsychicRequest::_addParams(const String& params, bool post)
|
||||
{
|
||||
size_t start = 0;
|
||||
while (start < params.length()) {
|
||||
int end = params.indexOf('&', start);
|
||||
if (end < 0)
|
||||
end = params.length();
|
||||
int equal = params.indexOf('=', start);
|
||||
if (equal < 0 || equal > end)
|
||||
equal = end;
|
||||
String name = params.substring(start, equal);
|
||||
String value = equal + 1 < end ? params.substring(equal + 1, end) : String();
|
||||
addParam(name, value, true, post);
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
|
||||
PsychicWebParameter* PsychicRequest::addParam(const String& name, const String& value, bool decode, bool post)
|
||||
{
|
||||
if (decode)
|
||||
return addParam(new PsychicWebParameter(urlDecode(name.c_str()), urlDecode(value.c_str()), post));
|
||||
else
|
||||
return addParam(new PsychicWebParameter(name, value, post));
|
||||
}
|
||||
|
||||
PsychicWebParameter* PsychicRequest::addParam(PsychicWebParameter* param)
|
||||
{
|
||||
// ESP_LOGD(PH_TAG, "Adding param: '%s' = '%s'", param->name().c_str(), param->value().c_str());
|
||||
_params.push_back(param);
|
||||
return param;
|
||||
}
|
||||
|
||||
bool PsychicRequest::hasParam(const char* key)
|
||||
{
|
||||
return getParam(key) != NULL;
|
||||
}
|
||||
|
||||
PsychicWebParameter * PsychicRequest::getParam(const char *key)
|
||||
bool PsychicRequest::hasParam(const char* key, bool isPost, bool isFile)
|
||||
{
|
||||
for (auto *param : _params)
|
||||
return getParam(key, isPost, isFile) != NULL;
|
||||
}
|
||||
|
||||
PsychicWebParameter* PsychicRequest::getParam(const char* key)
|
||||
{
|
||||
for (auto* param : _params)
|
||||
if (param->name().equals(key))
|
||||
return param;
|
||||
|
||||
@@ -349,6 +439,14 @@ PsychicWebParameter * PsychicRequest::getParam(int index)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PsychicWebParameter* PsychicRequest::getParam(const char* key, bool isPost, bool isFile)
|
||||
{
|
||||
for (auto* param : _params)
|
||||
if (param->name().equals(key) && isPost == param->isPost() && isFile == param->isFile())
|
||||
return param;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool PsychicRequest::hasSessionKey(const String& key)
|
||||
{
|
||||
return this->_session->find(key) != this->_session->end();
|
||||
@@ -368,7 +466,8 @@ void PsychicRequest::setSessionKey(const String& key, const String& value)
|
||||
this->_session->insert(std::pair<String, String>(key, value));
|
||||
}
|
||||
|
||||
static const String md5str(const String &in){
|
||||
static const String md5str(const String& in)
|
||||
{
|
||||
MD5Builder md5 = MD5Builder();
|
||||
md5.begin();
|
||||
md5.add(in);
|
||||
@@ -376,28 +475,27 @@ static const String md5str(const String &in){
|
||||
return md5.toString();
|
||||
}
|
||||
|
||||
bool PsychicRequest::authenticate(const char * username, const char * password)
|
||||
bool PsychicRequest::authenticate(const char* username, const char* password)
|
||||
{
|
||||
if(hasHeader("Authorization"))
|
||||
{
|
||||
if (hasHeader("Authorization")) {
|
||||
String authReq = header("Authorization");
|
||||
if(authReq.startsWith("Basic")){
|
||||
if (authReq.startsWith("Basic")) {
|
||||
authReq = authReq.substring(6);
|
||||
authReq.trim();
|
||||
char toencodeLen = strlen(username)+strlen(password)+1;
|
||||
char *toencode = new char[toencodeLen + 1];
|
||||
if(toencode == NULL){
|
||||
char toencodeLen = strlen(username) + strlen(password) + 1;
|
||||
char* toencode = new char[toencodeLen + 1];
|
||||
if (toencode == NULL) {
|
||||
authReq = "";
|
||||
return false;
|
||||
}
|
||||
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
|
||||
if(encoded == NULL){
|
||||
char* encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
|
||||
if (encoded == NULL) {
|
||||
authReq = "";
|
||||
delete[] toencode;
|
||||
return false;
|
||||
}
|
||||
sprintf(toencode, "%s:%s", username, password);
|
||||
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) {
|
||||
if (base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) {
|
||||
authReq = "";
|
||||
delete[] toencode;
|
||||
delete[] encoded;
|
||||
@@ -405,64 +503,62 @@ bool PsychicRequest::authenticate(const char * username, const char * password)
|
||||
}
|
||||
delete[] toencode;
|
||||
delete[] encoded;
|
||||
}
|
||||
else if(authReq.startsWith(F("Digest")))
|
||||
{
|
||||
} else if (authReq.startsWith(F("Digest"))) {
|
||||
authReq = authReq.substring(7);
|
||||
String _username = _extractParam(authReq,F("username=\""),'\"');
|
||||
if(!_username.length() || _username != String(username)) {
|
||||
String _username = _extractParam(authReq, F("username=\""), '\"');
|
||||
if (!_username.length() || _username != String(username)) {
|
||||
authReq = "";
|
||||
return false;
|
||||
}
|
||||
// extracting required parameters for RFC 2069 simpler Digest
|
||||
String _realm = _extractParam(authReq, F("realm=\""),'\"');
|
||||
String _nonce = _extractParam(authReq, F("nonce=\""),'\"');
|
||||
String _uri = _extractParam(authReq, F("uri=\""),'\"');
|
||||
String _resp = _extractParam(authReq, F("response=\""),'\"');
|
||||
String _opaque = _extractParam(authReq, F("opaque=\""),'\"');
|
||||
String _realm = _extractParam(authReq, F("realm=\""), '\"');
|
||||
String _nonce = _extractParam(authReq, F("nonce=\""), '\"');
|
||||
String _url = _extractParam(authReq, F("uri=\""), '\"');
|
||||
String _resp = _extractParam(authReq, F("response=\""), '\"');
|
||||
String _opaque = _extractParam(authReq, F("opaque=\""), '\"');
|
||||
|
||||
if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_resp.length()) || (!_opaque.length())) {
|
||||
if ((!_realm.length()) || (!_nonce.length()) || (!_url.length()) || (!_resp.length()) || (!_opaque.length())) {
|
||||
authReq = "";
|
||||
return false;
|
||||
}
|
||||
if((_opaque != this->getSessionKey("opaque")) || (_nonce != this->getSessionKey("nonce")) || (_realm != this->getSessionKey("realm")))
|
||||
{
|
||||
if ((_opaque != this->getSessionKey("opaque")) || (_nonce != this->getSessionKey("nonce")) || (_realm != this->getSessionKey("realm"))) {
|
||||
authReq = "";
|
||||
return false;
|
||||
}
|
||||
// parameters for the RFC 2617 newer Digest
|
||||
String _nc,_cnonce;
|
||||
if(authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) {
|
||||
String _nc, _cnonce;
|
||||
if (authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) {
|
||||
_nc = _extractParam(authReq, F("nc="), ',');
|
||||
_cnonce = _extractParam(authReq, F("cnonce=\""),'\"');
|
||||
_cnonce = _extractParam(authReq, F("cnonce=\""), '\"');
|
||||
}
|
||||
|
||||
|
||||
String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password));
|
||||
//ESP_LOGD(PH_TAG, "Hash of user:realm:pass=%s", _H1.c_str());
|
||||
|
||||
// ESP_LOGD(PH_TAG, "Hash of user:realm:pass=%s", _H1.c_str());
|
||||
|
||||
String _H2 = "";
|
||||
if(_method == HTTP_GET){
|
||||
_H2 = md5str(String(F("GET:")) + _uri);
|
||||
}else if(_method == HTTP_POST){
|
||||
_H2 = md5str(String(F("POST:")) + _uri);
|
||||
}else if(_method == HTTP_PUT){
|
||||
_H2 = md5str(String(F("PUT:")) + _uri);
|
||||
}else if(_method == HTTP_DELETE){
|
||||
_H2 = md5str(String(F("DELETE:")) + _uri);
|
||||
}else{
|
||||
_H2 = md5str(String(F("GET:")) + _uri);
|
||||
}
|
||||
//ESP_LOGD(PH_TAG, "Hash of GET:uri=%s", _H2.c_str());
|
||||
|
||||
String _responsecheck = "";
|
||||
if(authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) {
|
||||
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2);
|
||||
http_method method = this->method();
|
||||
if (method == HTTP_GET) {
|
||||
_H2 = md5str(String(F("GET:")) + _url);
|
||||
} else if (method == HTTP_POST) {
|
||||
_H2 = md5str(String(F("POST:")) + _url);
|
||||
} else if (method == HTTP_PUT) {
|
||||
_H2 = md5str(String(F("PUT:")) + _url);
|
||||
} else if (method == HTTP_DELETE) {
|
||||
_H2 = md5str(String(F("DELETE:")) + _url);
|
||||
} else {
|
||||
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2);
|
||||
_H2 = md5str(String(F("GET:")) + _url);
|
||||
}
|
||||
|
||||
//ESP_LOGD(PH_TAG, "The Proper response=%s", _responsecheck.c_str());
|
||||
if(_resp == _responsecheck){
|
||||
// ESP_LOGD(PH_TAG, "Hash of GET:uri=%s", _H2.c_str());
|
||||
|
||||
String _responsecheck = "";
|
||||
if (authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) {
|
||||
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2);
|
||||
} else {
|
||||
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2);
|
||||
}
|
||||
|
||||
// ESP_LOGD(PH_TAG, "The Proper response=%s", _responsecheck.c_str());
|
||||
if (_resp == _responsecheck) {
|
||||
authReq = "";
|
||||
return true;
|
||||
}
|
||||
@@ -477,23 +573,23 @@ const String PsychicRequest::_extractParam(const String& authReq, const String&
|
||||
int _begin = authReq.indexOf(param);
|
||||
if (_begin == -1)
|
||||
return "";
|
||||
return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length()));
|
||||
return authReq.substring(_begin + param.length(), authReq.indexOf(delimit, _begin + param.length()));
|
||||
}
|
||||
|
||||
const String PsychicRequest::_getRandomHexString()
|
||||
{
|
||||
char buffer[33]; // buffer to hold 32 Hex Digit + /0
|
||||
char buffer[33]; // buffer to hold 32 Hex Digit + /0
|
||||
int i;
|
||||
for(i = 0; i < 4; i++) {
|
||||
sprintf (buffer + (i*8), "%08lx", (unsigned long int)esp_random());
|
||||
for (i = 0; i < 4; i++) {
|
||||
sprintf(buffer + (i * 8), "%08lx", (unsigned long int)esp_random());
|
||||
}
|
||||
return String(buffer);
|
||||
}
|
||||
|
||||
esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, const char* realm, const char* authFailMsg)
|
||||
{
|
||||
//what is thy realm, sire?
|
||||
if(!strcmp(realm, ""))
|
||||
// what is thy realm, sire?
|
||||
if (!strcmp(realm, ""))
|
||||
this->setSessionKey("realm", "Login Required");
|
||||
else
|
||||
this->setSessionKey("realm", realm);
|
||||
@@ -501,17 +597,14 @@ esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, const char*
|
||||
PsychicResponse response(this);
|
||||
String authStr;
|
||||
|
||||
//what kind of auth?
|
||||
if(mode == BASIC_AUTH)
|
||||
{
|
||||
// what kind of auth?
|
||||
if (mode == BASIC_AUTH) {
|
||||
authStr = "Basic realm=\"" + this->getSessionKey("realm") + "\"";
|
||||
response.addHeader("WWW-Authenticate", authStr.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
//only make new ones if we havent sent them yet
|
||||
} else {
|
||||
// only make new ones if we havent sent them yet
|
||||
if (this->getSessionKey("nonce").isEmpty())
|
||||
this->setSessionKey("nonce", _getRandomHexString());
|
||||
this->setSessionKey("nonce", _getRandomHexString());
|
||||
if (this->getSessionKey("opaque").isEmpty())
|
||||
this->setSessionKey("opaque", _getRandomHexString());
|
||||
|
||||
@@ -521,39 +614,6 @@ esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, const char*
|
||||
|
||||
response.setCode(401);
|
||||
response.setContentType("text/html");
|
||||
response.setContent(authStr.c_str());
|
||||
response.setContent(authFailMsg);
|
||||
return response.send();
|
||||
}
|
||||
|
||||
esp_err_t PsychicRequest::reply(int code)
|
||||
{
|
||||
PsychicResponse response(this);
|
||||
|
||||
response.setCode(code);
|
||||
response.setContentType("text/plain");
|
||||
response.setContent(http_status_reason(code));
|
||||
|
||||
return response.send();
|
||||
}
|
||||
|
||||
esp_err_t PsychicRequest::reply(const char *content)
|
||||
{
|
||||
PsychicResponse response(this);
|
||||
|
||||
response.setCode(200);
|
||||
response.setContentType("text/html");
|
||||
response.setContent(content);
|
||||
|
||||
return response.send();
|
||||
}
|
||||
|
||||
esp_err_t PsychicRequest::reply(int code, const char *contentType, const char *content)
|
||||
{
|
||||
PsychicResponse response(this);
|
||||
|
||||
response.setCode(code);
|
||||
response.setContentType(contentType);
|
||||
response.setContent(content);
|
||||
|
||||
return response.send();
|
||||
}
|
||||
@@ -1,38 +1,55 @@
|
||||
#ifndef PsychicRequest_h
|
||||
#define PsychicRequest_h
|
||||
|
||||
#include "PsychicCore.h"
|
||||
#include "PsychicHttpServer.h"
|
||||
#include "PsychicClient.h"
|
||||
#include "PsychicCore.h"
|
||||
#include "PsychicEndpoint.h"
|
||||
#include "PsychicHttpServer.h"
|
||||
#include "PsychicWebParameter.h"
|
||||
#include "PsychicResponse.h"
|
||||
|
||||
#ifdef PSY_ENABLE_REGEX
|
||||
#include <regex>
|
||||
#endif
|
||||
|
||||
typedef std::map<String, String> SessionData;
|
||||
|
||||
enum Disposition { NONE, INLINE, ATTACHMENT, FORM_DATA};
|
||||
|
||||
struct ContentDisposition {
|
||||
Disposition disposition;
|
||||
String filename;
|
||||
String name;
|
||||
enum Disposition {
|
||||
NONE,
|
||||
INLINE,
|
||||
ATTACHMENT,
|
||||
FORM_DATA
|
||||
};
|
||||
|
||||
class PsychicRequest {
|
||||
friend PsychicHttpServer;
|
||||
struct ContentDisposition {
|
||||
Disposition disposition;
|
||||
String filename;
|
||||
String name;
|
||||
};
|
||||
|
||||
class PsychicRequest
|
||||
{
|
||||
friend PsychicHttpServer;
|
||||
friend PsychicResponse;
|
||||
|
||||
protected:
|
||||
PsychicHttpServer *_server;
|
||||
httpd_req_t *_req;
|
||||
SessionData *_session;
|
||||
PsychicClient *_client;
|
||||
PsychicHttpServer* _server;
|
||||
httpd_req_t* _req;
|
||||
SessionData* _session;
|
||||
PsychicClient* _client;
|
||||
PsychicEndpoint* _endpoint;
|
||||
|
||||
http_method _method;
|
||||
String _uri;
|
||||
String _query;
|
||||
String _body;
|
||||
esp_err_t _bodyParsed = ESP_ERR_NOT_FINISHED;
|
||||
esp_err_t _paramsParsed = ESP_ERR_NOT_FINISHED;
|
||||
|
||||
std::list<PsychicWebParameter*> _params;
|
||||
|
||||
PsychicResponse* _response;
|
||||
|
||||
void _setUri(const char* uri);
|
||||
void _addParams(const String& params, bool post);
|
||||
void _parseGETParams();
|
||||
void _parsePOSTParams();
|
||||
@@ -41,28 +58,59 @@ class PsychicRequest {
|
||||
const String _getRandomHexString();
|
||||
|
||||
public:
|
||||
PsychicRequest(PsychicHttpServer *server, httpd_req_t *req);
|
||||
PsychicRequest(PsychicHttpServer* server, httpd_req_t* req);
|
||||
virtual ~PsychicRequest();
|
||||
|
||||
void *_tempObject;
|
||||
void* _tempObject;
|
||||
|
||||
PsychicHttpServer * server();
|
||||
httpd_req_t * request();
|
||||
virtual PsychicClient * client();
|
||||
PsychicHttpServer* server();
|
||||
httpd_req_t* request();
|
||||
virtual PsychicClient* client();
|
||||
|
||||
PsychicEndpoint* endpoint();
|
||||
void setEndpoint(PsychicEndpoint* endpoint);
|
||||
|
||||
#ifdef PSY_ENABLE_REGEX
|
||||
bool getRegexMatches(std::smatch& matches, bool use_full_uri = false);
|
||||
#endif
|
||||
|
||||
bool isMultipart();
|
||||
esp_err_t loadBody();
|
||||
|
||||
const String header(const char *name);
|
||||
bool hasHeader(const char *name);
|
||||
const String header(const char* name);
|
||||
bool hasHeader(const char* name);
|
||||
|
||||
static void freeSession(void *ctx);
|
||||
static void freeSession(void* ctx);
|
||||
bool hasSessionKey(const String& key);
|
||||
const String getSessionKey(const String& key);
|
||||
void setSessionKey(const String& key, const String& value);
|
||||
|
||||
bool hasCookie(const char * key);
|
||||
const String getCookie(const char * key);
|
||||
bool hasCookie(const char* key, size_t* size = nullptr);
|
||||
|
||||
PsychicResponse* response() { return _response; }
|
||||
void replaceResponse(PsychicResponse* response);
|
||||
void addResponseHeader(const char* key, const char* value);
|
||||
std::list<HTTPHeader>& getResponseHeaders();
|
||||
|
||||
/**
|
||||
* @brief Get the value string of a cookie value from the "Cookie" request headers by cookie name.
|
||||
*
|
||||
* @param[in] key The cookie name to be searched in the request
|
||||
* @param[out] buffer Pointer to the buffer into which the value of cookie will be copied if the cookie is found
|
||||
* @param[inout] size Pointer to size of the user buffer "val". This variable will contain cookie length if
|
||||
* ESP_OK is returned and required buffer length in case ESP_ERR_HTTPD_RESULT_TRUNC is returned.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK : Key is found in the cookie string and copied to buffer. The value is null-terminated.
|
||||
* - ESP_ERR_NOT_FOUND : Key not found
|
||||
* - ESP_ERR_INVALID_ARG : Null arguments
|
||||
* - ESP_ERR_HTTPD_RESULT_TRUNC : Value string truncated
|
||||
* - ESP_ERR_NO_MEM : Memory allocation failure
|
||||
*/
|
||||
esp_err_t getCookie(const char* key, char* buffer, size_t* size);
|
||||
|
||||
// convenience / lazy function for getting cookies.
|
||||
String getCookie(const char* key);
|
||||
|
||||
http_method method(); // returns the HTTP method used as enum value (eg. HTTP_GET)
|
||||
const String methodStr(); // returns the HTTP method used as a string (eg. "GET")
|
||||
@@ -74,27 +122,25 @@ class PsychicRequest {
|
||||
size_t contentLength(); // returns the Content-Length header value
|
||||
const String& body(); // returns the body of the request
|
||||
const ContentDisposition getContentDisposition();
|
||||
const char* version() { return "HTTP/1.1"; }
|
||||
|
||||
const String& queryString() { return query(); } //compatability function. same as query()
|
||||
const String& url() { return uri(); } //compatability function. same as uri()
|
||||
const String& queryString() { return query(); } // compatability function. same as query()
|
||||
const String& url() { return uri(); } // compatability function. same as uri()
|
||||
|
||||
void loadParams();
|
||||
PsychicWebParameter * addParam(PsychicWebParameter *param);
|
||||
PsychicWebParameter * addParam(const String &name, const String &value, bool decode = true, bool post = false);
|
||||
PsychicWebParameter* addParam(PsychicWebParameter* param);
|
||||
PsychicWebParameter* addParam(const String& name, const String& value, bool decode = true, bool post = false);
|
||||
int params();
|
||||
bool hasParam(const char *key);
|
||||
PsychicWebParameter * getParam(const char *name);
|
||||
PsychicWebParameter * getParam(int index);
|
||||
|
||||
bool hasParam(const char* key);
|
||||
bool hasParam(const char* key, bool isPost, bool isFile = false);
|
||||
PsychicWebParameter* getParam(const char* name);
|
||||
PsychicWebParameter* getParam(int index);
|
||||
PsychicWebParameter* getParam(const char* name, bool isPost, bool isFile = false);
|
||||
|
||||
const String getFilename();
|
||||
|
||||
bool authenticate(const char * username, const char * password);
|
||||
bool authenticate(const char* username, const char* password);
|
||||
esp_err_t requestAuthentication(HTTPAuthMethod mode, const char* realm, const char* authFailMsg);
|
||||
|
||||
esp_err_t redirect(const char *url);
|
||||
esp_err_t reply(int code);
|
||||
esp_err_t reply(const char *content);
|
||||
esp_err_t reply(int code, const char *contentType, const char *content);
|
||||
};
|
||||
|
||||
#endif // PsychicRequest_h
|
||||
@@ -2,52 +2,49 @@
|
||||
#include "PsychicRequest.h"
|
||||
#include <http_status.h>
|
||||
|
||||
PsychicResponse::PsychicResponse(PsychicRequest *request) :
|
||||
_request(request),
|
||||
_code(200),
|
||||
_status(""),
|
||||
_contentLength(0),
|
||||
_body("")
|
||||
PsychicResponse::PsychicResponse(PsychicRequest* request) : _request(request),
|
||||
_code(200),
|
||||
_status(""),
|
||||
_contentType(emptyString),
|
||||
_contentLength(0),
|
||||
_body("")
|
||||
{
|
||||
// get our global headers out of the way
|
||||
for (auto& header : DefaultHeaders::Instance().getHeaders())
|
||||
addHeader(header.field.c_str(), header.value.c_str());
|
||||
}
|
||||
|
||||
PsychicResponse::~PsychicResponse()
|
||||
{
|
||||
//clean up our header variables. we have to do this on desctruct since httpd_resp_send doesn't store copies
|
||||
for (HTTPHeader header : _headers)
|
||||
{
|
||||
free(header.field);
|
||||
free(header.value);
|
||||
}
|
||||
_headers.clear();
|
||||
}
|
||||
|
||||
void PsychicResponse::addHeader(const char *field, const char *value)
|
||||
void PsychicResponse::addHeader(const char* field, const char* value)
|
||||
{
|
||||
//these get freed after send by the destructor
|
||||
HTTPHeader header;
|
||||
header.field =(char *)malloc(strlen(field)+1);
|
||||
header.value = (char *)malloc(strlen(value)+1);
|
||||
// erase any existing ones.
|
||||
for (auto itr = _headers.begin(); itr != _headers.end();) {
|
||||
if (itr->field.equalsIgnoreCase(field))
|
||||
itr = _headers.erase(itr);
|
||||
else
|
||||
itr++;
|
||||
}
|
||||
|
||||
strlcpy(header.field, field, strlen(field)+1);
|
||||
strlcpy(header.value, value, strlen(value)+1);
|
||||
|
||||
_headers.push_back(header);
|
||||
// now add it.
|
||||
_headers.push_back({field, value});
|
||||
}
|
||||
|
||||
void PsychicResponse::setCookie(const char *name, const char *value, unsigned long secondsFromNow, const char *extras)
|
||||
void PsychicResponse::setCookie(const char* name, const char* value, unsigned long secondsFromNow, const char* extras)
|
||||
{
|
||||
time_t now = time(nullptr);
|
||||
|
||||
String output;
|
||||
output = urlEncode(name) + "=" + urlEncode(value);
|
||||
|
||||
//if current time isn't modern, default to using max age
|
||||
// if current time isn't modern, default to using max age
|
||||
if (now < 1700000000)
|
||||
output += "; Max-Age=" + String(secondsFromNow);
|
||||
//otherwise, set an expiration date
|
||||
else
|
||||
{
|
||||
output += "; Max-Age=" + String(secondsFromNow);
|
||||
// otherwise, set an expiration date
|
||||
else {
|
||||
time_t expirationTimestamp = now + secondsFromNow;
|
||||
|
||||
// Convert the expiration timestamp to a formatted string for the "expires" attribute
|
||||
@@ -57,11 +54,11 @@ void PsychicResponse::setCookie(const char *name, const char *value, unsigned lo
|
||||
output += "; Expires=" + String(expires);
|
||||
}
|
||||
|
||||
//did we get any extras?
|
||||
// did we get any extras?
|
||||
if (strlen(extras))
|
||||
output += "; " + String(extras);
|
||||
|
||||
//okay, add it in.
|
||||
// okay, add it in.
|
||||
addHeader("Set-Cookie", output.c_str());
|
||||
}
|
||||
|
||||
@@ -70,24 +67,24 @@ void PsychicResponse::setCode(int code)
|
||||
_code = code;
|
||||
}
|
||||
|
||||
void PsychicResponse::setContentType(const char *contentType)
|
||||
void PsychicResponse::setContentType(const char* contentType)
|
||||
{
|
||||
httpd_resp_set_type(_request->request(), contentType);
|
||||
_contentType = contentType;
|
||||
}
|
||||
|
||||
void PsychicResponse::setContent(const char *content)
|
||||
void PsychicResponse::setContent(const char* content)
|
||||
{
|
||||
_body = content;
|
||||
setContentLength(strlen(content));
|
||||
}
|
||||
|
||||
void PsychicResponse::setContent(const uint8_t *content, size_t len)
|
||||
void PsychicResponse::setContent(const uint8_t* content, size_t len)
|
||||
{
|
||||
_body = (char *)content;
|
||||
_body = (char*)content;
|
||||
setContentLength(len);
|
||||
}
|
||||
|
||||
const char * PsychicResponse::getContent()
|
||||
const char* PsychicResponse::getContent()
|
||||
{
|
||||
return _body;
|
||||
}
|
||||
@@ -99,17 +96,20 @@ size_t PsychicResponse::getContentLength()
|
||||
|
||||
esp_err_t PsychicResponse::send()
|
||||
{
|
||||
//esp-idf makes you set the whole status.
|
||||
// esp-idf makes you set the whole status.
|
||||
sprintf(_status, "%u %s", _code, http_status_reason(_code));
|
||||
httpd_resp_set_status(_request->request(), _status);
|
||||
|
||||
//our headers too
|
||||
// set the content type
|
||||
httpd_resp_set_type(_request->request(), _contentType.c_str());
|
||||
|
||||
// our headers too
|
||||
this->sendHeaders();
|
||||
|
||||
//now send it off
|
||||
// now send it off
|
||||
esp_err_t err = httpd_resp_send(_request->request(), getContent(), getContentLength());
|
||||
|
||||
//did something happen?
|
||||
// did something happen?
|
||||
if (err != ESP_OK)
|
||||
ESP_LOGE(PH_TAG, "Send response failed (%s)", esp_err_to_name(err));
|
||||
|
||||
@@ -118,38 +118,21 @@ esp_err_t PsychicResponse::send()
|
||||
|
||||
void PsychicResponse::sendHeaders()
|
||||
{
|
||||
//get our global headers out of the way first
|
||||
for (HTTPHeader header : DefaultHeaders::Instance().getHeaders())
|
||||
httpd_resp_set_hdr(_request->request(), header.field, header.value);
|
||||
|
||||
//now do our individual headers
|
||||
for (HTTPHeader header : _headers)
|
||||
httpd_resp_set_hdr(this->_request->request(), header.field, header.value);
|
||||
|
||||
// DO NOT RELEASE HEADERS HERE... released in the PsychicResponse destructor after they have been sent.
|
||||
// httpd_resp_set_hdr just passes on the pointer, but its needed after this call.
|
||||
// clean up our header variables after send
|
||||
// for (HTTPHeader header : _headers)
|
||||
// {
|
||||
// free(header.field);
|
||||
// free(header.value);
|
||||
// }
|
||||
// _headers.clear();
|
||||
// now do our individual headers
|
||||
for (auto& header : _headers)
|
||||
httpd_resp_set_hdr(this->_request->request(), header.field.c_str(), header.value.c_str());
|
||||
}
|
||||
|
||||
esp_err_t PsychicResponse::sendChunk(uint8_t *chunk, size_t chunksize)
|
||||
esp_err_t PsychicResponse::sendChunk(uint8_t* chunk, size_t chunksize)
|
||||
{
|
||||
/* Send the buffer contents as HTTP response chunk */
|
||||
esp_err_t err = httpd_resp_send_chunk(this->_request->request(), (char *)chunk, chunksize);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGD(PH_TAG, "Sending chunk: %d", chunksize);
|
||||
esp_err_t err = httpd_resp_send_chunk(request(), (char*)chunk, chunksize);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(PH_TAG, "File sending failed (%s)", esp_err_to_name(err));
|
||||
|
||||
/* Abort sending file */
|
||||
httpd_resp_sendstr_chunk(this->_request->request(), NULL);
|
||||
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
|
||||
}
|
||||
|
||||
return err;
|
||||
@@ -159,4 +142,63 @@ esp_err_t PsychicResponse::finishChunking()
|
||||
{
|
||||
/* Respond with an empty chunk to signal HTTP response completion */
|
||||
return httpd_resp_send_chunk(this->_request->request(), NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t PsychicResponse::redirect(const char* url)
|
||||
{
|
||||
if (!_code)
|
||||
setCode(301);
|
||||
addHeader("Location", url);
|
||||
return send();
|
||||
}
|
||||
|
||||
esp_err_t PsychicResponse::send(int code)
|
||||
{
|
||||
setCode(code);
|
||||
return send();
|
||||
}
|
||||
|
||||
esp_err_t PsychicResponse::send(const char* content)
|
||||
{
|
||||
if (!_code)
|
||||
setCode(200);
|
||||
if (_contentType.isEmpty())
|
||||
setContentType("text/html");
|
||||
setContent(content);
|
||||
return send();
|
||||
}
|
||||
|
||||
esp_err_t PsychicResponse::send(const char* contentType, const char* content)
|
||||
{
|
||||
if (!_code)
|
||||
setCode(200);
|
||||
setContentType(contentType);
|
||||
setContent(content);
|
||||
return send();
|
||||
}
|
||||
|
||||
esp_err_t PsychicResponse::send(int code, const char* contentType, const char* content)
|
||||
{
|
||||
setCode(code);
|
||||
setContentType(contentType);
|
||||
setContent(content);
|
||||
return send();
|
||||
}
|
||||
|
||||
esp_err_t PsychicResponse::send(int code, const char* contentType, const uint8_t* content, size_t len)
|
||||
{
|
||||
setCode(code);
|
||||
setContentType(contentType);
|
||||
setContent(content, len);
|
||||
return send();
|
||||
}
|
||||
|
||||
esp_err_t PsychicResponse::error(httpd_err_code_t code, const char* message)
|
||||
{
|
||||
return httpd_resp_send_err(_request->_req, code, message);
|
||||
}
|
||||
|
||||
httpd_req_t* PsychicResponse::request()
|
||||
{
|
||||
return _request->_req;
|
||||
}
|
||||
|
||||
@@ -9,38 +9,101 @@ class PsychicRequest;
|
||||
class PsychicResponse
|
||||
{
|
||||
protected:
|
||||
PsychicRequest *_request;
|
||||
PsychicRequest* _request;
|
||||
|
||||
int _code;
|
||||
char _status[60];
|
||||
std::list<HTTPHeader> _headers;
|
||||
String _contentType;
|
||||
int64_t _contentLength;
|
||||
const char * _body;
|
||||
const char* _body;
|
||||
|
||||
public:
|
||||
PsychicResponse(PsychicRequest *request);
|
||||
PsychicResponse(PsychicRequest* request);
|
||||
virtual ~PsychicResponse();
|
||||
|
||||
void setCode(int code);
|
||||
const char* version() { return "HTTP/1.1"; }
|
||||
|
||||
void setCode(int code);
|
||||
int getCode() { return _code; }
|
||||
|
||||
void setContentType(const char* contentType);
|
||||
String& getContentType() { return _contentType; }
|
||||
|
||||
void setContentType(const char *contentType);
|
||||
void setContentLength(int64_t contentLength) { _contentLength = contentLength; }
|
||||
int64_t getContentLength(int64_t contentLength) { return _contentLength; }
|
||||
|
||||
void addHeader(const char *field, const char *value);
|
||||
void addHeader(const char* field, const char* value);
|
||||
std::list<HTTPHeader>& headers() { return _headers; }
|
||||
|
||||
void setCookie(const char *key, const char *value, unsigned long max_age = 60*60*24*30, const char *extras = "");
|
||||
void setCookie(const char* key, const char* value, unsigned long max_age = 60 * 60 * 24 * 30, const char* extras = "");
|
||||
|
||||
void setContent(const char *content);
|
||||
void setContent(const uint8_t *content, size_t len);
|
||||
void setContent(const char* content);
|
||||
void setContent(const uint8_t* content, size_t len);
|
||||
|
||||
const char * getContent();
|
||||
const char* getContent();
|
||||
size_t getContentLength();
|
||||
|
||||
virtual esp_err_t send();
|
||||
void sendHeaders();
|
||||
esp_err_t sendChunk(uint8_t *chunk, size_t chunksize);
|
||||
esp_err_t sendChunk(uint8_t* chunk, size_t chunksize);
|
||||
esp_err_t finishChunking();
|
||||
|
||||
esp_err_t redirect(const char* url);
|
||||
esp_err_t send(int code);
|
||||
esp_err_t send(const char* content);
|
||||
esp_err_t send(const char* contentType, const char* content);
|
||||
esp_err_t send(int code, const char* contentType, const char* content);
|
||||
esp_err_t send(int code, const char* contentType, const uint8_t* content, size_t len);
|
||||
esp_err_t error(httpd_err_code_t code, const char* message);
|
||||
|
||||
httpd_req_t* request();
|
||||
};
|
||||
|
||||
class PsychicResponseDelegate
|
||||
{
|
||||
protected:
|
||||
PsychicResponse* _response;
|
||||
|
||||
public:
|
||||
PsychicResponseDelegate(PsychicResponse* response) : _response(response) {}
|
||||
virtual ~PsychicResponseDelegate() {}
|
||||
|
||||
const char* version() { return _response->version(); }
|
||||
|
||||
void setCode(int code) { _response->setCode(code); }
|
||||
|
||||
void setContentType(const char* contentType) { _response->setContentType(contentType); }
|
||||
String& getContentType() { return _response->getContentType(); }
|
||||
|
||||
void setContentLength(int64_t contentLength) { _response->setContentLength(contentLength); }
|
||||
int64_t getContentLength(int64_t contentLength) { return _response->getContentLength(); }
|
||||
|
||||
void addHeader(const char* field, const char* value) { _response->addHeader(field, value); }
|
||||
|
||||
void setCookie(const char* key, const char* value, unsigned long max_age = 60 * 60 * 24 * 30, const char* extras = "") { _response->setCookie(key, value, max_age, extras); }
|
||||
|
||||
void setContent(const char* content) { _response->setContent(content); }
|
||||
void setContent(const uint8_t* content, size_t len) { _response->setContent(content, len); }
|
||||
|
||||
const char* getContent() { return _response->getContent(); }
|
||||
size_t getContentLength() { return _response->getContentLength(); }
|
||||
|
||||
esp_err_t send() { return _response->send(); }
|
||||
void sendHeaders() { _response->sendHeaders(); }
|
||||
|
||||
esp_err_t sendChunk(uint8_t* chunk, size_t chunksize) { return _response->sendChunk(chunk, chunksize); }
|
||||
esp_err_t finishChunking() { return _response->finishChunking(); }
|
||||
|
||||
esp_err_t redirect(const char* url) { return _response->redirect(url); }
|
||||
esp_err_t send(int code) { return _response->send(code); }
|
||||
esp_err_t send(const char* content) { return _response->send(content); }
|
||||
esp_err_t send(const char* contentType, const char* content) { return _response->send(contentType, content); }
|
||||
esp_err_t send(int code, const char* contentType, const char* content) { return _response->send(code, contentType, content); }
|
||||
esp_err_t send(int code, const char* contentType, const uint8_t* content, size_t len) { return _response->send(code, contentType, content, len); }
|
||||
esp_err_t error(httpd_err_code_t code, const char* message) { return _response->error(code, message); }
|
||||
|
||||
httpd_req_t* request() { return _response->request(); }
|
||||
};
|
||||
|
||||
#endif // PsychicResponse_h
|
||||
54
lib/PsychicHttp/src/PsychicRewrite.cpp
Normal file
54
lib/PsychicHttp/src/PsychicRewrite.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "PsychicRewrite.h"
|
||||
#include "PsychicRequest.h"
|
||||
|
||||
PsychicRewrite::PsychicRewrite(const char* from, const char* to):
|
||||
_fromPath(from),
|
||||
_toUri(to),
|
||||
_toPath(String()),
|
||||
_toParams(String()),
|
||||
_filter(nullptr)
|
||||
{
|
||||
int index = _toUri.indexOf('?');
|
||||
if (index > 0) {
|
||||
_toParams = _toUri.substring(index + 1);
|
||||
_toPath = _toUri.substring(0, index);
|
||||
}
|
||||
else
|
||||
_toPath = _toUri;
|
||||
}
|
||||
PsychicRewrite::~PsychicRewrite()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
PsychicRewrite* PsychicRewrite::setFilter(PsychicRequestFilterFunction fn)
|
||||
{
|
||||
_filter = fn; return this;
|
||||
}
|
||||
|
||||
bool PsychicRewrite::filter(PsychicRequest *request) const
|
||||
{
|
||||
return _filter == nullptr || _filter(request);
|
||||
}
|
||||
|
||||
const String& PsychicRewrite::from(void) const
|
||||
{
|
||||
return _fromPath;
|
||||
}
|
||||
const String& PsychicRewrite::toUrl(void) const
|
||||
{
|
||||
return _toUri;
|
||||
}
|
||||
|
||||
const String& PsychicRewrite::params(void) const
|
||||
{
|
||||
return _toParams;
|
||||
}
|
||||
|
||||
bool PsychicRewrite::match(PsychicRequest *request)
|
||||
{
|
||||
if (!filter(request))
|
||||
return false;
|
||||
|
||||
return _fromPath == request->path();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user