PsychichHTTP v2-dev
This commit is contained in:
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
|
# v1.2.1
|
||||||
|
|
||||||
* Fix bug with missing include preventing the HTTPS server from compiling.
|
* 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
|
## Differences from ESPAsyncWebserver
|
||||||
|
|
||||||
* No templating system (anyone actually use this?)
|
* 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
|
# Usage
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ If you have existing code using ESPAsyncWebserver, you will feel right at home w
|
|||||||
|
|
||||||
## setup() Stuff
|
## 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.
|
* server has a configurable limit on .on() endpoints. change it with ```server.config.max_uri_handlers = 20;``` as needed.
|
||||||
* check your callback function definitions:
|
* check your callback function definitions:
|
||||||
* AsyncWebServerRequest -> PsychicRequest
|
* AsyncWebServerRequest -> PsychicRequest
|
||||||
@@ -136,7 +136,7 @@ If you have existing code using ESPAsyncWebserver, you will feel right at home w
|
|||||||
|
|
||||||
## Requests / Responses
|
## 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)
|
* 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.
|
* request->headers() is not supported by ESP-IDF, you have to just check for the header you need.
|
||||||
* No AsyncCallbackJsonWebHandler (for now... can add if needed)
|
* No AsyncCallbackJsonWebHandler (for now... can add if needed)
|
||||||
@@ -164,9 +164,6 @@ void setup()
|
|||||||
|
|
||||||
//connect to wifi
|
//connect to wifi
|
||||||
|
|
||||||
//start the server listening on port 80 (standard HTTP port)
|
|
||||||
server.listen(80);
|
|
||||||
|
|
||||||
//call server methods to attach endpoints and handlers
|
//call server methods to attach endpoints and handlers
|
||||||
server.on(...);
|
server.on(...);
|
||||||
server.serveStatic(...);
|
server.serveStatic(...);
|
||||||
@@ -198,7 +195,7 @@ The ```server.on(...)``` returns a pointer to the endpoint, which can be used to
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
//respond to /url only from requests to the AP
|
//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
|
//require authentication on /url
|
||||||
server.on("/url", HTTP_GET, request_callback)->setAuthentication("user", "pass");
|
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.
|
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:
|
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)
|
server.on("/ip", [](PsychicRequest *request)
|
||||||
{
|
{
|
||||||
String output = "Your IP is: " + request->client()->remoteIP().toString();
|
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 url = "/" + request->getFilename();
|
||||||
String output = "<a href=\"" + url + "\">" + url + "</a>";
|
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
|
||||||
@@ -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 1: " + request->getParam("param1")->value() + "<br/>\n";
|
||||||
output += "Param 2: " + request->getParam("param2")->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
|
//upload to /multipart url
|
||||||
@@ -374,11 +371,11 @@ The ```server.serveStatic()``` function handles creating the handler and assigni
|
|||||||
```cpp
|
```cpp
|
||||||
//serve static files from LittleFS/www on / only to clients on same wifi network
|
//serve static files from LittleFS/www on / only to clients on same wifi network
|
||||||
//this is where our /index.html file lives
|
//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
|
//serve static files from LittleFS/www-ap on / only to clients on SoftAP
|
||||||
//this is where our /index.html file lives
|
//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
|
//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.
|
//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();
|
PsychicWebSocketHandler websocketHandler();
|
||||||
|
|
||||||
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
|
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!");
|
client->sendMessage("Hello!");
|
||||||
});
|
});
|
||||||
|
|
||||||
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
|
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
|
||||||
Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload);
|
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) {
|
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
|
//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:
|
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.
|
* ```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
|
* ```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;
|
PsychicEventSource eventSource;
|
||||||
|
|
||||||
eventSource.onOpen([](PsychicEventSourceClient *client) {
|
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);
|
client->send("Hello user!", NULL, millis(), 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
eventSource.onClose([](PsychicEventSourceClient *client) {
|
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
|
//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 <PsychicHttp.h>
|
||||||
#include <PsychicHttpsServer.h>
|
#include <PsychicHttpsServer.h>
|
||||||
PsychicHttpsServer server;
|
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.
|
```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
|
//this creates a 2nd server listening on port 80 and redirects all requests HTTPS
|
||||||
PsychicHttpServer *redirectServer = new PsychicHttpServer();
|
PsychicHttpServer *redirectServer = new PsychicHttpServer();
|
||||||
redirectServer->config.ctrl_port = 20420; // just a random port different from the default one
|
redirectServer->config.ctrl_port = 20420; // just a random port different from the default one
|
||||||
redirectServer->listen(80);
|
|
||||||
redirectServer->onNotFound([](PsychicRequest *request) {
|
redirectServer->onNotFound([](PsychicRequest *request) {
|
||||||
String url = "https://" + request->host() + request->url();
|
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.
|
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
|
# Roadmap
|
||||||
|
|
||||||
## v1.2: ESPAsyncWebserver Parity
|
## v2.0: 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?
|
|
||||||
|
|
||||||
|
* As much ESPAsyncWebServer compatibility as possible
|
||||||
|
* Update benchmarks and get new data
|
||||||
|
* we should also track program size and memory usage
|
||||||
|
|
||||||
## Longterm Wants
|
## Longterm Wants
|
||||||
|
|
||||||
* investigate websocket performance gap
|
* investigate websocket performance gap
|
||||||
* support for esp-idf framework
|
* support for esp-idf framework
|
||||||
* support for arduino 3.0 framework
|
|
||||||
* Enable worker based multithreading with esp-idf v5.x
|
* Enable worker based multithreading with esp-idf v5.x
|
||||||
* 100-continue support?
|
* 100-continue support?
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
* Update CHANGELOG
|
* Update CHANGELOG
|
||||||
|
* Bump version in src/PsychicVersion.h
|
||||||
* Bump version in library.json
|
* Bump version in library.json
|
||||||
* Bump version in library.properties
|
* Bump version in library.properties
|
||||||
* Make new release + tag
|
* Make new release + tag
|
||||||
* this will get pulled in automatically by Arduino Library Indexer
|
* 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,11 +8,11 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <WiFi.h>
|
#include <ArduinoJSON.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
#include <MongooseCore.h>
|
#include <MongooseCore.h>
|
||||||
#include <MongooseHttpServer.h>
|
#include <MongooseHttpServer.h>
|
||||||
#include <LittleFS.h>
|
#include <WiFi.h>
|
||||||
#include <ArduinoJSON.h>
|
|
||||||
|
|
||||||
const char* ssid = "";
|
const char* ssid = "";
|
||||||
const char* password = "";
|
const char* password = "";
|
||||||
@@ -166,9 +166,7 @@ void setup()
|
|||||||
|
|
||||||
// index file
|
// index file
|
||||||
server.on("/", HTTP_GET, [](MongooseHttpServerRequest* request)
|
server.on("/", HTTP_GET, [](MongooseHttpServerRequest* request)
|
||||||
{
|
{ request->send(200, "text/html", htmlContent); });
|
||||||
request->send(200, "text/html", htmlContent);
|
|
||||||
});
|
|
||||||
|
|
||||||
// api - parameters passed in via query eg. /api/endpoint?foo=bar
|
// api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||||
server.on("/api", HTTP_GET, [](MongooseHttpServerRequest* request)
|
server.on("/api", HTTP_GET, [](MongooseHttpServerRequest* request)
|
||||||
@@ -189,12 +187,11 @@ void setup()
|
|||||||
//serialize and return
|
//serialize and return
|
||||||
String jsonBuffer;
|
String jsonBuffer;
|
||||||
serializeJson(output, jsonBuffer);
|
serializeJson(output, jsonBuffer);
|
||||||
request->send(200, "application/json", jsonBuffer.c_str());
|
request->send(200, "application/json", jsonBuffer.c_str()); });
|
||||||
});
|
|
||||||
|
|
||||||
// websocket
|
// websocket
|
||||||
server.on("/ws$")->
|
server.on("/ws$")->onFrame([](MongooseHttpWebSocketConnection* connection, int flags, uint8_t* data, size_t len)
|
||||||
onFrame([](MongooseHttpWebSocketConnection *connection, int flags, uint8_t *data, size_t len) {
|
{
|
||||||
connection->send(WEBSOCKET_OP_TEXT, data, len);
|
connection->send(WEBSOCKET_OP_TEXT, data, len);
|
||||||
// server.sendAll(connection, (char *)data);
|
// server.sendAll(connection, (char *)data);
|
||||||
});
|
});
|
||||||
@@ -223,8 +220,7 @@ void setup()
|
|||||||
free(data);
|
free(data);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
request->send(503);
|
request->send(503); });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,12 +11,31 @@
|
|||||||
[env]
|
[env]
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
framework = arduino
|
framework = arduino
|
||||||
board = esp32dev
|
; board = esp32dev
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
upload_port = /dev/ttyACM0
|
||||||
|
monitor_port = /dev/ttyACM1
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
|
|
||||||
|
lib_compat_mode = strict
|
||||||
|
lib_ldf_mode = chain
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/me-no-dev/ESPAsyncWebServer
|
; 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
|
bblanchon/ArduinoJson
|
||||||
|
lib_ignore =
|
||||||
|
AsyncTCP
|
||||||
|
mathieucarbou/AsyncTCP
|
||||||
|
|
||||||
board_build.filesystem = littlefs
|
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]
|
[env:default]
|
||||||
@@ -7,14 +7,24 @@
|
|||||||
CONDITIONS OF ANY KIND, either express or implied.
|
CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
#include "_secret.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <WiFi.h>
|
#include <ArduinoJson.h>
|
||||||
#include <ESPAsyncWebServer.h>
|
#include <ESPAsyncWebServer.h>
|
||||||
|
#include <ESPmDNS.h>
|
||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
#include <ArduinoJSON.h>
|
#include <WiFi.h>
|
||||||
|
|
||||||
const char *ssid = "";
|
#ifndef WIFI_SSID
|
||||||
const char *password = "";
|
#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);
|
AsyncWebServer server(80);
|
||||||
AsyncWebSocket ws("/ws");
|
AsyncWebSocket ws("/ws");
|
||||||
@@ -79,14 +89,16 @@ const char *htmlContent = R"(
|
|||||||
</html>
|
</html>
|
||||||
)";
|
)";
|
||||||
|
|
||||||
|
const size_t htmlContentLen = strlen(htmlContent);
|
||||||
|
|
||||||
bool connectToWifi()
|
bool connectToWifi()
|
||||||
{
|
{
|
||||||
Serial.println();
|
Serial.println();
|
||||||
Serial.print("[WiFi] Connecting to ");
|
Serial.print("[WiFi] Connecting to ");
|
||||||
Serial.println(ssid);
|
Serial.println(ssid);
|
||||||
|
|
||||||
WiFi.setSleep(false);
|
// WiFi.setSleep(false);
|
||||||
WiFi.useStaticBuffers(true);
|
// WiFi.useStaticBuffers(true);
|
||||||
|
|
||||||
WiFi.begin(ssid, password);
|
WiFi.begin(ssid, password);
|
||||||
|
|
||||||
@@ -95,10 +107,8 @@ bool connectToWifi()
|
|||||||
int numberOfTries = 20;
|
int numberOfTries = 20;
|
||||||
|
|
||||||
// Wait for the WiFi event
|
// Wait for the WiFi event
|
||||||
while (true)
|
while (true) {
|
||||||
{
|
switch (WiFi.status()) {
|
||||||
switch (WiFi.status())
|
|
||||||
{
|
|
||||||
case WL_NO_SSID_AVAIL:
|
case WL_NO_SSID_AVAIL:
|
||||||
Serial.println("[WiFi] SSID not found");
|
Serial.println("[WiFi] SSID not found");
|
||||||
break;
|
break;
|
||||||
@@ -128,15 +138,12 @@ bool connectToWifi()
|
|||||||
}
|
}
|
||||||
delay(tryDelay);
|
delay(tryDelay);
|
||||||
|
|
||||||
if (numberOfTries <= 0)
|
if (numberOfTries <= 0) {
|
||||||
{
|
|
||||||
Serial.print("[WiFi] Failed to connect to WiFi!");
|
Serial.print("[WiFi] Failed to connect to WiFi!");
|
||||||
// Use disconnect function to force stop trying to connect
|
// Use disconnect function to force stop trying to connect
|
||||||
WiFi.disconnect();
|
WiFi.disconnect();
|
||||||
return false;
|
return false;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
numberOfTries--;
|
numberOfTries--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,7 +151,8 @@ bool connectToWifi()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
|
void onEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
|
||||||
|
{
|
||||||
if (type == WS_EVT_CONNECT) {
|
if (type == WS_EVT_CONNECT) {
|
||||||
// client connected
|
// client connected
|
||||||
// Serial.printf("ws[%s][%u] connect\n", server->url(), client->id());
|
// Serial.printf("ws[%s][%u] connect\n", server->url(), client->id());
|
||||||
@@ -174,8 +182,7 @@ void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
|
|||||||
// }
|
// }
|
||||||
// Serial.printf("\n");
|
// Serial.printf("\n");
|
||||||
}
|
}
|
||||||
if(info->opcode == WS_TEXT)
|
if (info->opcode == WS_TEXT) {
|
||||||
{
|
|
||||||
client->text((char*)data, len);
|
client->text((char*)data, len);
|
||||||
}
|
}
|
||||||
// else
|
// else
|
||||||
@@ -203,8 +210,7 @@ void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp
|
|||||||
// Serial.printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, 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");
|
// 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)
|
if (info->message_opcode == WS_TEXT) {
|
||||||
{
|
|
||||||
client->text((char*)data, info->len);
|
client->text((char*)data, info->len);
|
||||||
}
|
}
|
||||||
// else
|
// else
|
||||||
@@ -223,36 +229,37 @@ void setup()
|
|||||||
|
|
||||||
// We start by connecting to a WiFi network
|
// We start by connecting to a WiFi network
|
||||||
// To debug, please enable Core Debug Level to Verbose
|
// To debug, please enable Core Debug Level to Verbose
|
||||||
if (connectToWifi())
|
if (connectToWifi()) {
|
||||||
{
|
// set up our esp32 to listen on the local_hostname.local domain
|
||||||
if(!LittleFS.begin())
|
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");
|
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// api - parameters passed in via query eg. /api/endpoint?foo=bar
|
// api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
|
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
{
|
// ESPAsyncWebServer, sending a char* does a buffer copy, unlike Psychic.
|
||||||
request->send(200, "text/html", htmlContent);
|
// 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
|
// api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||||
server.on("/api", HTTP_GET, [](AsyncWebServerRequest *request)
|
server.on("/api", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||||
{
|
|
||||||
// create a response object
|
// create a response object
|
||||||
StaticJsonDocument<128> output;
|
JsonDocument output;
|
||||||
output["msg"] = "status";
|
output["msg"] = "status";
|
||||||
output["status"] = "success";
|
output["status"] = "success";
|
||||||
output["millis"] = millis();
|
output["millis"] = millis();
|
||||||
|
|
||||||
// work with some params
|
// work with some params
|
||||||
if (request->hasParam("foo"))
|
if (request->hasParam("foo")) {
|
||||||
{
|
const AsyncWebParameter* foo = request->getParam("foo");
|
||||||
AsyncWebParameter* foo = request->getParam("foo");
|
|
||||||
output["foo"] = foo->value();
|
output["foo"] = foo->value();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,6 +272,10 @@ void setup()
|
|||||||
ws.onEvent(onEvent);
|
ws.onEvent(onEvent);
|
||||||
server.addHandler(&ws);
|
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();
|
server.begin();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,5 +283,6 @@ void setup()
|
|||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
ws.cleanupClients();
|
ws.cleanupClients();
|
||||||
|
Serial.printf("Free Heap: %d\n", esp_get_free_heap_size());
|
||||||
delay(1000);
|
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
|
#!/usr/bin/env node
|
||||||
|
//stress test the client opening/closing code
|
||||||
|
|
||||||
const EventSource = require('eventsource');
|
const EventSource = require('eventsource');
|
||||||
const url = 'http://192.168.2.131/events';
|
const url = 'http://psychic.local/events';
|
||||||
|
|
||||||
async function eventSourceClient() {
|
async function eventSourceClient() {
|
||||||
console.log(`Starting test`);
|
console.log(`Starting test`);
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
//stress test the http request code
|
||||||
|
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
|
||||||
const url = 'http://192.168.2.131/api';
|
const url = 'http://psychic.local/api';
|
||||||
const queryParams = {
|
const queryParams = {
|
||||||
foo: 'bar',
|
foo: 'bar',
|
||||||
foo1: 'bar',
|
foo1: 'bar',
|
||||||
|
|||||||
@@ -1,37 +1,42 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#Command to install the testers:
|
#Command to install the testers:
|
||||||
# npm install -g autocannon
|
# npm install
|
||||||
|
|
||||||
TEST_IP="192.168.2.131"
|
TEST_IP="psychic.local"
|
||||||
TEST_TIME=60
|
TEST_TIME=10
|
||||||
LOG_FILE=psychic-http-loadtest.log
|
#LOG_FILE=psychic-http-loadtest.log
|
||||||
|
LOG_FILE=_psychic-http-loadtest.json
|
||||||
|
RESULTS_FILE=http-loadtest-results.csv
|
||||||
TIMEOUT=10000
|
TIMEOUT=10000
|
||||||
|
WORKERS=1
|
||||||
PROTOCOL=http
|
PROTOCOL=http
|
||||||
#PROTOCOL=https
|
#PROTOCOL=https
|
||||||
|
|
||||||
if test -f "$LOG_FILE"; then
|
echo "url,connections,rps,latency,errors" > $RESULTS_FILE
|
||||||
rm $LOG_FILE
|
|
||||||
fi
|
|
||||||
|
|
||||||
for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20
|
for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20
|
||||||
#for CONCURRENCY in 20
|
|
||||||
do
|
do
|
||||||
printf "\n\nCLIENTS: *** $CONCURRENCY ***\n\n" >> $LOG_FILE
|
printf "\n\nCLIENTS: *** $CONCURRENCY ***\n\n" >> $LOG_FILE
|
||||||
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/"
|
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 $WORKERS -d $TEST_TIME -j "$PROTOCOL://$TEST_IP/" > $LOG_FILE
|
||||||
autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/" >> $LOG_FILE 2>&1
|
node parse-http-test.js $LOG_FILE $RESULTS_FILE
|
||||||
printf "\n\n----------------\n\n" >> $LOG_FILE
|
sleep 5
|
||||||
sleep 1
|
|
||||||
|
|
||||||
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/api"
|
|
||||||
#loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/api?foo=bar" --quiet >> $LOG_FILE
|
|
||||||
autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/api?foo=bar" >> $LOG_FILE 2>&1
|
|
||||||
printf "\n\n----------------\n\n" >> $LOG_FILE
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/alien.png"
|
|
||||||
#loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/alien.png" --quiet >> $LOG_FILE
|
|
||||||
autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/alien.png" >> $LOG_FILE 2>&1
|
|
||||||
printf "\n\n----------------\n\n" >> $LOG_FILE
|
|
||||||
sleep 1
|
|
||||||
done
|
done
|
||||||
|
|
||||||
|
for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20
|
||||||
|
do
|
||||||
|
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/api"
|
||||||
|
autocannon -c $CONCURRENCY -w $WORKERS -d $TEST_TIME -j "$PROTOCOL://$TEST_IP/api?foo=bar" > $LOG_FILE
|
||||||
|
node parse-http-test.js $LOG_FILE $RESULTS_FILE
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20
|
||||||
|
do
|
||||||
|
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/alien.png"
|
||||||
|
autocannon -c $CONCURRENCY -w $WORKERS -d $TEST_TIME -j "$PROTOCOL://$TEST_IP/alien.png" > $LOG_FILE
|
||||||
|
node parse-http-test.js $LOG_FILE $RESULTS_FILE
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
rm $LOG_FILE
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#Command to install the testers:
|
#Command to install the testers:
|
||||||
# npm install -g loadtest
|
# npm install
|
||||||
|
|
||||||
TEST_IP="192.168.2.131"
|
TEST_IP="psychic.local"
|
||||||
TEST_TIME=60
|
TEST_TIME=60
|
||||||
LOG_FILE=psychic-websocket-loadtest.log
|
LOG_FILE=psychic-websocket-loadtest.json
|
||||||
|
RESULTS_FILE=websocket-loadtest-results.csv
|
||||||
PROTOCOL=ws
|
PROTOCOL=ws
|
||||||
#PROTOCOL=wss
|
#PROTOCOL=wss
|
||||||
|
|
||||||
@@ -12,20 +13,33 @@ if test -f "$LOG_FILE"; then
|
|||||||
rm $LOG_FILE
|
rm $LOG_FILE
|
||||||
fi
|
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
|
do
|
||||||
printf "\n\nCLIENTS: *** $CONCURRENCY ***\n\n" >> $LOG_FILE
|
|
||||||
echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/ws"
|
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
|
loadtest -c $CONCURRENCY --cores $CORES -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
|
||||||
sleep 1
|
node parse-websocket-test.js $LOG_FILE $RESULTS_FILE
|
||||||
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
for CONNECTIONS in 8 10 16 20
|
CORES=2
|
||||||
#for CONNECTIONS in 20
|
for CONNECTIONS in 6 8 10 12 14
|
||||||
do
|
do
|
||||||
CONCURRENCY=$((CONNECTIONS / 2))
|
CONCURRENCY=$((CONNECTIONS / 2))
|
||||||
printf "\n\nCLIENTS: *** $CONNECTIONS ***\n\n" >> $LOG_FILE
|
|
||||||
echo "Testing $CONNECTIONS clients on $PROTOCOL://$TEST_IP/ws"
|
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
|
loadtest -c $CONCURRENCY --cores $CORES -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE
|
||||||
sleep 1
|
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
|
done
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"autocannon": "^7.15.0",
|
||||||
"axios": "^1.6.2",
|
"axios": "^1.6.2",
|
||||||
|
"csv-writer": "^1.6.0",
|
||||||
"eventsource": "^2.0.2",
|
"eventsource": "^2.0.2",
|
||||||
|
"loadtest": "^8.0.9",
|
||||||
"ws": "^8.14.2"
|
"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_speed = 115200
|
||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/hoeken/PsychicHttp
|
|
||||||
bblanchon/ArduinoJson
|
bblanchon/ArduinoJson
|
||||||
board_build.filesystem = littlefs
|
board_build.filesystem = littlefs
|
||||||
|
|
||||||
[env:default]
|
[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,12 +7,13 @@
|
|||||||
CONDITIONS OF ANY KIND, either express or implied.
|
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 "_secret.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <ESPmDNS.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
|
#include <PsychicHttp.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
|
||||||
#ifndef WIFI_SSID
|
#ifndef WIFI_SSID
|
||||||
#error "You need to enter your wifi credentials. Copy secret.h to _secret.h and enter your credentials there."
|
#error "You need to enter your wifi credentials. Copy secret.h to _secret.h and enter your credentials there."
|
||||||
@@ -22,6 +23,9 @@
|
|||||||
const char* ssid = WIFI_SSID;
|
const char* ssid = WIFI_SSID;
|
||||||
const char* password = WIFI_PASS;
|
const char* password = WIFI_PASS;
|
||||||
|
|
||||||
|
// hostname for mdns (psychic.local)
|
||||||
|
const char* local_hostname = "psychic";
|
||||||
|
|
||||||
PsychicHttpServer server;
|
PsychicHttpServer server;
|
||||||
PsychicWebSocketHandler websocketHandler;
|
PsychicWebSocketHandler websocketHandler;
|
||||||
PsychicEventSource eventSource;
|
PsychicEventSource eventSource;
|
||||||
@@ -92,8 +96,8 @@ bool connectToWifi()
|
|||||||
Serial.print("[WiFi] Connecting to ");
|
Serial.print("[WiFi] Connecting to ");
|
||||||
Serial.println(ssid);
|
Serial.println(ssid);
|
||||||
|
|
||||||
WiFi.setSleep(false);
|
// WiFi.setSleep(false);
|
||||||
WiFi.useStaticBuffers(true);
|
// WiFi.useStaticBuffers(true);
|
||||||
|
|
||||||
WiFi.begin(ssid, password);
|
WiFi.begin(ssid, password);
|
||||||
|
|
||||||
@@ -102,10 +106,8 @@ bool connectToWifi()
|
|||||||
int numberOfTries = 20;
|
int numberOfTries = 20;
|
||||||
|
|
||||||
// Wait for the WiFi event
|
// Wait for the WiFi event
|
||||||
while (true)
|
while (true) {
|
||||||
{
|
switch (WiFi.status()) {
|
||||||
switch (WiFi.status())
|
|
||||||
{
|
|
||||||
case WL_NO_SSID_AVAIL:
|
case WL_NO_SSID_AVAIL:
|
||||||
Serial.println("[WiFi] SSID not found");
|
Serial.println("[WiFi] SSID not found");
|
||||||
break;
|
break;
|
||||||
@@ -135,15 +137,12 @@ bool connectToWifi()
|
|||||||
}
|
}
|
||||||
delay(tryDelay);
|
delay(tryDelay);
|
||||||
|
|
||||||
if (numberOfTries <= 0)
|
if (numberOfTries <= 0) {
|
||||||
{
|
|
||||||
Serial.print("[WiFi] Failed to connect to WiFi!");
|
Serial.print("[WiFi] Failed to connect to WiFi!");
|
||||||
// Use disconnect function to force stop trying to connect
|
// Use disconnect function to force stop trying to connect
|
||||||
WiFi.disconnect();
|
WiFi.disconnect();
|
||||||
return false;
|
return false;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
numberOfTries--;
|
numberOfTries--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,47 +156,42 @@ void setup()
|
|||||||
delay(10);
|
delay(10);
|
||||||
Serial.println("PsychicHTTP Benchmark");
|
Serial.println("PsychicHTTP Benchmark");
|
||||||
|
|
||||||
if (connectToWifi())
|
if (connectToWifi()) {
|
||||||
{
|
// set up our esp32 to listen on the local_hostname.local domain
|
||||||
if(!LittleFS.begin())
|
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");
|
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//start our server
|
|
||||||
server.listen(80);
|
|
||||||
|
|
||||||
// our index
|
// our index
|
||||||
server.on("/", HTTP_GET, [](PsychicRequest *request)
|
server.on("/", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->send(200, "text/html", htmlContent); });
|
||||||
{
|
|
||||||
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/");
|
server.serveStatic("/", LittleFS, "/www/");
|
||||||
|
|
||||||
// a websocket echo server
|
// a websocket echo server
|
||||||
websocketHandler.onOpen([](PsychicWebSocketClient* client) {
|
websocketHandler.onOpen([](PsychicWebSocketClient* client) {
|
||||||
client->sendMessage("Hello!");
|
// client->sendMessage("Hello!");
|
||||||
});
|
});
|
||||||
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) {
|
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) {
|
||||||
request->reply(frame);
|
response->send(frame);
|
||||||
return ESP_OK;
|
return ESP_OK; });
|
||||||
});
|
|
||||||
server.on("/ws", &websocketHandler);
|
server.on("/ws", &websocketHandler);
|
||||||
|
|
||||||
// EventSource server
|
// EventSource server
|
||||||
eventSource.onOpen([](PsychicEventSourceClient *client) {
|
eventSource.onOpen([](PsychicEventSourceClient* client) { client->send("Hello", NULL, millis(), 1000); });
|
||||||
client->send("Hello", NULL, millis(), 1000);
|
|
||||||
});
|
|
||||||
server.on("/events", &eventSource);
|
server.on("/events", &eventSource);
|
||||||
|
|
||||||
// api - parameters passed in via query eg. /api/endpoint?foo=bar
|
// 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
|
//create a response object
|
||||||
StaticJsonDocument<128> output;
|
JsonDocument output;
|
||||||
output["msg"] = "status";
|
output["msg"] = "status";
|
||||||
output["status"] = "success";
|
output["status"] = "success";
|
||||||
output["millis"] = millis();
|
output["millis"] = millis();
|
||||||
@@ -212,16 +206,16 @@ void setup()
|
|||||||
//serialize and return
|
//serialize and return
|
||||||
String jsonBuffer;
|
String jsonBuffer;
|
||||||
serializeJson(output, 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;
|
unsigned long last;
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
if (millis() - last > 1000)
|
if (millis() - last > 1000) {
|
||||||
{
|
|
||||||
Serial.printf("Free Heap: %d\n", esp_get_free_heap_size());
|
Serial.printf("Free Heap: %d\n", esp_get_free_heap_size());
|
||||||
last = millis();
|
last = millis();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,13 @@
|
|||||||
CONDITIONS OF ANY KIND, either express or implied.
|
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 "_secret.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ArduinoJSON.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
#include <PsychicHttp.h>
|
#include <PsychicHttp.h>
|
||||||
#include <PsychicHttpsServer.h>
|
#include <PsychicHttpsServer.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
|
||||||
#ifndef WIFI_SSID
|
#ifndef WIFI_SSID
|
||||||
#error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there."
|
#error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there."
|
||||||
@@ -169,40 +168,44 @@ void setup()
|
|||||||
}
|
}
|
||||||
|
|
||||||
File fp = LittleFS.open("/server.crt");
|
File fp = LittleFS.open("/server.crt");
|
||||||
if (fp) {
|
if (fp)
|
||||||
|
{
|
||||||
server_cert = fp.readString();
|
server_cert = fp.readString();
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
Serial.println("server.pem not found, SSL not available");
|
Serial.println("server.pem not found, SSL not available");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fp.close();
|
fp.close();
|
||||||
|
|
||||||
File fp2 = LittleFS.open("/server.key");
|
File fp2 = LittleFS.open("/server.key");
|
||||||
if (fp2) {
|
if (fp2)
|
||||||
|
{
|
||||||
server_key = fp2.readString();
|
server_key = fp2.readString();
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
Serial.println("server.key not found, SSL not available");
|
Serial.println("server.key not found, SSL not available");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fp2.close();
|
fp2.close();
|
||||||
|
|
||||||
// start our server
|
// start our server
|
||||||
server.listen(443, server_cert.c_str(), server_key.c_str());
|
server.setCertificate(server_cert.c_str(), server_key.c_str());
|
||||||
|
|
||||||
// our index
|
// our index
|
||||||
server.on("/", HTTP_GET, [](PsychicRequest* request)
|
server.on("/", HTTP_GET, [](PsychicRequest* request)
|
||||||
{
|
{ return response->send(200, "text/html", htmlContent); });
|
||||||
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/");
|
server.serveStatic("/", LittleFS, "/www/");
|
||||||
|
|
||||||
// a websocket echo server
|
// a websocket echo server
|
||||||
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
|
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame)
|
||||||
request->reply(frame);
|
{
|
||||||
return ESP_OK;
|
response->send(frame);
|
||||||
});
|
return ESP_OK; });
|
||||||
server.on("/ws", &websocketHandler);
|
server.on("/ws", &websocketHandler);
|
||||||
|
|
||||||
// api - parameters passed in via query eg. /api/endpoint?foo=bar
|
// api - parameters passed in via query eg. /api/endpoint?foo=bar
|
||||||
@@ -224,8 +227,7 @@ void setup()
|
|||||||
//serialize and return
|
//serialize and return
|
||||||
String jsonBuffer;
|
String jsonBuffer;
|
||||||
serializeJson(output, 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
|
#!/usr/bin/env node
|
||||||
|
//stress test the client open/close for websockets
|
||||||
|
|
||||||
const WebSocket = require('ws');
|
const WebSocket = require('ws');
|
||||||
|
|
||||||
const uri = 'ws://192.168.2.131/ws';
|
const uri = 'ws://psychic.local/ws';
|
||||||
|
|
||||||
async function websocketClient() {
|
async function websocketClient() {
|
||||||
console.log(`Starting test`);
|
console.log(`Starting test`);
|
||||||
|
|||||||
@@ -206,30 +206,25 @@ void setup()
|
|||||||
//do we want secure or not?
|
//do we want secure or not?
|
||||||
if (app_enable_ssl)
|
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
|
//this creates a 2nd server listening on port 80 and redirects all requests HTTPS
|
||||||
PsychicHttpServer *redirectServer = new PsychicHttpServer();
|
PsychicHttpServer *redirectServer = new PsychicHttpServer();
|
||||||
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
|
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
|
||||||
redirectServer->listen(80);
|
redirectServer->onNotFound([](PsychicRequest *request, PsychicResponse *response) {
|
||||||
redirectServer->onNotFound([](PsychicRequest *request) {
|
|
||||||
String url = "https://" + request->host() + request->url();
|
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
|
#endif
|
||||||
|
|
||||||
//serve static files from LittleFS/www on / only to clients on same wifi network
|
//serve static files from LittleFS/www on / only to clients on same wifi network
|
||||||
//this is where our /index.html file lives
|
//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
|
//serve static files from LittleFS/www-ap on / only to clients on SoftAP
|
||||||
//this is where our /index.html file lives
|
//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
|
//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.
|
//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
|
//example callback everytime a connection is opened
|
||||||
server.onOpen([](PsychicClient *client) {
|
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
|
//example callback everytime a connection is closed
|
||||||
server.onClose([](PsychicClient *client) {
|
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
|
//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
|
//load our JSON request
|
||||||
StaticJsonDocument<1024> json;
|
StaticJsonDocument<1024> json;
|
||||||
@@ -272,18 +267,18 @@ void setup()
|
|||||||
//serialize and return
|
//serialize and return
|
||||||
String jsonBuffer;
|
String jsonBuffer;
|
||||||
serializeJson(output, 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
|
//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();
|
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
|
//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
|
//create a response object
|
||||||
StaticJsonDocument<128> output;
|
StaticJsonDocument<128> output;
|
||||||
@@ -301,65 +296,64 @@ void setup()
|
|||||||
//serialize and return
|
//serialize and return
|
||||||
String jsonBuffer;
|
String jsonBuffer;
|
||||||
serializeJson(output, 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
|
//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
|
//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))
|
if (!request->authenticate(app_user, app_pass))
|
||||||
return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in.");
|
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
|
//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))
|
if (!request->authenticate(app_user, app_pass))
|
||||||
return request->requestAuthentication(DIGEST_AUTH, app_name, "You must log in.");
|
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
|
//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;
|
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++;
|
counter++;
|
||||||
}
|
}
|
||||||
|
sprintf(cookie, "%d", counter);
|
||||||
|
|
||||||
char cookie[10];
|
response->setCookie("counter", cookie);
|
||||||
sprintf(cookie, "%i", counter);
|
response->setContent(cookie);
|
||||||
|
return response->send();
|
||||||
response.setCookie("counter", cookie);
|
|
||||||
response.setContent(cookie);
|
|
||||||
return response.send();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//example of getting POST variables
|
//example of getting POST variables
|
||||||
server.on("/post", HTTP_POST, [](PsychicRequest *request)
|
server.on("/post", HTTP_POST, [](PsychicRequest *request, PsychicResponse *response)
|
||||||
{
|
{
|
||||||
String output;
|
String output;
|
||||||
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
||||||
output += "Param 2: " + request->getParam("param2")->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.
|
//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
|
//handle a very basic upload as post body
|
||||||
@@ -393,12 +387,12 @@ void setup()
|
|||||||
});
|
});
|
||||||
|
|
||||||
//gets called after upload has been handled
|
//gets called after upload has been handled
|
||||||
uploadHandler->onRequest([](PsychicRequest *request)
|
uploadHandler->onRequest([](PsychicRequest *request, PsychicResponse *response)
|
||||||
{
|
{
|
||||||
String url = "/" + request->getFilename();
|
String url = "/" + request->getFilename();
|
||||||
String output = "<a href=\"" + url + "\">" + url + "</a>";
|
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
|
||||||
@@ -435,7 +429,7 @@ void setup()
|
|||||||
});
|
});
|
||||||
|
|
||||||
//gets called after upload has been handled
|
//gets called after upload has been handled
|
||||||
multipartHandler->onRequest([](PsychicRequest *request)
|
multipartHandler->onRequest([](PsychicRequest *request, PsychicResponse *response)
|
||||||
{
|
{
|
||||||
PsychicWebParameter *file = request->getParam("file_upload");
|
PsychicWebParameter *file = request->getParam("file_upload");
|
||||||
|
|
||||||
@@ -447,7 +441,7 @@ void setup()
|
|||||||
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
||||||
output += "Param 2: " + request->getParam("param2")->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
|
||||||
@@ -455,7 +449,7 @@ void setup()
|
|||||||
|
|
||||||
//a websocket echo server
|
//a websocket echo server
|
||||||
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
|
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!");
|
client->sendMessage("Hello!");
|
||||||
});
|
});
|
||||||
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
|
websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) {
|
||||||
@@ -463,17 +457,17 @@ void setup()
|
|||||||
return request->reply(frame);
|
return request->reply(frame);
|
||||||
});
|
});
|
||||||
websocketHandler.onClose([](PsychicWebSocketClient *client) {
|
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);
|
server.on("/ws", &websocketHandler);
|
||||||
|
|
||||||
//EventSource server
|
//EventSource server
|
||||||
eventSource.onOpen([](PsychicEventSourceClient *client) {
|
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);
|
client->send("Hello user!", NULL, millis(), 1000);
|
||||||
});
|
});
|
||||||
eventSource.onClose([](PsychicEventSourceClient *client) {
|
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);
|
server.on("/events", &eventSource);
|
||||||
}
|
}
|
||||||
@@ -486,10 +480,10 @@ void loop()
|
|||||||
{
|
{
|
||||||
if (millis() - lastUpdate > 2000)
|
if (millis() - lastUpdate > 2000)
|
||||||
{
|
{
|
||||||
sprintf(output, "Millis: %d\n", millis());
|
sprintf(output, "Millis: %lu\n", millis());
|
||||||
websocketHandler.sendAll(output);
|
websocketHandler.sendAll(output);
|
||||||
|
|
||||||
sprintf(output, "%d", millis());
|
sprintf(output, "%lu", millis());
|
||||||
eventSource.send(output, "millis", millis(), 0);
|
eventSource.send(output, "millis", millis(), 0);
|
||||||
|
|
||||||
lastUpdate = millis();
|
lastUpdate = millis();
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public:
|
|||||||
esp_err_t handleRequest(PsychicRequest *request) {
|
esp_err_t handleRequest(PsychicRequest *request) {
|
||||||
//PsychicFileResponse response(request, LittleFS, "/captiveportal.html"); // uncomment : for captive portal page, if any, eg "captiveportal.html"
|
//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 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
|
CaptiveRequestHandler *captivehandler=NULL; // handler for captive portal
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
#include <PsychicHttp.h>
|
#include <PsychicHttp.h>
|
||||||
|
|
||||||
char* TAG = "CAPTPORT";
|
#define TAG "CAPTPORT"
|
||||||
|
|
||||||
// captiveportal
|
// captiveportal
|
||||||
// credits https://github.com/me-no-dev/ESPAsyncWebServer/blob/master/examples/CaptivePortal/CaptivePortal.ino
|
// 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);
|
// ... if needed some tests ... return(false);
|
||||||
return true; // activate captive portal
|
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"
|
//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 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
|
CaptiveRequestHandler *captivehandler=NULL; // handler for captive portal
|
||||||
@@ -128,7 +128,6 @@ void setup() {
|
|||||||
|
|
||||||
//setup server config stuff here
|
//setup server config stuff here
|
||||||
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
|
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
|
||||||
server.listen(80);
|
|
||||||
|
|
||||||
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
|
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
CONDITIONS OF ANY KIND, either express or implied.
|
CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
char *TAG = "OTA"; // ESP_LOG tag
|
#define TAG "OTA" // ESP_LOG tag
|
||||||
|
|
||||||
// PsychicHttp
|
// PsychicHttp
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
@@ -100,7 +100,6 @@ void setup()
|
|||||||
|
|
||||||
//setup server config stuff here
|
//setup server config stuff here
|
||||||
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
|
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
|
||||||
server.listen(80);
|
|
||||||
|
|
||||||
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
|
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
|
||||||
|
|
||||||
@@ -109,8 +108,8 @@ void setup()
|
|||||||
|
|
||||||
//you can set up a custom 404 handler.
|
//you can set up a custom 404 handler.
|
||||||
// curl -i http://psychic.local/404
|
// curl -i http://psychic.local/404
|
||||||
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");
|
||||||
});
|
});
|
||||||
|
|
||||||
// OTA
|
// OTA
|
||||||
@@ -177,34 +176,34 @@ void setup()
|
|||||||
}
|
}
|
||||||
}); // end onUpload
|
}); // 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
|
String result; // request result
|
||||||
// code below is executed when update is finished
|
// code below is executed when update is finished
|
||||||
if (!Update.hasError()) { // update is OK
|
if (!Update.hasError()) { // update is OK
|
||||||
ESP_LOGI(TAG,"Update code or data OK Update.errorString() %s", Update.errorString());
|
ESP_LOGI(TAG,"Update code or data OK Update.errorString() %s", Update.errorString());
|
||||||
result = "<b style='color:green'>Update done for file.</b>";
|
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
|
// ESP.restart(); // restart ESP if needed
|
||||||
} // end update is OK
|
} // end update is OK
|
||||||
else { // update is KO, send request with pretty print error
|
else { // update is KO, send request with pretty print error
|
||||||
result = " Update.errorString() " + String(Update.errorString());
|
result = " Update.errorString() " + String(Update.errorString());
|
||||||
ESP_LOGE(TAG,"ERROR : error %s",result.c_str());
|
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
|
} // end update is KO
|
||||||
});
|
});
|
||||||
|
|
||||||
server.on("/update", HTTP_GET, [](PsychicRequest*request){
|
server.on("/update", HTTP_GET, [](PsychicRequest*request, PsychicResponse *res){
|
||||||
PsychicFileResponse response(request, LittleFS, "/update.html");
|
PsychicFileResponse response(res, LittleFS, "/update.html");
|
||||||
return response.send();
|
return response.send();
|
||||||
});
|
});
|
||||||
|
|
||||||
server.on("/update", HTTP_POST, updateHandler);
|
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>";
|
String output = "<b style='color:green'>Restarting ...</b>";
|
||||||
ESP_LOGI(TAG,"%s",output.c_str());
|
ESP_LOGI(TAG,"%s",output.c_str());
|
||||||
esprestart=true;
|
esprestart=true;
|
||||||
return request->reply(output.c_str());
|
return response->send(output.c_str());
|
||||||
});
|
});
|
||||||
} // end onRequest
|
} // end onRequest
|
||||||
|
|
||||||
|
|||||||
@@ -18,13 +18,13 @@
|
|||||||
* Follow instructions here: https://randomnerdtutorials.com/esp32-littlefs-arduino-ide/
|
* Follow instructions here: https://randomnerdtutorials.com/esp32-littlefs-arduino-ide/
|
||||||
**********************************************************************************************/
|
**********************************************************************************************/
|
||||||
|
|
||||||
|
#include "secret.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <WiFi.h>
|
|
||||||
#include <LittleFS.h>
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
#include "secret.h"
|
#include <LittleFS.h>
|
||||||
#include <PsychicHttp.h>
|
#include <PsychicHttp.h>
|
||||||
|
#include <WiFi.h>
|
||||||
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE // set this to y in menuconfig to enable SSL
|
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE // set this to y in menuconfig to enable SSL
|
||||||
#include <PsychicHttpsServer.h>
|
#include <PsychicHttpsServer.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -47,6 +47,9 @@ const char *app_user = "admin";
|
|||||||
const char* app_pass = "admin";
|
const char* app_pass = "admin";
|
||||||
const char* app_name = "Your App";
|
const char* app_name = "Your App";
|
||||||
|
|
||||||
|
AuthenticationMiddleware basicAuth;
|
||||||
|
AuthenticationMiddleware digestAuth;
|
||||||
|
|
||||||
// hostname for mdns (psychic.local)
|
// hostname for mdns (psychic.local)
|
||||||
const char* local_hostname = "psychic";
|
const char* local_hostname = "psychic";
|
||||||
|
|
||||||
@@ -92,10 +95,8 @@ bool connectToWifi()
|
|||||||
int numberOfTries = 20;
|
int numberOfTries = 20;
|
||||||
|
|
||||||
// Wait for the WiFi event
|
// Wait for the WiFi event
|
||||||
while (true)
|
while (true) {
|
||||||
{
|
switch (WiFi.status()) {
|
||||||
switch (WiFi.status())
|
|
||||||
{
|
|
||||||
case WL_NO_SSID_AVAIL:
|
case WL_NO_SSID_AVAIL:
|
||||||
Serial.println("[WiFi] SSID not found");
|
Serial.println("[WiFi] SSID not found");
|
||||||
break;
|
break;
|
||||||
@@ -125,15 +126,12 @@ bool connectToWifi()
|
|||||||
}
|
}
|
||||||
delay(tryDelay);
|
delay(tryDelay);
|
||||||
|
|
||||||
if (numberOfTries <= 0)
|
if (numberOfTries <= 0) {
|
||||||
{
|
|
||||||
Serial.print("[WiFi] Failed to connect to WiFi!");
|
Serial.print("[WiFi] Failed to connect to WiFi!");
|
||||||
// Use disconnect function to force stop trying to connect
|
// Use disconnect function to force stop trying to connect
|
||||||
WiFi.disconnect();
|
WiFi.disconnect();
|
||||||
return false;
|
return false;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
numberOfTries--;
|
numberOfTries--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,8 +146,7 @@ void setup()
|
|||||||
|
|
||||||
// We start by connecting to a WiFi network
|
// We start by connecting to a WiFi network
|
||||||
// To debug, please enable Core Debug Level to Verbose
|
// To debug, please enable Core Debug Level to Verbose
|
||||||
if (connectToWifi())
|
if (connectToWifi()) {
|
||||||
{
|
|
||||||
// set up our esp32 to listen on the local_hostname.local domain
|
// set up our esp32 to listen on the local_hostname.local domain
|
||||||
if (!MDNS.begin(local_hostname)) {
|
if (!MDNS.begin(local_hostname)) {
|
||||||
Serial.println("Error starting mDNS");
|
Serial.println("Error starting mDNS");
|
||||||
@@ -157,41 +154,33 @@ void setup()
|
|||||||
}
|
}
|
||||||
MDNS.addService("http", "tcp", 80);
|
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");
|
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// look up our keys?
|
// look up our keys?
|
||||||
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
|
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
|
||||||
if (app_enable_ssl)
|
if (app_enable_ssl) {
|
||||||
{
|
|
||||||
File fp = LittleFS.open("/server.crt");
|
File fp = LittleFS.open("/server.crt");
|
||||||
if (fp)
|
if (fp) {
|
||||||
{
|
|
||||||
server_cert = fp.readString();
|
server_cert = fp.readString();
|
||||||
|
|
||||||
// Serial.println("Server Cert:");
|
// Serial.println("Server Cert:");
|
||||||
// Serial.println(server_cert);
|
// Serial.println(server_cert);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
Serial.println("server.pem not found, SSL not available");
|
Serial.println("server.pem not found, SSL not available");
|
||||||
app_enable_ssl = false;
|
app_enable_ssl = false;
|
||||||
}
|
}
|
||||||
fp.close();
|
fp.close();
|
||||||
|
|
||||||
File fp2 = LittleFS.open("/server.key");
|
File fp2 = LittleFS.open("/server.key");
|
||||||
if (fp2)
|
if (fp2) {
|
||||||
{
|
|
||||||
server_key = fp2.readString();
|
server_key = fp2.readString();
|
||||||
|
|
||||||
// Serial.println("Server Key:");
|
// Serial.println("Server Key:");
|
||||||
// Serial.println(server_key);
|
// Serial.println(server_key);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
Serial.println("server.key not found, SSL not available");
|
Serial.println("server.key not found, SSL not available");
|
||||||
app_enable_ssl = false;
|
app_enable_ssl = false;
|
||||||
}
|
}
|
||||||
@@ -206,32 +195,37 @@ void setup()
|
|||||||
server.ssl_config.httpd.max_uri_handlers = 20; // maximum number of uri handlers (.on() calls)
|
server.ssl_config.httpd.max_uri_handlers = 20; // maximum number of uri handlers (.on() calls)
|
||||||
|
|
||||||
// do we want secure or not?
|
// do we want secure or not?
|
||||||
if (app_enable_ssl)
|
if (app_enable_ssl) {
|
||||||
{
|
server.setCertificate(server_cert.c_str(), server_key.c_str());
|
||||||
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
|
// this creates a 2nd server listening on port 80 and redirects all requests HTTPS
|
||||||
PsychicHttpServer* redirectServer = new PsychicHttpServer();
|
PsychicHttpServer* redirectServer = new PsychicHttpServer();
|
||||||
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
|
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
|
||||||
redirectServer->listen(80);
|
redirectServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) {
|
||||||
redirectServer->onNotFound([](PsychicRequest *request) {
|
|
||||||
String url = "https://" + request->host() + request->url();
|
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
|
#endif
|
||||||
|
|
||||||
|
basicAuth.setUsername(app_user);
|
||||||
|
basicAuth.setPassword(app_pass);
|
||||||
|
basicAuth.setRealm(app_name);
|
||||||
|
basicAuth.setAuthMethod(HTTPAuthMethod::BASIC_AUTH);
|
||||||
|
basicAuth.setAuthFailureMessage("You must log in.");
|
||||||
|
|
||||||
|
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/www on / only to clients on same wifi network
|
// serve static files from LittleFS/www on / only to clients on same wifi network
|
||||||
// this is where our /index.html file lives
|
// 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
|
// serve static files from LittleFS/www-ap on / only to clients on SoftAP
|
||||||
// this is where our /index.html file lives
|
// 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
|
// 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.
|
// it's more efficient to serve everything from a single www directory, but this is also possible.
|
||||||
@@ -241,18 +235,13 @@ void setup()
|
|||||||
server.serveStatic("/myfile.txt", LittleFS, "/custom.txt");
|
server.serveStatic("/myfile.txt", LittleFS, "/custom.txt");
|
||||||
|
|
||||||
// example callback everytime a connection is opened
|
// example callback everytime a connection is opened
|
||||||
server.onOpen([](PsychicClient *client) {
|
server.onOpen([](PsychicClient* client) { Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str()); });
|
||||||
Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
|
|
||||||
});
|
|
||||||
|
|
||||||
// example callback everytime a connection is closed
|
// example callback everytime a connection is closed
|
||||||
server.onClose([](PsychicClient *client) {
|
server.onClose([](PsychicClient* client) { Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); });
|
||||||
Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str());
|
|
||||||
});
|
|
||||||
|
|
||||||
// api - json message passed in as post body
|
// 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
|
//load our JSON request
|
||||||
JsonDocument json;
|
JsonDocument json;
|
||||||
String body = request->body();
|
String body = request->body();
|
||||||
@@ -284,19 +273,15 @@ void setup()
|
|||||||
//serialize and return
|
//serialize and return
|
||||||
String jsonBuffer;
|
String jsonBuffer;
|
||||||
serializeJson(output, 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
|
// 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();
|
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
|
// 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
|
//create a response object
|
||||||
JsonDocument output;
|
JsonDocument output;
|
||||||
output["msg"] = "status";
|
output["msg"] = "status";
|
||||||
@@ -313,66 +298,44 @@ void setup()
|
|||||||
//serialize and return
|
//serialize and return
|
||||||
String jsonBuffer;
|
String jsonBuffer;
|
||||||
serializeJson(output, 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
|
// how to redirect a request
|
||||||
server.on("/redirect", HTTP_GET, [](PsychicRequest *request)
|
server.on("/redirect", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->redirect("/alien.png"); });
|
||||||
{
|
|
||||||
return request->redirect("/alien.png");
|
|
||||||
});
|
|
||||||
|
|
||||||
// how to do basic auth
|
// how to do basic auth
|
||||||
server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request)
|
server.on("/auth-basic", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->send("Auth Basic Success!"); })->addMiddleware(&basicAuth);
|
||||||
{
|
|
||||||
if (!request->authenticate(app_user, app_pass))
|
|
||||||
return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in.");
|
|
||||||
return request->reply("Auth Basic Success!");
|
|
||||||
});
|
|
||||||
|
|
||||||
// how to do digest auth
|
// how to do digest auth
|
||||||
server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request)
|
server.on("/auth-digest", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->send("Auth Digest Success!"); })->addMiddleware(&digestAuth);
|
||||||
{
|
|
||||||
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
|
// 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;
|
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++;
|
counter++;
|
||||||
}
|
}
|
||||||
|
sprintf(cookie, "%d", counter);
|
||||||
|
|
||||||
char cookie[12];
|
response->setCookie("counter", cookie);
|
||||||
sprintf(cookie, "%i", counter);
|
response->setContent(cookie);
|
||||||
|
return response->send(); });
|
||||||
response.setCookie("counter", cookie);
|
|
||||||
response.setContent(cookie);
|
|
||||||
return response.send();
|
|
||||||
});
|
|
||||||
|
|
||||||
// example of getting POST variables
|
// example of getting POST variables
|
||||||
server.on("/post", HTTP_POST, [](PsychicRequest *request)
|
server.on("/post", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) {
|
||||||
{
|
|
||||||
String output;
|
String output;
|
||||||
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
||||||
output += "Param 2: " + request->getParam("param2")->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.
|
// you can set up a custom 404 handler.
|
||||||
server.onNotFound([](PsychicRequest *request)
|
server.onNotFound([](PsychicRequest* request, PsychicResponse* response) { return response->send(404, "text/html", "Custom 404 Handler"); });
|
||||||
{
|
|
||||||
return request->reply(404, "text/html", "Custom 404 Handler");
|
|
||||||
});
|
|
||||||
|
|
||||||
// handle a very basic upload as post body
|
// handle a very basic upload as post body
|
||||||
PsychicUploadHandler* uploadHandler = new PsychicUploadHandler();
|
PsychicUploadHandler* uploadHandler = new PsychicUploadHandler();
|
||||||
@@ -401,17 +364,14 @@ void setup()
|
|||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK; });
|
||||||
});
|
|
||||||
|
|
||||||
// gets called after upload has been handled
|
// gets called after upload has been handled
|
||||||
uploadHandler->onRequest([](PsychicRequest *request)
|
uploadHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) {
|
||||||
{
|
|
||||||
String url = "/" + request->getFilename();
|
String url = "/" + request->getFilename();
|
||||||
String output = "<a href=\"" + url + "\">" + url + "</a>";
|
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);
|
server.on("/upload/*", HTTP_POST, uploadHandler);
|
||||||
@@ -443,12 +403,10 @@ void setup()
|
|||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK; });
|
||||||
});
|
|
||||||
|
|
||||||
// gets called after upload has been handled
|
// gets called after upload has been handled
|
||||||
multipartHandler->onRequest([](PsychicRequest *request)
|
multipartHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) {
|
||||||
{
|
|
||||||
PsychicWebParameter *file = request->getParam("file_upload");
|
PsychicWebParameter *file = request->getParam("file_upload");
|
||||||
|
|
||||||
String url = "/" + file->value();
|
String url = "/" + file->value();
|
||||||
@@ -459,8 +417,7 @@ void setup()
|
|||||||
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
||||||
output += "Param 2: " + request->getParam("param2")->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);
|
server.on("/multipart", HTTP_POST, multipartHandler);
|
||||||
@@ -468,25 +425,18 @@ void setup()
|
|||||||
// a websocket echo server
|
// a websocket echo server
|
||||||
websocketHandler.onOpen([](PsychicWebSocketClient* client) {
|
websocketHandler.onOpen([](PsychicWebSocketClient* client) {
|
||||||
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
|
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
|
||||||
client->sendMessage("Hello!");
|
client->sendMessage("Hello!"); });
|
||||||
});
|
|
||||||
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) {
|
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) {
|
||||||
Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload);
|
Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload);
|
||||||
return request->reply(frame);
|
return request->reply(frame); });
|
||||||
});
|
websocketHandler.onClose([](PsychicWebSocketClient* client) { Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); });
|
||||||
websocketHandler.onClose([](PsychicWebSocketClient *client) {
|
|
||||||
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str());
|
|
||||||
});
|
|
||||||
server.on("/ws", &websocketHandler);
|
server.on("/ws", &websocketHandler);
|
||||||
|
|
||||||
// EventSource server
|
// EventSource server
|
||||||
eventSource.onOpen([](PsychicEventSourceClient* client) {
|
eventSource.onOpen([](PsychicEventSourceClient* client) {
|
||||||
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
|
Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str());
|
||||||
client->send("Hello user!", NULL, millis(), 1000);
|
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()); });
|
||||||
eventSource.onClose([](PsychicEventSourceClient *client) {
|
|
||||||
Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str());
|
|
||||||
});
|
|
||||||
server.on("/events", &eventSource);
|
server.on("/events", &eventSource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -496,8 +446,7 @@ char output[60];
|
|||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
if (millis() - lastUpdate > 2000)
|
if (millis() - lastUpdate > 2000) {
|
||||||
{
|
|
||||||
sprintf(output, "Millis: %lu\n", millis());
|
sprintf(output, "Millis: %lu\n", millis());
|
||||||
websocketHandler.sendAll(output);
|
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,11 +1,13 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>PsychicHTTP Demo</title>
|
<title>PsychicHTTP Demo</title>
|
||||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<h1>Basic Request Examples</h1>
|
<h1>Basic Request Examples</h1>
|
||||||
@@ -18,6 +20,11 @@
|
|||||||
<li><a href="/404">404</a></li>
|
<li><a href="/404">404</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<h1>Utilities</h1>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/websocket-test.html">WebSocket Tester</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<h1>Static Serving</h1>
|
<h1>Static Serving</h1>
|
||||||
<p>
|
<p>
|
||||||
<a href="/alien.png"><img width="60" src="/alien.png"></a>
|
<a href="/alien.png"><img width="60" src="/alien.png"></a>
|
||||||
@@ -124,7 +131,8 @@
|
|||||||
<br>
|
<br>
|
||||||
|
|
||||||
<label for="file-upload">File Upload:</label>
|
<label for="file-upload">File Upload:</label>
|
||||||
<input type="file" id="file-upload" name="file_upload" accept=".txt, .html, .pdf, .png, .jpg, .gif" required>
|
<input type="file" id="file-upload" name="file_upload" accept=".txt, .html, .pdf, .png, .jpg, .gif"
|
||||||
|
required>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<input type="submit" value="Upload File">
|
<input type="submit" value="Upload File">
|
||||||
@@ -143,8 +151,7 @@
|
|||||||
const outputText = document.getElementById('websocket_output');
|
const outputText = document.getElementById('websocket_output');
|
||||||
const messageInput = document.getElementById('message_input');
|
const messageInput = document.getElementById('message_input');
|
||||||
|
|
||||||
function websocketConnect()
|
function websocketConnect() {
|
||||||
{
|
|
||||||
// Create a WebSocket connection
|
// Create a WebSocket connection
|
||||||
socket = new WebSocket((location.protocol === "https:" ? "wss://" : "ws://") + window.location.host + "/ws");
|
socket = new WebSocket((location.protocol === "https:" ? "wss://" : "ws://") + window.location.host + "/ws");
|
||||||
|
|
||||||
@@ -186,8 +193,7 @@
|
|||||||
outputText.scrollTop = outputText.scrollHeight;
|
outputText.scrollTop = outputText.scrollHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
outputText.value += `[error] Not Connected\n`;
|
outputText.value += `[error] Not Connected\n`;
|
||||||
outputText.scrollTop = outputText.scrollHeight;
|
outputText.scrollTop = outputText.scrollHeight;
|
||||||
}
|
}
|
||||||
@@ -203,8 +209,7 @@
|
|||||||
<script>
|
<script>
|
||||||
const dataElement = document.getElementById('eventsource_output');
|
const dataElement = document.getElementById('eventsource_output');
|
||||||
|
|
||||||
function eventSourceConnect()
|
function eventSourceConnect() {
|
||||||
{
|
|
||||||
const eventSource = new EventSource('/events');
|
const eventSource = new EventSource('/events');
|
||||||
|
|
||||||
eventSource.onopen = () => {
|
eventSource.onopen = () => {
|
||||||
@@ -233,4 +238,5 @@
|
|||||||
</script>
|
</script>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</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
|
platform = espressif32
|
||||||
framework = arduino
|
framework = arduino
|
||||||
board = esp32-s3-devkitc-1
|
board = esp32-s3-devkitc-1
|
||||||
|
upload_port = /dev/ttyACM0
|
||||||
|
monitor_port = /dev/ttyACM1
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_filters = esp32_exception_decoder
|
monitor_filters = esp32_exception_decoder
|
||||||
lib_deps =
|
lib_deps =
|
||||||
; devmode: with this disabled make a symlink from platformio/lib to the PsychicHttp directory
|
|
||||||
; hoeken/PsychicHttp
|
; hoeken/PsychicHttp
|
||||||
bblanchon/ArduinoJson
|
; PIO is not able to consider installed project in CI
|
||||||
|
;../..
|
||||||
board_build.filesystem = littlefs
|
board_build.filesystem = littlefs
|
||||||
|
|
||||||
[env:default]
|
|
||||||
build_flags =
|
build_flags =
|
||||||
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_WARN
|
-Wall
|
||||||
;-D ENABLE_ASYNC
|
-Wextra
|
||||||
|
|
||||||
; [env:arduino3]
|
[env:arduino2]
|
||||||
; platform = https://github.com/platformio/platform-espressif32.git
|
platform = espressif32@6.8.1
|
||||||
; platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32#master
|
|
||||||
|
[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
|
||||||
|
|||||||
@@ -13,15 +13,57 @@
|
|||||||
* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image
|
* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image
|
||||||
**********************************************************************************************/
|
**********************************************************************************************/
|
||||||
|
|
||||||
|
#include "_secret.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <WiFi.h>
|
|
||||||
#include <LittleFS.h>
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
#include <esp_sntp.h>
|
#include <LittleFS.h>
|
||||||
#include "_secret.h"
|
|
||||||
#include <PsychicHttp.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
|
#ifndef WIFI_SSID
|
||||||
#error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there."
|
#error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there."
|
||||||
@@ -41,6 +83,10 @@ const char *app_user = "admin";
|
|||||||
const char* app_pass = "admin";
|
const char* app_pass = "admin";
|
||||||
const char* app_name = "Your App";
|
const char* app_name = "Your App";
|
||||||
|
|
||||||
|
LoggingMiddleware loggingMiddleware;
|
||||||
|
AuthenticationMiddleware basicAuth;
|
||||||
|
AuthenticationMiddleware digestAuth;
|
||||||
|
|
||||||
// hostname for mdns (psychic.local)
|
// hostname for mdns (psychic.local)
|
||||||
const char* local_hostname = "psychic";
|
const char* local_hostname = "psychic";
|
||||||
|
|
||||||
@@ -59,6 +105,7 @@ const char *local_hostname = "psychic";
|
|||||||
#endif
|
#endif
|
||||||
PsychicWebSocketHandler websocketHandler;
|
PsychicWebSocketHandler websocketHandler;
|
||||||
PsychicEventSource eventSource;
|
PsychicEventSource eventSource;
|
||||||
|
CorsMiddleware corsMiddleware;
|
||||||
|
|
||||||
// NTP server stuff
|
// NTP server stuff
|
||||||
const char* ntpServer1 = "pool.ntp.org";
|
const char* ntpServer1 = "pool.ntp.org";
|
||||||
@@ -83,10 +130,12 @@ void timeAvailable(struct timeval *t)
|
|||||||
|
|
||||||
bool connectToWifi()
|
bool connectToWifi()
|
||||||
{
|
{
|
||||||
//dual client and AP mode
|
// WiFi.mode(WIFI_AP); // ap only mode
|
||||||
WiFi.mode(WIFI_AP_STA);
|
// WiFi.mode(WIFI_STA); // client only mode
|
||||||
|
WiFi.mode(WIFI_AP_STA); // ap and client
|
||||||
|
|
||||||
// Configure SoftAP
|
// Configure SoftAP
|
||||||
|
// dual client and AP mode
|
||||||
WiFi.softAPConfig(softap_ip, softap_ip, IPAddress(255, 255, 255, 0)); // subnet FF FF FF 00
|
WiFi.softAPConfig(softap_ip, softap_ip, IPAddress(255, 255, 255, 0)); // subnet FF FF FF 00
|
||||||
WiFi.softAP(softap_ssid, softap_password);
|
WiFi.softAP(softap_ssid, softap_password);
|
||||||
IPAddress myIP = WiFi.softAPIP();
|
IPAddress myIP = WiFi.softAPIP();
|
||||||
@@ -107,10 +156,8 @@ bool connectToWifi()
|
|||||||
int numberOfTries = 20;
|
int numberOfTries = 20;
|
||||||
|
|
||||||
// Wait for the WiFi event
|
// Wait for the WiFi event
|
||||||
while (true)
|
while (true) {
|
||||||
{
|
switch (WiFi.status()) {
|
||||||
switch (WiFi.status())
|
|
||||||
{
|
|
||||||
case WL_NO_SSID_AVAIL:
|
case WL_NO_SSID_AVAIL:
|
||||||
Serial.println("[WiFi] SSID not found");
|
Serial.println("[WiFi] SSID not found");
|
||||||
break;
|
break;
|
||||||
@@ -140,15 +187,12 @@ bool connectToWifi()
|
|||||||
}
|
}
|
||||||
delay(tryDelay);
|
delay(tryDelay);
|
||||||
|
|
||||||
if (numberOfTries <= 0)
|
if (numberOfTries <= 0) {
|
||||||
{
|
|
||||||
Serial.print("[WiFi] Failed to connect to WiFi!");
|
Serial.print("[WiFi] Failed to connect to WiFi!");
|
||||||
// Use disconnect function to force stop trying to connect
|
// Use disconnect function to force stop trying to connect
|
||||||
WiFi.disconnect();
|
WiFi.disconnect();
|
||||||
return false;
|
return false;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
numberOfTries--;
|
numberOfTries--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,144 +200,209 @@ bool connectToWifi()
|
|||||||
return false;
|
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()
|
void setup()
|
||||||
{
|
{
|
||||||
|
esp_log_level_set(PH_TAG, ESP_LOG_DEBUG);
|
||||||
|
esp_log_level_set("httpd_uri", ESP_LOG_DEBUG);
|
||||||
|
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
delay(10);
|
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
|
// We start by connecting to a WiFi network
|
||||||
// To debug, please enable Core Debug Level to Verbose
|
// To debug, please enable Core Debug Level to Verbose
|
||||||
if (connectToWifi())
|
if (connectToWifi()) {
|
||||||
{
|
|
||||||
// Setup our NTP to get the current time.
|
// Setup our NTP to get the current time.
|
||||||
sntp_set_time_sync_notification_cb(timeAvailable);
|
sntp_set_time_sync_notification_cb(timeAvailable);
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2)
|
||||||
|
esp_sntp_servermode_dhcp(1); // (optional)
|
||||||
|
#else
|
||||||
sntp_servermode_dhcp(1); // (optional)
|
sntp_servermode_dhcp(1); // (optional)
|
||||||
|
#endif
|
||||||
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1, ntpServer2);
|
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1, ntpServer2);
|
||||||
|
|
||||||
//set up our esp32 to listen on the local_hostname.local domain
|
// set up our esp32 to listen on the psychic.local domain
|
||||||
if (!MDNS.begin(local_hostname)) {
|
if (MDNS.begin(local_hostname))
|
||||||
Serial.println("Error starting mDNS");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MDNS.addService("http", "tcp", 80);
|
MDNS.addService("http", "tcp", 80);
|
||||||
|
else
|
||||||
|
Serial.println("Error starting mDNS");
|
||||||
|
|
||||||
if(!LittleFS.begin())
|
if (!LittleFS.begin()) {
|
||||||
{
|
|
||||||
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
|
Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//look up our keys?
|
|
||||||
#ifdef PSY_ENABLE_SSL
|
#ifdef PSY_ENABLE_SSL
|
||||||
if (app_enable_ssl)
|
// look up our keys?
|
||||||
{
|
if (app_enable_ssl) {
|
||||||
File fp = LittleFS.open("/server.crt");
|
File fp = LittleFS.open("/server.crt");
|
||||||
if (fp)
|
if (fp) {
|
||||||
{
|
|
||||||
server_cert = fp.readString();
|
server_cert = fp.readString();
|
||||||
|
|
||||||
// Serial.println("Server Cert:");
|
// Serial.println("Server Cert:");
|
||||||
// Serial.println(server_cert);
|
// Serial.println(server_cert);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
Serial.println("server.pem not found, SSL not available");
|
Serial.println("server.pem not found, SSL not available");
|
||||||
app_enable_ssl = false;
|
app_enable_ssl = false;
|
||||||
}
|
}
|
||||||
fp.close();
|
fp.close();
|
||||||
|
|
||||||
File fp2 = LittleFS.open("/server.key");
|
File fp2 = LittleFS.open("/server.key");
|
||||||
if (fp2)
|
if (fp2) {
|
||||||
{
|
|
||||||
server_key = fp2.readString();
|
server_key = fp2.readString();
|
||||||
|
|
||||||
// Serial.println("Server Key:");
|
// Serial.println("Server Key:");
|
||||||
// Serial.println(server_key);
|
// Serial.println(server_key);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
Serial.println("server.key not found, SSL not available");
|
Serial.println("server.key not found, SSL not available");
|
||||||
app_enable_ssl = false;
|
app_enable_ssl = false;
|
||||||
}
|
}
|
||||||
fp2.close();
|
fp2.close();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
//setup server config stuff here
|
|
||||||
server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
|
|
||||||
|
|
||||||
#ifdef PSY_ENABLE_SSL
|
|
||||||
server.ssl_config.httpd.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls)
|
|
||||||
|
|
||||||
// do we want secure or not?
|
// do we want secure or not?
|
||||||
if (app_enable_ssl)
|
if (app_enable_ssl) {
|
||||||
{
|
server.setCertificate(server_cert.c_str(), server_key.c_str());
|
||||||
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
|
// this creates a 2nd server listening on port 80 and redirects all requests HTTPS
|
||||||
PsychicHttpServer* redirectServer = new PsychicHttpServer();
|
PsychicHttpServer* redirectServer = new PsychicHttpServer();
|
||||||
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
|
redirectServer->config.ctrl_port = 20424; // just a random port different from the default one
|
||||||
redirectServer->listen(80);
|
redirectServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) {
|
||||||
redirectServer->onNotFound([](PsychicRequest *request) {
|
|
||||||
String url = "https://" + request->host() + request->url();
|
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
|
#endif
|
||||||
|
|
||||||
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
|
DefaultHeaders::Instance().addHeader("Server", "PsychicHttp");
|
||||||
|
|
||||||
|
loggingMiddleware.setOutput(Serial);
|
||||||
|
|
||||||
|
basicAuth.setUsername(app_user);
|
||||||
|
basicAuth.setPassword(app_pass);
|
||||||
|
basicAuth.setRealm(app_name);
|
||||||
|
basicAuth.setAuthMethod(HTTPAuthMethod::BASIC_AUTH);
|
||||||
|
basicAuth.setAuthFailureMessage("You must log in.");
|
||||||
|
|
||||||
|
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
|
// serve static files from LittleFS/www on / only to clients on same wifi network
|
||||||
// this is where our /index.html file lives
|
// this is where our /index.html file lives
|
||||||
// curl -i http://psychic.local/
|
// curl -i http://psychic.local/
|
||||||
PsychicStaticFileHandler* handler = server.serveStatic("/", LittleFS, "/www/");
|
server.serveStatic("/", LittleFS, "/www/")
|
||||||
handler->setFilter(ON_STA_FILTER);
|
->setCacheControl("max-age=60")
|
||||||
handler->setCacheControl("max-age=60");
|
->addFilter(ON_STA_FILTER);
|
||||||
|
|
||||||
// serve static files from LittleFS/www-ap on / only to clients on SoftAP
|
// serve static files from LittleFS/www-ap on / only to clients on SoftAP
|
||||||
// this is where our /index.html file lives
|
// 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
|
// 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.
|
// 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
|
// curl -i http://psychic.local/img/request_flow.png
|
||||||
server.serveStatic("/img", LittleFS, "/img/");
|
server.serveStatic("/img", LittleFS, "/img/");
|
||||||
|
|
||||||
|
#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
|
// you can also serve single files
|
||||||
// curl -i http://psychic.local/myfile.txt
|
// curl -i http://psychic.local/myfile.txt
|
||||||
server.serveStatic("/myfile.txt", LittleFS, "/custom.txt");
|
server.serveStatic("/myfile.txt", LittleFS, "/custom.txt");
|
||||||
|
|
||||||
// example callback everytime a connection is opened
|
// example callback everytime a connection is opened
|
||||||
server.onOpen([](PsychicClient *client) {
|
server.onOpen([](PsychicClient* client) { Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str()); });
|
||||||
Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
// example callback everytime a connection is closed
|
// example callback everytime a connection is closed
|
||||||
server.onClose([](PsychicClient *client) {
|
server.onClose([](PsychicClient* client) { Serial.printf("[http] connection #%u closed\n", client->socket()); });
|
||||||
Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
// api - json message passed in as post body
|
// api - json message passed in as post body
|
||||||
// curl -i -X POST -H "Content-Type: application/json" -d '{"foo":"bar"}' http://psychic.local/api
|
// 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)
|
server.on("/api", HTTP_POST, [](PsychicRequest* request, PsychicResponse* resp, JsonVariant& json) {
|
||||||
{
|
|
||||||
JsonObject input = json.as<JsonObject>();
|
JsonObject input = json.as<JsonObject>();
|
||||||
|
|
||||||
// create our response json
|
// create our response json
|
||||||
PsychicJsonResponse response = PsychicJsonResponse(request);
|
PsychicJsonResponse response(resp);
|
||||||
JsonObject output = response.getRoot();
|
JsonObject output = response.getRoot();
|
||||||
|
|
||||||
output["msg"] = "status";
|
output["msg"] = "status";
|
||||||
output["status"] = "success";
|
output["status"] = "success";
|
||||||
output["millis"] = millis();
|
output["millis"] = millis();
|
||||||
|
output["method"] = request->methodStr();
|
||||||
|
|
||||||
// work with some params
|
// work with some params
|
||||||
if (input.containsKey("foo"))
|
if (input.containsKey("foo")) {
|
||||||
{
|
|
||||||
String foo = input["foo"];
|
String foo = input["foo"];
|
||||||
output["foo"] = foo;
|
output["foo"] = foo;
|
||||||
}
|
}
|
||||||
@@ -303,51 +412,35 @@ void setup()
|
|||||||
|
|
||||||
// ip - get info about the client
|
// ip - get info about the client
|
||||||
// curl -i http://psychic.local/ip
|
// curl -i http://psychic.local/ip
|
||||||
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();
|
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
|
// client connect/disconnect to a url
|
||||||
// curl -i http://psychic.local/handler
|
// curl -i http://psychic.local/handler
|
||||||
PsychicWebHandler* connectionHandler = new PsychicWebHandler();
|
PsychicWebHandler* connectionHandler = new PsychicWebHandler();
|
||||||
connectionHandler->onRequest([](PsychicRequest *request)
|
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()); });
|
||||||
return request->reply("OK");
|
connectionHandler->onClose([](PsychicClient* client) { Serial.printf("[handler] connection #%u closed\n", client->socket()); });
|
||||||
});
|
|
||||||
connectionHandler->onOpen([](PsychicClient *client) {
|
|
||||||
Serial.printf("[handler] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
|
|
||||||
});
|
|
||||||
connectionHandler->onClose([](PsychicClient *client) {
|
|
||||||
Serial.printf("[handler] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
// add it to our server
|
// add it to our server
|
||||||
server.on("/handler", connectionHandler);
|
server.on("/handler", connectionHandler);
|
||||||
|
|
||||||
// api - parameters passed in via query eg. /api?foo=bar
|
// api - parameters passed in via query eg. /api?foo=bar
|
||||||
// curl -i 'http://psychic.local/api?foo=bar'
|
// curl -i 'http://psychic.local/api?foo=bar'
|
||||||
server.on("/api", HTTP_GET, [](PsychicRequest *request)
|
server.on("/api", HTTP_GET, [](PsychicRequest* request, PsychicResponse* resp) {
|
||||||
{
|
|
||||||
//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
|
// create our response json
|
||||||
PsychicJsonResponse response = PsychicJsonResponse(request);
|
PsychicJsonResponse response = PsychicJsonResponse(resp);
|
||||||
JsonObject output = response.getRoot();
|
JsonObject output = response.getRoot();
|
||||||
|
|
||||||
output["msg"] = "status";
|
output["msg"] = "status";
|
||||||
output["status"] = "success";
|
output["status"] = "success";
|
||||||
output["millis"] = millis();
|
output["millis"] = millis();
|
||||||
|
output["method"] = request->methodStr();
|
||||||
|
|
||||||
// work with some params
|
// work with some params
|
||||||
if (request->hasParam("foo"))
|
if (request->hasParam("foo")) {
|
||||||
{
|
|
||||||
String foo = request->getParam("foo")->value();
|
String foo = request->getParam("foo")->value();
|
||||||
output["foo"] = foo;
|
output["foo"] = foo;
|
||||||
}
|
}
|
||||||
@@ -355,88 +448,111 @@ void setup()
|
|||||||
return response.send();
|
return response.send();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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
|
// JsonResponse example
|
||||||
// curl -i http://psychic.local/json
|
// curl -i http://psychic.local/json
|
||||||
server.on("/json", HTTP_GET, [](PsychicRequest *request)
|
server.on("/json", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
|
||||||
{
|
PsychicJsonResponse jsonResponse = PsychicJsonResponse(response);
|
||||||
PsychicJsonResponse response = PsychicJsonResponse(request);
|
|
||||||
|
|
||||||
char key[16];
|
char key[16];
|
||||||
char value[32];
|
char value[32];
|
||||||
JsonObject root = response.getRoot();
|
JsonObject root = jsonResponse.getRoot();
|
||||||
for (int i=0; i<100; i++)
|
for (int i = 0; i < 100; i++) {
|
||||||
{
|
|
||||||
sprintf(key, "key%d", i);
|
sprintf(key, "key%d", i);
|
||||||
sprintf(value, "value is %d", i);
|
sprintf(value, "value is %d", i);
|
||||||
root[key] = value;
|
root[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.send();
|
return jsonResponse.send();
|
||||||
});
|
});
|
||||||
|
|
||||||
// how to redirect a request
|
// how to redirect a request
|
||||||
// curl -i http://psychic.local/redirect
|
// curl -i http://psychic.local/redirect
|
||||||
server.on("/redirect", HTTP_GET, [](PsychicRequest *request)
|
server.on("/redirect", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->redirect("/alien.png"); });
|
||||||
{
|
|
||||||
return request->redirect("/alien.png");
|
|
||||||
});
|
|
||||||
|
|
||||||
// how to do basic auth
|
// how to do basic auth
|
||||||
// curl -i --user admin:admin http://psychic.local/auth-basic
|
// curl -i --user admin:admin http://psychic.local/auth-basic
|
||||||
server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request)
|
server.on("/auth-basic", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
|
||||||
{
|
return response->send("Auth Basic Success!");
|
||||||
if (!request->authenticate(app_user, app_pass))
|
})->addMiddleware(&basicAuth);
|
||||||
return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in.");
|
|
||||||
return request->reply("Auth Basic Success!");
|
|
||||||
});
|
|
||||||
|
|
||||||
// how to do digest auth
|
// how to do digest auth
|
||||||
// curl -i --user admin:admin http://psychic.local/auth-digest
|
// curl -i --user admin:admin http://psychic.local/auth-digest
|
||||||
server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request)
|
server.on("/auth-digest", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
|
||||||
{
|
return response->send("Auth Digest Success!");
|
||||||
if (!request->authenticate(app_user, app_pass))
|
})->addMiddleware(&digestAuth);
|
||||||
return request->requestAuthentication(DIGEST_AUTH, app_name, "You must log in.");
|
|
||||||
return request->reply("Auth Digest Success!");
|
|
||||||
});
|
|
||||||
|
|
||||||
// example of getting / setting cookies
|
// example of getting / setting cookies
|
||||||
// curl -i -b cookie.txt -c cookie.txt http://psychic.local/cookies
|
// curl -i -b cookie.txt -c cookie.txt http://psychic.local/cookies
|
||||||
server.on("/cookies", HTTP_GET, [](PsychicRequest *request)
|
server.on("/cookies", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) {
|
||||||
{
|
|
||||||
PsychicResponse response(request);
|
|
||||||
|
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
if (request->hasCookie("counter"))
|
char cookie[14];
|
||||||
{
|
size_t size = sizeof(cookie);
|
||||||
counter = std::stoi(request->getCookie("counter").c_str());
|
if (request->getCookie("counter", cookie, &size) == ESP_OK) {
|
||||||
|
// value is null-terminated.
|
||||||
|
counter = std::stoi(cookie);
|
||||||
counter++;
|
counter++;
|
||||||
}
|
}
|
||||||
|
sprintf(cookie, "%d", counter);
|
||||||
|
|
||||||
char cookie[10];
|
response->setCookie("counter", cookie);
|
||||||
sprintf(cookie, "%i", counter);
|
response->setContent(cookie);
|
||||||
|
return response->send();
|
||||||
response.setCookie("counter", cookie);
|
|
||||||
response.setContent(cookie);
|
|
||||||
return response.send();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// example of getting POST variables
|
// example of getting POST variables
|
||||||
// curl -i -d "param1=value1¶m2=value2" -X POST http://psychic.local/post
|
// curl -i -d "param1=value1¶m2=value2" -X POST http://psychic.local/post
|
||||||
server.on("/post", HTTP_POST, [](PsychicRequest *request)
|
// curl -F "param1=value1" -F "param2=value2" -X POST http://psychic.local/post
|
||||||
{
|
server.on("/post", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) {
|
||||||
String output;
|
String output;
|
||||||
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
||||||
output += "Param 2: " + request->getParam("param2")->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.
|
// you can set up a custom 404 handler.
|
||||||
// curl -i http://psychic.local/404
|
// curl -i http://psychic.local/404
|
||||||
server.onNotFound([](PsychicRequest *request)
|
server.onNotFound([](PsychicRequest* request, PsychicResponse* response) { return response->send(404, "text/html", "Custom 404 Handler"); });
|
||||||
{
|
|
||||||
return request->reply(404, "text/html", "Custom 404 Handler");
|
|
||||||
});
|
|
||||||
|
|
||||||
// handle a very basic upload as post body
|
// handle a very basic upload as post body
|
||||||
PsychicUploadHandler* uploadHandler = new PsychicUploadHandler();
|
PsychicUploadHandler* uploadHandler = new PsychicUploadHandler();
|
||||||
@@ -447,7 +563,7 @@ void setup()
|
|||||||
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)
|
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)
|
if (!index)
|
||||||
@@ -469,12 +585,11 @@ void setup()
|
|||||||
});
|
});
|
||||||
|
|
||||||
// gets called after upload has been handled
|
// gets called after upload has been handled
|
||||||
uploadHandler->onRequest([](PsychicRequest *request)
|
uploadHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) {
|
||||||
{
|
|
||||||
String url = "/" + request->getFilename();
|
String url = "/" + request->getFilename();
|
||||||
String output = "<a href=\"" + url + "\">" + url + "</a>";
|
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
|
||||||
@@ -488,9 +603,9 @@ void setup()
|
|||||||
String path = "/www/" + filename;
|
String path = "/www/" + filename;
|
||||||
|
|
||||||
// some progress over serial.
|
// some progress over serial.
|
||||||
Serial.printf("Writing %d bytes to: %s\n", (int)len, path.c_str());
|
Serial.printf("Writing %d bytes to: %s @ index %llu\n", (int)len, path.c_str(), index);
|
||||||
if (last)
|
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)
|
if (!index)
|
||||||
@@ -512,56 +627,82 @@ void setup()
|
|||||||
});
|
});
|
||||||
|
|
||||||
// gets called after upload has been handled
|
// gets called after upload has been handled
|
||||||
multipartHandler->onRequest([](PsychicRequest *request)
|
multipartHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) {
|
||||||
{
|
String output;
|
||||||
if (request->hasParam("file_upload"))
|
if (request->hasParam("file_upload")) {
|
||||||
{
|
|
||||||
PsychicWebParameter* file = request->getParam("file_upload");
|
PsychicWebParameter* file = request->getParam("file_upload");
|
||||||
|
|
||||||
String url = "/" + file->value();
|
String url = "/" + file->value();
|
||||||
String output;
|
|
||||||
|
|
||||||
output += "<a href=\"" + url + "\">" + url + "</a><br/>\n";
|
output += "<a href=\"" + url + "\">" + url + "</a><br/>\n";
|
||||||
output += "Bytes: " + String(file->size()) + "<br/>\n";
|
output += "Bytes: " + String(file->size()) + "<br/>\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request->hasParam("param1"))
|
||||||
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
output += "Param 1: " + request->getParam("param1")->value() + "<br/>\n";
|
||||||
|
if (request->hasParam("param2"))
|
||||||
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
|
output += "Param 2: " + request->getParam("param2")->value() + "<br/>\n";
|
||||||
|
|
||||||
return request->reply(output.c_str());
|
return response->send(output.c_str());
|
||||||
}
|
|
||||||
else
|
|
||||||
return request->reply("No upload.");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// wildcard basic file upload - POST to /upload/filename.ext
|
// wildcard basic file upload - POST to /upload/filename.ext
|
||||||
// use http://psychic.local/ to test
|
// 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);
|
server.on("/multipart", HTTP_POST, multipartHandler);
|
||||||
|
|
||||||
|
// 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
|
// a websocket echo server
|
||||||
// npm install -g wscat
|
// npm install -g wscat
|
||||||
// wscat -c ws://psychic.local/ws
|
// Plaintext: wscat -c ws://psychic.local/ws
|
||||||
|
// SSL: wscat -n -c wss://psychic.local/ws
|
||||||
websocketHandler.onOpen([](PsychicWebSocketClient* client) {
|
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!");
|
client->sendMessage("Hello!");
|
||||||
});
|
});
|
||||||
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) {
|
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) {
|
||||||
Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload);
|
// Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), String((char*)frame->payload, frame->len).c_str());
|
||||||
return request->reply(frame);
|
return request->reply(frame);
|
||||||
});
|
});
|
||||||
websocketHandler.onClose([](PsychicWebSocketClient *client) {
|
websocketHandler.onClose([](PsychicWebSocketClient* client) { Serial.printf("[socket] connection #%u closed\n", client->socket()); });
|
||||||
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
|
|
||||||
});
|
|
||||||
server.on("/ws", &websocketHandler);
|
server.on("/ws", &websocketHandler);
|
||||||
|
|
||||||
// EventSource server
|
// EventSource server
|
||||||
// curl -i -N http://psychic.local/events
|
// curl -i -N http://psychic.local/events
|
||||||
eventSource.onOpen([](PsychicEventSourceClient* client) {
|
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);
|
client->send("Hello user!", NULL, millis(), 1000);
|
||||||
});
|
});
|
||||||
eventSource.onClose([](PsychicEventSourceClient *client) {
|
eventSource.onClose([](PsychicEventSourceClient* client) { Serial.printf("[eventsource] connection #%u closed\n", client->socket()); });
|
||||||
Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
|
|
||||||
});
|
|
||||||
server.on("/events", &eventSource);
|
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()
|
void loop()
|
||||||
{
|
{
|
||||||
if (millis() - lastUpdate > 2000)
|
if (millis() - lastUpdate > 1000) {
|
||||||
{
|
sprintf(output, "Millis: %lu\n", millis());
|
||||||
sprintf(output, "Millis: %d\n", millis());
|
|
||||||
websocketHandler.sendAll(output);
|
websocketHandler.sendAll(output);
|
||||||
|
|
||||||
sprintf(output, "%d", millis());
|
sprintf(output, "%lu", millis());
|
||||||
eventSource.send(output, "millis", millis(), 0);
|
eventSource.send(output, "millis", millis(), 0);
|
||||||
|
|
||||||
lastUpdate = millis();
|
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
|
|
||||||
@@ -13,14 +13,14 @@
|
|||||||
* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image
|
* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image
|
||||||
**********************************************************************************************/
|
**********************************************************************************************/
|
||||||
|
|
||||||
|
#include "_secret.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <WiFi.h>
|
|
||||||
#include <LittleFS.h>
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
#include <esp_sntp.h>
|
#include <LittleFS.h>
|
||||||
#include "_secret.h"
|
|
||||||
#include <PsychicHttp.h>
|
#include <PsychicHttp.h>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <esp_sntp.h>
|
||||||
#include <freertos/queue.h>
|
#include <freertos/queue.h>
|
||||||
|
|
||||||
#ifndef WIFI_SSID
|
#ifndef WIFI_SSID
|
||||||
@@ -37,7 +37,8 @@ const char *local_hostname = "psychic";
|
|||||||
PsychicHttpServer server;
|
PsychicHttpServer server;
|
||||||
PsychicWebSocketHandler websocketHandler;
|
PsychicWebSocketHandler websocketHandler;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct
|
||||||
|
{
|
||||||
int socket;
|
int socket;
|
||||||
char* buffer;
|
char* buffer;
|
||||||
size_t len;
|
size_t len;
|
||||||
@@ -123,7 +124,8 @@ void setup()
|
|||||||
if (connectToWifi())
|
if (connectToWifi())
|
||||||
{
|
{
|
||||||
// set up our esp32 to listen on the local_hostname.local domain
|
// set up our esp32 to listen on the local_hostname.local domain
|
||||||
if (!MDNS.begin(local_hostname)) {
|
if (!MDNS.begin(local_hostname))
|
||||||
|
{
|
||||||
Serial.println("Error starting mDNS");
|
Serial.println("Error starting mDNS");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -135,19 +137,17 @@ void setup()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
server.listen(80);
|
|
||||||
|
|
||||||
// this is where our /index.html file lives
|
// this is where our /index.html file lives
|
||||||
// curl -i http://psychic.local/
|
// curl -i http://psychic.local/
|
||||||
PsychicStaticFileHandler* handler = server.serveStatic("/", LittleFS, "/www/");
|
server.serveStatic("/", LittleFS, "/www/");
|
||||||
|
|
||||||
// a websocket echo server
|
// a websocket echo server
|
||||||
// npm install -g wscat
|
// npm install -g wscat
|
||||||
// wscat -c ws://psychic.local/ws
|
// wscat -c ws://psychic.local/ws
|
||||||
websocketHandler.onOpen([](PsychicWebSocketClient *client) {
|
websocketHandler.onOpen([](PsychicWebSocketClient* client)
|
||||||
Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
|
{
|
||||||
client->sendMessage("Hello!");
|
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)
|
websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame)
|
||||||
{
|
{
|
||||||
Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload);
|
Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload);
|
||||||
@@ -181,11 +181,9 @@ void setup()
|
|||||||
if (!uxQueueSpacesAvailable(wsMessages))
|
if (!uxQueueSpacesAvailable(wsMessages))
|
||||||
return request->reply("Queue Full");
|
return request->reply("Queue Full");
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK; });
|
||||||
});
|
websocketHandler.onClose([](PsychicWebSocketClient* client)
|
||||||
websocketHandler.onClose([](PsychicWebSocketClient *client) {
|
{ Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString().c_str()); });
|
||||||
Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
|
|
||||||
});
|
|
||||||
server.on("/ws", &websocketHandler);
|
server.on("/ws", &websocketHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,7 +199,8 @@ void loop()
|
|||||||
{
|
{
|
||||||
// make sure our client is still good.
|
// make sure our client is still good.
|
||||||
PsychicWebSocketClient* client = websocketHandler.getClient(message.socket);
|
PsychicWebSocketClient* client = websocketHandler.getClient(message.socket);
|
||||||
if (client == NULL) {
|
if (client == NULL)
|
||||||
|
{
|
||||||
Serial.printf("[socket] client #%d bad, bailing\n", message.socket);
|
Serial.printf("[socket] client #%d bad, bailing\n", message.socket);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -217,7 +216,7 @@ void loop()
|
|||||||
// send a periodic update to all clients
|
// send a periodic update to all clients
|
||||||
if (millis() - lastUpdate > 2000)
|
if (millis() - lastUpdate > 2000)
|
||||||
{
|
{
|
||||||
sprintf(output, "Millis: %d\n", millis());
|
sprintf(output, "Millis: %lu\n", millis());
|
||||||
websocketHandler.sendAll(output);
|
websocketHandler.sendAll(output);
|
||||||
|
|
||||||
lastUpdate = millis();
|
lastUpdate = millis();
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "PsychicHttp",
|
"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",
|
"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",
|
"keywords": "network,http,https,tcp,ssl,tls,websocket,espasyncwebserver",
|
||||||
"repository":
|
"repository": {
|
||||||
{
|
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/hoeken/PsychicHttp"
|
"url": "https://github.com/hoeken/PsychicHttp"
|
||||||
},
|
},
|
||||||
"authors":
|
"authors": [
|
||||||
[
|
|
||||||
{
|
{
|
||||||
"name": "Zach Hoeken",
|
"name": "Zach Hoeken",
|
||||||
"email": "hoeken@gmail.com",
|
"email": "hoeken@gmail.com",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name=PsychicHttp
|
name=PsychicHttp
|
||||||
version=1.2.1
|
version=2.0.0
|
||||||
author=Zach Hoeken <hoeken@gmail.com>
|
author=Zach Hoeken <hoeken@gmail.com>
|
||||||
maintainer=Zach Hoeken <hoeken@gmail.com>
|
maintainer=Zach Hoeken <hoeken@gmail.com>
|
||||||
sentence=PsychicHttp is a robust webserver that supports http/https + websockets.
|
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"
|
#include "ChunkPrinter.h"
|
||||||
|
|
||||||
ChunkPrinter::ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len) :
|
ChunkPrinter::ChunkPrinter(PsychicResponse* response, uint8_t* buffer, size_t len) : _response(response),
|
||||||
_response(response),
|
|
||||||
_buffer(buffer),
|
_buffer(buffer),
|
||||||
_length(len),
|
_length(len),
|
||||||
_pos(0)
|
_pos(0)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
ChunkPrinter::~ChunkPrinter()
|
ChunkPrinter::~ChunkPrinter()
|
||||||
{
|
{
|
||||||
@@ -69,7 +69,8 @@ size_t ChunkPrinter::copyFrom(Stream &stream)
|
|||||||
{
|
{
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
|
|
||||||
while (stream.available()){
|
while (stream.available())
|
||||||
|
{
|
||||||
|
|
||||||
if (_pos == _length)
|
if (_pos == _length)
|
||||||
{
|
{
|
||||||
|
|||||||
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 "PsychicHttpServer.h"
|
||||||
#include <lwip/sockets.h>
|
#include <lwip/sockets.h>
|
||||||
|
|
||||||
PsychicClient::PsychicClient(httpd_handle_t server, int socket) :
|
PsychicClient::PsychicClient(httpd_handle_t server, int socket) : _server(server),
|
||||||
_server(server),
|
|
||||||
_socket(socket),
|
_socket(socket),
|
||||||
_friend(NULL),
|
_friend(NULL),
|
||||||
isNew(false)
|
isNew(false)
|
||||||
{}
|
{
|
||||||
|
|
||||||
PsychicClient::~PsychicClient() {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
httpd_handle_t PsychicClient::server() {
|
PsychicClient::~PsychicClient()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_handle_t PsychicClient::server()
|
||||||
|
{
|
||||||
return _server;
|
return _server;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PsychicClient::socket() {
|
int PsychicClient::socket()
|
||||||
|
{
|
||||||
return _socket;
|
return _socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +53,15 @@ IPAddress PsychicClient::localIP()
|
|||||||
return address;
|
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 PsychicClient::remoteIP()
|
||||||
{
|
{
|
||||||
IPAddress address(0, 0, 0, 0);
|
IPAddress address(0, 0, 0, 0);
|
||||||
@@ -70,3 +82,12 @@ IPAddress PsychicClient::remoteIP()
|
|||||||
|
|
||||||
return address;
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
* PsychicClient :: Generic wrapper around the ESP-IDF socket
|
* PsychicClient :: Generic wrapper around the ESP-IDF socket
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class PsychicClient {
|
class PsychicClient
|
||||||
|
{
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
httpd_handle_t _server;
|
httpd_handle_t _server;
|
||||||
int _socket;
|
int _socket;
|
||||||
@@ -29,7 +31,9 @@ class PsychicClient {
|
|||||||
esp_err_t close();
|
esp_err_t close();
|
||||||
|
|
||||||
IPAddress localIP();
|
IPAddress localIP();
|
||||||
|
uint16_t localPort() const;
|
||||||
IPAddress remoteIP();
|
IPAddress remoteIP();
|
||||||
|
uint16_t remotePort() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -3,15 +3,6 @@
|
|||||||
|
|
||||||
#define PH_TAG "psychic"
|
#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
|
#ifndef FILE_CHUNK_SIZE
|
||||||
#define FILE_CHUNK_SIZE 8 * 1024
|
#define FILE_CHUNK_SIZE 8 * 1024
|
||||||
#endif
|
#endif
|
||||||
@@ -32,41 +23,55 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#endif
|
#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 "FS.h"
|
||||||
|
#include "MD5Builder.h"
|
||||||
|
#include "esp_random.h"
|
||||||
#include <ArduinoJson.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);
|
String urlDecode(const char* encoded);
|
||||||
|
|
||||||
class PsychicHttpServer;
|
class PsychicHttpServer;
|
||||||
class PsychicRequest;
|
class PsychicRequest;
|
||||||
|
class PsychicResponse;
|
||||||
class PsychicWebSocketRequest;
|
class PsychicWebSocketRequest;
|
||||||
class PsychicClient;
|
class PsychicClient;
|
||||||
|
|
||||||
// filter function definition
|
// filter function definition
|
||||||
typedef std::function<bool(PsychicRequest* request)> PsychicRequestFilterFunction;
|
typedef std::function<bool(PsychicRequest* request)> PsychicRequestFilterFunction;
|
||||||
|
|
||||||
|
// middleware function definition
|
||||||
|
typedef std::function<esp_err_t()> PsychicMiddlewareNext;
|
||||||
|
typedef std::function<esp_err_t(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next)> PsychicMiddlewareCallback;
|
||||||
|
|
||||||
// client connect callback
|
// client connect callback
|
||||||
typedef std::function<void(PsychicClient* client)> PsychicClientCallback;
|
typedef std::function<void(PsychicClient* client)> PsychicClientCallback;
|
||||||
|
|
||||||
// callback definitions
|
// callback definitions
|
||||||
typedef std::function<esp_err_t(PsychicRequest *request)> PsychicHttpRequestCallback;
|
typedef std::function<esp_err_t(PsychicRequest* request, PsychicResponse* response)> PsychicHttpRequestCallback;
|
||||||
typedef std::function<esp_err_t(PsychicRequest *request, JsonVariant &json)> PsychicJsonRequestCallback;
|
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 {
|
struct HTTPHeader {
|
||||||
char * field;
|
String field;
|
||||||
char * value;
|
String value;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DefaultHeaders {
|
class DefaultHeaders
|
||||||
|
{
|
||||||
std::list<HTTPHeader> _headers;
|
std::list<HTTPHeader> _headers;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -74,21 +79,12 @@ public:
|
|||||||
|
|
||||||
void addHeader(const String& field, const String& value)
|
void addHeader(const String& field, const String& value)
|
||||||
{
|
{
|
||||||
addHeader(field.c_str(), value.c_str());
|
_headers.push_back({field, value});
|
||||||
}
|
}
|
||||||
|
|
||||||
void addHeader(const char* field, const char* value)
|
void addHeader(const char* field, const char* value)
|
||||||
{
|
{
|
||||||
HTTPHeader header;
|
_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);
|
|
||||||
|
|
||||||
strlcpy(header.field, field, strlen(field)+1);
|
|
||||||
strlcpy(header.value, value, strlen(value)+1);
|
|
||||||
|
|
||||||
_headers.push_back(header);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::list<HTTPHeader>& getHeaders() { return _headers; }
|
const std::list<HTTPHeader>& getHeaders() { return _headers; }
|
||||||
@@ -98,7 +94,8 @@ public:
|
|||||||
DefaultHeaders& operator=(DefaultHeaders const&) = delete;
|
DefaultHeaders& operator=(DefaultHeaders const&) = delete;
|
||||||
|
|
||||||
// single static class interface
|
// single static class interface
|
||||||
static DefaultHeaders &Instance() {
|
static DefaultHeaders& Instance()
|
||||||
|
{
|
||||||
static DefaultHeaders instance;
|
static DefaultHeaders instance;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
#include "PsychicEndpoint.h"
|
#include "PsychicEndpoint.h"
|
||||||
#include "PsychicHttpServer.h"
|
#include "PsychicHttpServer.h"
|
||||||
|
|
||||||
PsychicEndpoint::PsychicEndpoint() :
|
PsychicEndpoint::PsychicEndpoint() : _server(NULL),
|
||||||
_server(NULL),
|
|
||||||
_uri(""),
|
_uri(""),
|
||||||
_method(HTTP_GET),
|
_method(HTTP_GET),
|
||||||
_handler(NULL)
|
_handler(NULL)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicEndpoint::PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri) :
|
PsychicEndpoint::PsychicEndpoint(PsychicHttpServer* server, int method, const char* uri) : _server(server),
|
||||||
_server(server),
|
|
||||||
_uri(uri),
|
_uri(uri),
|
||||||
_method(method),
|
_method(method),
|
||||||
_handler(NULL)
|
_handler(NULL)
|
||||||
@@ -37,7 +35,8 @@ PsychicHandler * PsychicEndpoint::handler()
|
|||||||
return _handler;
|
return _handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
String PsychicEndpoint::uri() {
|
String PsychicEndpoint::uri()
|
||||||
|
{
|
||||||
return _uri;
|
return _uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,35 +55,86 @@ esp_err_t PsychicEndpoint::requestCallback(httpd_req_t *req)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
PsychicEndpoint* self = (PsychicEndpoint*)req->user_ctx;
|
PsychicEndpoint* self = (PsychicEndpoint*)req->user_ctx;
|
||||||
PsychicHandler *handler = self->handler();
|
|
||||||
PsychicRequest request(self->_server, req);
|
PsychicRequest request(self->_server, req);
|
||||||
|
|
||||||
//make sure we have a handler
|
esp_err_t err = self->process(&request);
|
||||||
if (handler != NULL)
|
|
||||||
{
|
|
||||||
if (handler->filter(&request) && handler->canHandle(&request))
|
|
||||||
{
|
|
||||||
//check our credentials
|
|
||||||
if (handler->needsAuthentication(&request))
|
|
||||||
return handler->authenticate(&request);
|
|
||||||
|
|
||||||
//pass it to our handler
|
if (err == HTTPD_404_NOT_FOUND)
|
||||||
return handler->handleRequest(&request);
|
return PsychicHttpServer::requestHandler(req);
|
||||||
}
|
|
||||||
//pass it to our generic handlers
|
if (err == ESP_ERR_HTTPD_INVALID_REQ)
|
||||||
else
|
return request.response()->error(HTTPD_500_INTERNAL_SERVER_ERROR, "No handler registered.");
|
||||||
return PsychicHttpServer::notFoundHandler(req, HTTPD_500_INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
return err;
|
||||||
else
|
|
||||||
return request.reply(500, "text/html", "No handler registered.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicEndpoint* PsychicEndpoint::setFilter(PsychicRequestFilterFunction fn) {
|
bool PsychicEndpoint::matches(const char* uri)
|
||||||
_handler->setFilter(fn);
|
{
|
||||||
|
// 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
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicEndpoint* PsychicEndpoint::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) {
|
PsychicEndpoint* PsychicEndpoint::addMiddleware(PsychicMiddleware* middleware)
|
||||||
_handler->setAuthentication(username, password, method, realm, authFailMsg);
|
{
|
||||||
|
_handler->addMiddleware(middleware);
|
||||||
return this;
|
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"
|
#include "PsychicCore.h"
|
||||||
|
|
||||||
class PsychicHandler;
|
class PsychicHandler;
|
||||||
|
class PsychicMiddleware;
|
||||||
|
|
||||||
#ifdef ENABLE_ASYNC
|
#ifdef ENABLE_ASYNC
|
||||||
#include "async_worker.h"
|
#include "async_worker.h"
|
||||||
@@ -16,18 +17,30 @@ class PsychicEndpoint
|
|||||||
private:
|
private:
|
||||||
PsychicHttpServer* _server;
|
PsychicHttpServer* _server;
|
||||||
String _uri;
|
String _uri;
|
||||||
http_method _method;
|
int _method;
|
||||||
PsychicHandler* _handler;
|
PsychicHandler* _handler;
|
||||||
|
httpd_uri_match_func_t _uri_match_fn = nullptr; // use this change the endpoint matching function.
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PsychicEndpoint();
|
PsychicEndpoint();
|
||||||
PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri);
|
PsychicEndpoint(PsychicHttpServer* server, int method, const char* uri);
|
||||||
|
|
||||||
PsychicEndpoint* setHandler(PsychicHandler* handler);
|
PsychicEndpoint* setHandler(PsychicHandler* handler);
|
||||||
PsychicHandler* handler();
|
PsychicHandler* handler();
|
||||||
|
|
||||||
PsychicEndpoint* setFilter(PsychicRequestFilterFunction fn);
|
httpd_uri_match_func_t getURIMatchFunction();
|
||||||
PsychicEndpoint* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = "");
|
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();
|
String uri();
|
||||||
|
|
||||||
|
|||||||
@@ -19,18 +19,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "PsychicEventSource.h"
|
#include "PsychicEventSource.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
/*****************************************/
|
/*****************************************/
|
||||||
// PsychicEventSource - Handler
|
// PsychicEventSource - Handler
|
||||||
/*****************************************/
|
/*****************************************/
|
||||||
|
|
||||||
PsychicEventSource::PsychicEventSource() :
|
PsychicEventSource::PsychicEventSource() : PsychicHandler(),
|
||||||
PsychicHandler(),
|
|
||||||
_onOpen(NULL),
|
_onOpen(NULL),
|
||||||
_onClose(NULL)
|
_onClose(NULL)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
|
|
||||||
PsychicEventSource::~PsychicEventSource() {
|
PsychicEventSource::~PsychicEventSource()
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicEventSourceClient* PsychicEventSource::getClient(int socket)
|
PsychicEventSourceClient* PsychicEventSource::getClient(int socket)
|
||||||
@@ -43,23 +45,22 @@ PsychicEventSourceClient * PsychicEventSource::getClient(int socket)
|
|||||||
return (PsychicEventSourceClient*)client->_friend;
|
return (PsychicEventSourceClient*)client->_friend;
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicEventSourceClient * PsychicEventSource::getClient(PsychicClient *client) {
|
PsychicEventSourceClient* PsychicEventSource::getClient(PsychicClient* client)
|
||||||
|
{
|
||||||
return getClient(client->socket());
|
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
|
// start our open ended HTTP response
|
||||||
PsychicEventSourceResponse response(request);
|
PsychicEventSourceResponse response(resp);
|
||||||
esp_err_t err = response.send();
|
esp_err_t err = response.send();
|
||||||
|
|
||||||
// lookup our client
|
// lookup our client
|
||||||
PsychicClient* client = checkForNewClient(request->client());
|
PsychicClient* client = checkForNewClient(request->client());
|
||||||
if (client->isNew)
|
if (client->isNew) {
|
||||||
{
|
|
||||||
// did we get our last id?
|
// did we get our last id?
|
||||||
if(request->hasHeader("Last-Event-ID"))
|
if (request->hasHeader("Last-Event-ID")) {
|
||||||
{
|
|
||||||
PsychicEventSourceClient* buddy = getClient(client);
|
PsychicEventSourceClient* buddy = getClient(client);
|
||||||
buddy->_lastId = atoi(request->header("Last-Event-ID").c_str());
|
buddy->_lastId = atoi(request->header("Last-Event-ID").c_str());
|
||||||
}
|
}
|
||||||
@@ -71,31 +72,35 @@ esp_err_t PsychicEventSource::handleRequest(PsychicRequest *request)
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicEventSource * PsychicEventSource::onOpen(PsychicEventSourceClientCallback fn) {
|
PsychicEventSource* PsychicEventSource::onOpen(PsychicEventSourceClientCallback fn)
|
||||||
|
{
|
||||||
_onOpen = fn;
|
_onOpen = fn;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicEventSource * PsychicEventSource::onClose(PsychicEventSourceClientCallback fn) {
|
PsychicEventSource* PsychicEventSource::onClose(PsychicEventSourceClientCallback fn)
|
||||||
|
{
|
||||||
_onClose = fn;
|
_onClose = fn;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PsychicEventSource::addClient(PsychicClient *client) {
|
void PsychicEventSource::addClient(PsychicClient* client)
|
||||||
|
{
|
||||||
client->_friend = new PsychicEventSourceClient(client);
|
client->_friend = new PsychicEventSourceClient(client);
|
||||||
PsychicHandler::addClient(client);
|
PsychicHandler::addClient(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PsychicEventSource::removeClient(PsychicClient *client) {
|
void PsychicEventSource::removeClient(PsychicClient* client)
|
||||||
|
{
|
||||||
PsychicHandler::removeClient(client);
|
PsychicHandler::removeClient(client);
|
||||||
delete (PsychicEventSourceClient*)client->_friend;
|
delete (PsychicEventSourceClient*)client->_friend;
|
||||||
client->_friend = NULL;
|
client->_friend = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PsychicEventSource::openCallback(PsychicClient *client) {
|
void PsychicEventSource::openCallback(PsychicClient* client)
|
||||||
PsychicEventSourceClient *buddy = getClient(client);
|
|
||||||
if (buddy == NULL)
|
|
||||||
{
|
{
|
||||||
|
PsychicEventSourceClient* buddy = getClient(client);
|
||||||
|
if (buddy == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,10 +108,10 @@ void PsychicEventSource::openCallback(PsychicClient *client) {
|
|||||||
_onOpen(buddy);
|
_onOpen(buddy);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PsychicEventSource::closeCallback(PsychicClient *client) {
|
void PsychicEventSource::closeCallback(PsychicClient* client)
|
||||||
PsychicEventSourceClient *buddy = getClient(client);
|
|
||||||
if (buddy == NULL)
|
|
||||||
{
|
{
|
||||||
|
PsychicEventSourceClient* buddy = getClient(client);
|
||||||
|
if (buddy == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,66 +123,145 @@ void PsychicEventSource::send(const char *message, const char *event, uint32_t i
|
|||||||
{
|
{
|
||||||
String ev = generateEventMessage(message, event, id, reconnect);
|
String ev = generateEventMessage(message, event, id, reconnect);
|
||||||
for(PsychicClient *c : _clients) {
|
for(PsychicClient *c : _clients) {
|
||||||
|
if (c && c->_friend) {
|
||||||
((PsychicEventSourceClient*)c->_friend)->sendEvent(ev.c_str());
|
((PsychicEventSourceClient*)c->_friend)->sendEvent(ev.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*****************************************/
|
/*****************************************/
|
||||||
// PsychicEventSourceClient
|
// PsychicEventSourceClient
|
||||||
/*****************************************/
|
/*****************************************/
|
||||||
|
|
||||||
PsychicEventSourceClient::PsychicEventSourceClient(PsychicClient *client) :
|
PsychicEventSourceClient::PsychicEventSourceClient(PsychicClient* client) : PsychicClient(client->server(), client->socket()),
|
||||||
PsychicClient(client->server(), client->socket()),
|
|
||||||
_lastId(0)
|
_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);
|
String ev = generateEventMessage(message, event, id, reconnect);
|
||||||
sendEvent(ev.c_str());
|
sendEvent(ev.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void PsychicEventSourceClient::sendEvent(const char *event) {
|
void PsychicEventSourceClient::sendEvent(const char* event)
|
||||||
int result;
|
{
|
||||||
do {
|
_sendEventAsync(this->server(), this->socket(), event, strlen(event));
|
||||||
result = httpd_socket_send(this->server(), this->socket(), event, strlen(event), 0);
|
}
|
||||||
} while (result == HTTPD_SOCK_ERR_TIMEOUT);
|
|
||||||
|
|
||||||
//if (result < 0)
|
esp_err_t PsychicEventSourceClient::_sendEventAsync(httpd_handle_t handle, int socket, const char* event, size_t len)
|
||||||
//error log here
|
{
|
||||||
|
// 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::PsychicEventSourceResponse(PsychicRequest *request)
|
PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicResponse* response) : PsychicResponseDelegate(response)
|
||||||
: PsychicResponse(request)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
String out = String();
|
||||||
out.concat("HTTP/1.1 200 OK\r\n");
|
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
|
// get our global headers out of the way first
|
||||||
for (HTTPHeader header : DefaultHeaders::Instance().getHeaders())
|
for (auto& header : DefaultHeaders::Instance().getHeaders())
|
||||||
out.concat(String(header.field) + ": " + String(header.value) + "\r\n");
|
out.concat(header.field + ": " + header.value + "\r\n");
|
||||||
|
|
||||||
|
// now do our individual headers
|
||||||
|
for (auto& header : _response->headers())
|
||||||
|
out.concat(header.field + ": " + header.value + "\r\n");
|
||||||
|
|
||||||
// separator
|
// separator
|
||||||
out.concat("\r\n");
|
out.concat("\r\n");
|
||||||
|
|
||||||
int result;
|
int result;
|
||||||
do {
|
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);
|
} while (result == HTTPD_SOCK_ERR_TIMEOUT);
|
||||||
|
|
||||||
if (result < 0)
|
if (result < 0)
|
||||||
@@ -193,7 +277,8 @@ esp_err_t PsychicEventSourceResponse::send() {
|
|||||||
// Event Message Generator
|
// 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 = "";
|
String ev = "";
|
||||||
|
|
||||||
if (reconnect) {
|
if (reconnect) {
|
||||||
|
|||||||
@@ -20,9 +20,9 @@
|
|||||||
#ifndef PsychicEventSource_H_
|
#ifndef PsychicEventSource_H_
|
||||||
#define PsychicEventSource_H_
|
#define PsychicEventSource_H_
|
||||||
|
|
||||||
|
#include "PsychicClient.h"
|
||||||
#include "PsychicCore.h"
|
#include "PsychicCore.h"
|
||||||
#include "PsychicHandler.h"
|
#include "PsychicHandler.h"
|
||||||
#include "PsychicClient.h"
|
|
||||||
#include "PsychicResponse.h"
|
#include "PsychicResponse.h"
|
||||||
|
|
||||||
class PsychicEventSource;
|
class PsychicEventSource;
|
||||||
@@ -32,11 +32,24 @@ class PsychicResponse;
|
|||||||
|
|
||||||
typedef std::function<void(PsychicEventSourceClient* client)> PsychicEventSourceClientCallback;
|
typedef std::function<void(PsychicEventSourceClient* client)> PsychicEventSourceClientCallback;
|
||||||
|
|
||||||
class PsychicEventSourceClient : public PsychicClient {
|
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;
|
friend PsychicEventSource;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint32_t _lastId;
|
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:
|
public:
|
||||||
PsychicEventSourceClient(PsychicClient* client);
|
PsychicEventSourceClient(PsychicClient* client);
|
||||||
@@ -47,7 +60,8 @@ class PsychicEventSourceClient : public PsychicClient {
|
|||||||
void sendEvent(const char* event);
|
void sendEvent(const char* event);
|
||||||
};
|
};
|
||||||
|
|
||||||
class PsychicEventSource : public PsychicHandler {
|
class PsychicEventSource : public PsychicHandler
|
||||||
|
{
|
||||||
private:
|
private:
|
||||||
PsychicEventSourceClientCallback _onOpen;
|
PsychicEventSourceClientCallback _onOpen;
|
||||||
PsychicEventSourceClientCallback _onClose;
|
PsychicEventSourceClientCallback _onClose;
|
||||||
@@ -66,15 +80,16 @@ class PsychicEventSource : public PsychicHandler {
|
|||||||
PsychicEventSource* onOpen(PsychicEventSourceClientCallback fn);
|
PsychicEventSource* onOpen(PsychicEventSourceClientCallback fn);
|
||||||
PsychicEventSource* onClose(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:
|
public:
|
||||||
PsychicEventSourceResponse(PsychicRequest *request);
|
PsychicEventSourceResponse(PsychicResponse* response);
|
||||||
virtual esp_err_t send() override;
|
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);
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
#include "PsychicFileResponse.h"
|
#include "PsychicFileResponse.h"
|
||||||
#include "PsychicResponse.h"
|
|
||||||
#include "PsychicRequest.h"
|
#include "PsychicRequest.h"
|
||||||
|
#include "PsychicResponse.h"
|
||||||
|
|
||||||
|
PsychicFileResponse::PsychicFileResponse(PsychicResponse* response, FS& fs, const String& path, const String& contentType, bool download) : PsychicResponseDelegate(response)
|
||||||
PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path, const String& contentType, bool download)
|
{
|
||||||
: PsychicResponse(request) {
|
|
||||||
//_code = 200;
|
//_code = 200;
|
||||||
String _path(path);
|
String _path(path);
|
||||||
|
|
||||||
@@ -14,10 +13,10 @@ PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, FS &fs, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
_content = fs.open(_path, "r");
|
_content = fs.open(_path, "r");
|
||||||
_contentLength = _content.size();
|
setContentLength(_content.size());
|
||||||
|
|
||||||
if (contentType == "")
|
if (contentType == "")
|
||||||
_setContentType(path);
|
_setContentTypeFromPath(path);
|
||||||
else
|
else
|
||||||
setContentType(contentType.c_str());
|
setContentType(contentType.c_str());
|
||||||
|
|
||||||
@@ -35,8 +34,8 @@ PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, FS &fs, const
|
|||||||
addHeader("Content-Disposition", buf);
|
addHeader("Content-Disposition", buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, File content, const String& path, const String& contentType, bool download)
|
PsychicFileResponse::PsychicFileResponse(PsychicResponse* response, File content, const String& path, const String& contentType, bool download) : PsychicResponseDelegate(response)
|
||||||
: PsychicResponse(request) {
|
{
|
||||||
String _path(path);
|
String _path(path);
|
||||||
|
|
||||||
if (!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")) {
|
if (!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")) {
|
||||||
@@ -44,10 +43,10 @@ PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, File content,
|
|||||||
}
|
}
|
||||||
|
|
||||||
_content = content;
|
_content = content;
|
||||||
_contentLength = _content.size();
|
setContentLength(_content.size());
|
||||||
|
|
||||||
if (contentType == "")
|
if (contentType == "")
|
||||||
_setContentType(path);
|
_setContentTypeFromPath(path);
|
||||||
else
|
else
|
||||||
setContentType(contentType.c_str());
|
setContentType(contentType.c_str());
|
||||||
|
|
||||||
@@ -69,28 +68,48 @@ PsychicFileResponse::~PsychicFileResponse()
|
|||||||
_content.close();
|
_content.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PsychicFileResponse::_setContentType(const String& path){
|
void PsychicFileResponse::_setContentTypeFromPath(const String& path)
|
||||||
|
{
|
||||||
const char* _contentType;
|
const char* _contentType;
|
||||||
|
|
||||||
if (path.endsWith(".html")) _contentType = "text/html";
|
if (path.endsWith(".html"))
|
||||||
else if (path.endsWith(".htm")) _contentType = "text/html";
|
_contentType = "text/html";
|
||||||
else if (path.endsWith(".css")) _contentType = "text/css";
|
else if (path.endsWith(".htm"))
|
||||||
else if (path.endsWith(".json")) _contentType = "application/json";
|
_contentType = "text/html";
|
||||||
else if (path.endsWith(".js")) _contentType = "application/javascript";
|
else if (path.endsWith(".css"))
|
||||||
else if (path.endsWith(".png")) _contentType = "image/png";
|
_contentType = "text/css";
|
||||||
else if (path.endsWith(".gif")) _contentType = "image/gif";
|
else if (path.endsWith(".json"))
|
||||||
else if (path.endsWith(".jpg")) _contentType = "image/jpeg";
|
_contentType = "application/json";
|
||||||
else if (path.endsWith(".ico")) _contentType = "image/x-icon";
|
else if (path.endsWith(".js"))
|
||||||
else if (path.endsWith(".svg")) _contentType = "image/svg+xml";
|
_contentType = "application/javascript";
|
||||||
else if (path.endsWith(".eot")) _contentType = "font/eot";
|
else if (path.endsWith(".png"))
|
||||||
else if (path.endsWith(".woff")) _contentType = "font/woff";
|
_contentType = "image/png";
|
||||||
else if (path.endsWith(".woff2")) _contentType = "font/woff2";
|
else if (path.endsWith(".gif"))
|
||||||
else if (path.endsWith(".ttf")) _contentType = "font/ttf";
|
_contentType = "image/gif";
|
||||||
else if (path.endsWith(".xml")) _contentType = "text/xml";
|
else if (path.endsWith(".jpg"))
|
||||||
else if (path.endsWith(".pdf")) _contentType = "application/pdf";
|
_contentType = "image/jpeg";
|
||||||
else if (path.endsWith(".zip")) _contentType = "application/zip";
|
else if (path.endsWith(".ico"))
|
||||||
else if(path.endsWith(".gz")) _contentType = "application/x-gzip";
|
_contentType = "image/x-icon";
|
||||||
else _contentType = "text/plain";
|
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);
|
setContentType(_contentType);
|
||||||
}
|
}
|
||||||
@@ -101,43 +120,37 @@ esp_err_t PsychicFileResponse::send()
|
|||||||
|
|
||||||
// just send small files directly
|
// just send small files directly
|
||||||
size_t size = getContentLength();
|
size_t size = getContentLength();
|
||||||
if (size < FILE_CHUNK_SIZE)
|
if (size < FILE_CHUNK_SIZE) {
|
||||||
{
|
|
||||||
uint8_t* buffer = (uint8_t*)malloc(size);
|
uint8_t* buffer = (uint8_t*)malloc(size);
|
||||||
if (buffer == NULL)
|
if (buffer == NULL) {
|
||||||
{
|
ESP_LOGE(PH_TAG, "Unable to allocate %" PRIu32 " bytes to send chunk", size);
|
||||||
/* Respond with 500 Internal Server Error */
|
httpd_resp_send_err(request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
|
||||||
httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
|
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t readSize = _content.readBytes((char*)buffer, size);
|
size_t readSize = _content.readBytes((char*)buffer, size);
|
||||||
|
|
||||||
this->setContent(buffer, readSize);
|
setContent(buffer, readSize);
|
||||||
err = PsychicResponse::send();
|
err = _response->send();
|
||||||
|
|
||||||
free(buffer);
|
free(buffer);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Retrieve the pointer to scratch buffer for temporary storage */
|
/* Retrieve the pointer to scratch buffer for temporary storage */
|
||||||
char* chunk = (char*)malloc(FILE_CHUNK_SIZE);
|
char* chunk = (char*)malloc(FILE_CHUNK_SIZE);
|
||||||
if (chunk == NULL)
|
if (chunk == NULL) {
|
||||||
{
|
ESP_LOGE(PH_TAG, "Unable to allocate %" PRIu32 " bytes to send chunk", FILE_CHUNK_SIZE);
|
||||||
/* Respond with 500 Internal Server Error */
|
httpd_resp_send_err(request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
|
||||||
httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
|
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->sendHeaders();
|
sendHeaders();
|
||||||
|
|
||||||
size_t chunksize;
|
size_t chunksize;
|
||||||
do {
|
do {
|
||||||
/* Read file in chunks into the scratch buffer */
|
/* Read file in chunks into the scratch buffer */
|
||||||
chunksize = _content.readBytes(chunk, FILE_CHUNK_SIZE);
|
chunksize = _content.readBytes(chunk, FILE_CHUNK_SIZE);
|
||||||
if (chunksize > 0)
|
if (chunksize > 0) {
|
||||||
{
|
err = sendChunk((uint8_t*)chunk, chunksize);
|
||||||
err = this->sendChunk((uint8_t *)chunk, chunksize);
|
|
||||||
if (err != ESP_OK)
|
if (err != ESP_OK)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -148,10 +161,9 @@ esp_err_t PsychicFileResponse::send()
|
|||||||
// keep track of our memory
|
// keep track of our memory
|
||||||
free(chunk);
|
free(chunk);
|
||||||
|
|
||||||
if (err == ESP_OK)
|
if (err == ESP_OK) {
|
||||||
{
|
|
||||||
ESP_LOGD(PH_TAG, "File sending complete");
|
ESP_LOGD(PH_TAG, "File sending complete");
|
||||||
this->finishChunking();
|
finishChunking();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,16 +6,18 @@
|
|||||||
|
|
||||||
class PsychicRequest;
|
class PsychicRequest;
|
||||||
|
|
||||||
class PsychicFileResponse: public PsychicResponse
|
class PsychicFileResponse : public PsychicResponseDelegate
|
||||||
{
|
{
|
||||||
using File = fs::File;
|
using File = fs::File;
|
||||||
using FS = fs::FS;
|
using FS = fs::FS;
|
||||||
private:
|
|
||||||
|
protected:
|
||||||
File _content;
|
File _content;
|
||||||
void _setContentType(const String& path);
|
void _setContentTypeFromPath(const String& path);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PsychicFileResponse(PsychicRequest *request, FS &fs, 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(PsychicRequest *request, File content, 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();
|
~PsychicFileResponse();
|
||||||
esp_err_t send();
|
esp_err_t send();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,66 +1,53 @@
|
|||||||
#include "PsychicHandler.h"
|
#include "PsychicHandler.h"
|
||||||
|
|
||||||
PsychicHandler::PsychicHandler() :
|
PsychicHandler::PsychicHandler()
|
||||||
_filter(NULL),
|
{
|
||||||
_server(NULL),
|
}
|
||||||
_username(""),
|
|
||||||
_password(""),
|
|
||||||
_method(DIGEST_AUTH),
|
|
||||||
_realm(""),
|
|
||||||
_authFailMsg(""),
|
|
||||||
_subprotocol("")
|
|
||||||
{}
|
|
||||||
|
|
||||||
PsychicHandler::~PsychicHandler() {
|
PsychicHandler::~PsychicHandler()
|
||||||
|
{
|
||||||
|
delete _chain;
|
||||||
// actual PsychicClient deletion handled by PsychicServer
|
// actual PsychicClient deletion handled by PsychicServer
|
||||||
// for (PsychicClient *client : _clients)
|
// for (PsychicClient *client : _clients)
|
||||||
// delete(client);
|
// delete(client);
|
||||||
_clients.clear();
|
_clients.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicHandler* PsychicHandler::setFilter(PsychicRequestFilterFunction fn) {
|
PsychicHandler* PsychicHandler::addFilter(PsychicRequestFilterFunction fn)
|
||||||
_filter = fn;
|
{
|
||||||
|
_filters.push_back(fn);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PsychicHandler::filter(PsychicRequest *request){
|
bool PsychicHandler::filter(PsychicRequest* request)
|
||||||
return _filter == NULL || _filter(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) {
|
void PsychicHandler::setSubprotocol(const String& subprotocol)
|
||||||
|
{
|
||||||
this->_subprotocol = subprotocol;
|
this->_subprotocol = subprotocol;
|
||||||
}
|
}
|
||||||
const char* PsychicHandler::getSubprotocol() const {
|
const char* PsychicHandler::getSubprotocol() const
|
||||||
|
{
|
||||||
return _subprotocol.c_str();
|
return _subprotocol.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicHandler* PsychicHandler::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) {
|
|
||||||
_username = String(username);
|
|
||||||
_password = String(password);
|
|
||||||
_method = method;
|
|
||||||
_realm = String(realm);
|
|
||||||
_authFailMsg = String(authFailMsg);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool PsychicHandler::needsAuthentication(PsychicRequest *request) {
|
|
||||||
return (_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t PsychicHandler::authenticate(PsychicRequest *request) {
|
|
||||||
return request->requestAuthentication(_method, _realm.c_str(), _authFailMsg.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
PsychicClient* PsychicHandler::checkForNewClient(PsychicClient* client)
|
PsychicClient* PsychicHandler::checkForNewClient(PsychicClient* client)
|
||||||
{
|
{
|
||||||
PsychicClient* c = PsychicHandler::getClient(client);
|
PsychicClient* c = PsychicHandler::getClient(client);
|
||||||
if (c == NULL)
|
if (c == NULL) {
|
||||||
{
|
|
||||||
c = client;
|
c = client;
|
||||||
addClient(c);
|
addClient(c);
|
||||||
c->isNew = true;
|
c->isNew = true;
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
c->isNew = false;
|
c->isNew = false;
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
@@ -68,18 +55,19 @@ PsychicClient * PsychicHandler::checkForNewClient(PsychicClient *client)
|
|||||||
|
|
||||||
void PsychicHandler::checkForClosedClient(PsychicClient* client)
|
void PsychicHandler::checkForClosedClient(PsychicClient* client)
|
||||||
{
|
{
|
||||||
if (hasClient(client))
|
if (hasClient(client)) {
|
||||||
{
|
|
||||||
closeCallback(client);
|
closeCallback(client);
|
||||||
removeClient(client);
|
removeClient(client);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PsychicHandler::addClient(PsychicClient *client) {
|
void PsychicHandler::addClient(PsychicClient* client)
|
||||||
|
{
|
||||||
_clients.push_back(client);
|
_clients.push_back(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PsychicHandler::removeClient(PsychicClient *client) {
|
void PsychicHandler::removeClient(PsychicClient* client)
|
||||||
|
{
|
||||||
_clients.remove(client);
|
_clients.remove(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,14 +86,63 @@ PsychicClient * PsychicHandler::getClient(int socket)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicClient * PsychicHandler::getClient(PsychicClient *client) {
|
PsychicClient* PsychicHandler::getClient(PsychicClient* client)
|
||||||
|
{
|
||||||
return PsychicHandler::getClient(client->socket());
|
return PsychicHandler::getClient(client->socket());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PsychicHandler::hasClient(PsychicClient *socket) {
|
bool PsychicHandler::hasClient(PsychicClient* socket)
|
||||||
|
{
|
||||||
return PsychicHandler::getClient(socket) != NULL;
|
return PsychicHandler::getClient(socket) != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::list<PsychicClient*>& PsychicHandler::getClientList() {
|
const std::list<PsychicClient*>& PsychicHandler::getClientList()
|
||||||
|
{
|
||||||
return _clients;
|
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 PsychicEndpoint;
|
||||||
class PsychicHttpServer;
|
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 {
|
class PsychicHandler
|
||||||
|
{
|
||||||
friend PsychicEndpoint;
|
friend PsychicEndpoint;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
PsychicRequestFilterFunction _filter;
|
PsychicHttpServer* _server = nullptr;
|
||||||
PsychicHttpServer *_server;
|
PsychicMiddlewareChain* _chain = nullptr;
|
||||||
|
std::list<PsychicRequestFilterFunction> _filters;
|
||||||
String _username;
|
|
||||||
String _password;
|
|
||||||
HTTPAuthMethod _method;
|
|
||||||
String _realm;
|
|
||||||
String _authFailMsg;
|
|
||||||
|
|
||||||
String _subprotocol;
|
String _subprotocol;
|
||||||
|
|
||||||
@@ -32,13 +30,6 @@ class PsychicHandler {
|
|||||||
PsychicHandler();
|
PsychicHandler();
|
||||||
virtual ~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; };
|
virtual bool isWebSocket() { return false; };
|
||||||
|
|
||||||
void setSubprotocol(const String& subprotocol);
|
void setSubprotocol(const String& subprotocol);
|
||||||
@@ -58,9 +49,20 @@ class PsychicHandler {
|
|||||||
int count() { return _clients.size(); };
|
int count() { return _clients.size(); };
|
||||||
const std::list<PsychicClient*>& getClientList();
|
const std::list<PsychicClient*>& getClientList();
|
||||||
|
|
||||||
|
// 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
|
// derived classes must implement these functions
|
||||||
virtual bool canHandle(PsychicRequest* request) { return true; };
|
virtual bool canHandle(PsychicRequest* request) { return true; };
|
||||||
virtual esp_err_t handleRequest(PsychicRequest *request) = 0;
|
virtual esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) { return HTTPD_404_NOT_FOUND; };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -3,22 +3,31 @@
|
|||||||
|
|
||||||
// #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 "PsychicHttpServer.h"
|
||||||
|
#include "PsychicJson.h"
|
||||||
|
#include "PsychicMiddleware.h"
|
||||||
|
#include "PsychicMiddlewareChain.h"
|
||||||
|
#include "PsychicMiddlewares.h"
|
||||||
#include "PsychicRequest.h"
|
#include "PsychicRequest.h"
|
||||||
#include "PsychicResponse.h"
|
#include "PsychicResponse.h"
|
||||||
#include "PsychicEndpoint.h"
|
|
||||||
#include "PsychicHandler.h"
|
|
||||||
#include "PsychicStaticFileHandler.h"
|
#include "PsychicStaticFileHandler.h"
|
||||||
#include "PsychicFileResponse.h"
|
|
||||||
#include "PsychicStreamResponse.h"
|
#include "PsychicStreamResponse.h"
|
||||||
#include "PsychicUploadHandler.h"
|
#include "PsychicUploadHandler.h"
|
||||||
|
#include "PsychicVersion.h"
|
||||||
#include "PsychicWebSocket.h"
|
#include "PsychicWebSocket.h"
|
||||||
#include "PsychicEventSource.h"
|
#include <http_status.h>
|
||||||
#include "PsychicJson.h"
|
|
||||||
|
|
||||||
#ifdef ENABLE_ASYNC
|
#ifdef ENABLE_ASYNC
|
||||||
#include "async_worker.h"
|
#include "async_worker.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// debugging library
|
||||||
|
#ifdef PSY_USE_ARDUINO_TRACE
|
||||||
|
#include <ArduinoTrace.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* PsychicHttp_h */
|
#endif /* PsychicHttp_h */
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
#include "PsychicHttpServer.h"
|
#include "PsychicHttpServer.h"
|
||||||
#include "PsychicEndpoint.h"
|
#include "PsychicEndpoint.h"
|
||||||
#include "PsychicHandler.h"
|
#include "PsychicHandler.h"
|
||||||
#include "PsychicWebHandler.h"
|
|
||||||
#include "PsychicStaticFileHandler.h"
|
|
||||||
#include "PsychicWebSocket.h"
|
|
||||||
#include "PsychicJson.h"
|
#include "PsychicJson.h"
|
||||||
|
#include "PsychicStaticFileHandler.h"
|
||||||
|
#include "PsychicWebHandler.h"
|
||||||
|
#include "PsychicWebSocket.h"
|
||||||
#include "WiFi.h"
|
#include "WiFi.h"
|
||||||
|
#ifdef PSY_ENABLE_ETHERNET
|
||||||
|
#include "ETH.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
PsychicHttpServer::PsychicHttpServer() :
|
PsychicHttpServer::PsychicHttpServer(uint16_t port)
|
||||||
_onOpen(NULL),
|
|
||||||
_onClose(NULL)
|
|
||||||
{
|
{
|
||||||
maxRequestBodySize = MAX_REQUEST_BODY_SIZE;
|
maxRequestBodySize = MAX_REQUEST_BODY_SIZE;
|
||||||
maxUploadSize = MAX_UPLOAD_SIZE;
|
maxUploadSize = MAX_UPLOAD_SIZE;
|
||||||
@@ -21,10 +22,13 @@ PsychicHttpServer::PsychicHttpServer() :
|
|||||||
config = HTTPD_DEFAULT_CONFIG();
|
config = HTTPD_DEFAULT_CONFIG();
|
||||||
config.open_fn = PsychicHttpServer::openCallback;
|
config.open_fn = PsychicHttpServer::openCallback;
|
||||||
config.close_fn = PsychicHttpServer::closeCallback;
|
config.close_fn = PsychicHttpServer::closeCallback;
|
||||||
config.uri_match_fn = httpd_uri_match_wildcard;
|
|
||||||
config.global_user_ctx = this;
|
config.global_user_ctx = this;
|
||||||
config.global_user_ctx_free_fn = destroy;
|
config.global_user_ctx_free_fn = PsychicHttpServer::destroy;
|
||||||
config.max_uri_handlers = 20;
|
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.
|
||||||
|
|
||||||
|
// our internal matching function for endpoints
|
||||||
|
_uri_match_fn = MATCH_WILDCARD; // use this change the endpoint matching function.
|
||||||
|
|
||||||
#ifdef ENABLE_ASYNC
|
#ifdef ENABLE_ASYNC
|
||||||
// It is advisable that httpd_config_t->max_open_sockets > MAX_ASYNC_REQUESTS
|
// It is advisable that httpd_config_t->max_open_sockets > MAX_ASYNC_REQUESTS
|
||||||
@@ -35,10 +39,14 @@ PsychicHttpServer::PsychicHttpServer() :
|
|||||||
config.max_open_sockets = ASYNC_WORKER_COUNT + 1;
|
config.max_open_sockets = ASYNC_WORKER_COUNT + 1;
|
||||||
config.lru_purge_enable = true;
|
config.lru_purge_enable = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
setPort(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicHttpServer::~PsychicHttpServer()
|
PsychicHttpServer::~PsychicHttpServer()
|
||||||
{
|
{
|
||||||
|
_esp_idf_endpoints.clear();
|
||||||
|
|
||||||
for (auto* client : _clients)
|
for (auto* client : _clients)
|
||||||
delete (client);
|
delete (client);
|
||||||
_clients.clear();
|
_clients.clear();
|
||||||
@@ -51,7 +59,12 @@ PsychicHttpServer::~PsychicHttpServer()
|
|||||||
delete (handler);
|
delete (handler);
|
||||||
_handlers.clear();
|
_handlers.clear();
|
||||||
|
|
||||||
|
for (auto* rewrite : _rewrites)
|
||||||
|
delete (rewrite);
|
||||||
|
_rewrites.clear();
|
||||||
|
|
||||||
delete defaultEndpoint;
|
delete defaultEndpoint;
|
||||||
|
delete _chain;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PsychicHttpServer::destroy(void* ctx)
|
void PsychicHttpServer::destroy(void* ctx)
|
||||||
@@ -59,16 +72,42 @@ void PsychicHttpServer::destroy(void *ctx)
|
|||||||
// do not release any resource for PsychicHttpServer in order to be able to restart it after stopping
|
// 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;
|
this->config.server_port = port;
|
||||||
|
|
||||||
return this->_start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t PsychicHttpServer::_start()
|
uint16_t PsychicHttpServer::getPort()
|
||||||
{
|
{
|
||||||
|
return this->config.server_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PsychicHttpServer::isConnected()
|
||||||
|
{
|
||||||
|
if (WiFi.softAPIP())
|
||||||
|
return true;
|
||||||
|
if (WiFi.localIP())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
#ifdef PSY_ENABLE_ETHERNET
|
||||||
|
if (ETH.localIP())
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicHttpServer::start()
|
||||||
|
{
|
||||||
|
if (_running)
|
||||||
|
return ESP_OK;
|
||||||
|
|
||||||
|
// starting without network will crash us.
|
||||||
|
if (!isConnected()) {
|
||||||
|
ESP_LOGE(PH_TAG, "Server start failed - no network.");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t ret;
|
esp_err_t ret;
|
||||||
|
|
||||||
#ifdef ENABLE_ASYNC
|
#ifdef ENABLE_ASYNC
|
||||||
@@ -76,45 +115,171 @@ esp_err_t PsychicHttpServer::_start()
|
|||||||
start_async_req_workers();
|
start_async_req_workers();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// one URI handler for each http_method
|
||||||
|
config.max_uri_handlers = supported_methods.size() + _esp_idf_endpoints.size();
|
||||||
|
|
||||||
// fire it up.
|
// fire it up.
|
||||||
ret = _startServer();
|
ret = _startServer();
|
||||||
if (ret != ESP_OK)
|
if (ret != ESP_OK) {
|
||||||
{
|
|
||||||
ESP_LOGE(PH_TAG, "Server start failed (%s)", esp_err_to_name(ret));
|
ESP_LOGE(PH_TAG, "Server start failed (%s)", esp_err_to_name(ret));
|
||||||
return 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
|
// Register handler
|
||||||
ret = httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, PsychicHttpServer::notFoundHandler);
|
ret = httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, PsychicHttpServer::notFoundHandler);
|
||||||
if (ret != ESP_OK)
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t PsychicHttpServer::_startServer() {
|
esp_err_t PsychicHttpServer::_startServer()
|
||||||
|
{
|
||||||
return httpd_start(&this->server, &this->config);
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicHandler& PsychicHttpServer::addHandler(PsychicHandler* handler){
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
_handlers.push_back(handler);
|
||||||
return *handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PsychicHttpServer::removeHandler(PsychicHandler *handler){
|
void PsychicHttpServer::removeHandler(PsychicHandler* handler)
|
||||||
|
{
|
||||||
_handlers.remove(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);
|
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();
|
||||||
|
|
||||||
@@ -126,7 +291,7 @@ PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHandler *handler)
|
|||||||
return on(uri, HTTP_GET, 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
|
// make our endpoint
|
||||||
PsychicEndpoint* endpoint = new PsychicEndpoint(this, method, uri);
|
PsychicEndpoint* endpoint = new PsychicEndpoint(this, method, uri);
|
||||||
@@ -134,22 +299,30 @@ PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, Psyc
|
|||||||
// set our handler
|
// set our handler
|
||||||
endpoint->setHandler(handler);
|
endpoint->setHandler(handler);
|
||||||
|
|
||||||
|
// websockets need a real endpoint in esp-idf
|
||||||
|
if (handler->isWebSocket()) {
|
||||||
// URI handler structure
|
// URI handler structure
|
||||||
httpd_uri_t my_uri {
|
httpd_uri_t my_uri;
|
||||||
.uri = uri,
|
my_uri.uri = uri;
|
||||||
.method = method,
|
my_uri.method = HTTP_GET;
|
||||||
.handler = PsychicEndpoint::requestCallback,
|
my_uri.handler = PsychicEndpoint::requestCallback;
|
||||||
.user_ctx = endpoint,
|
my_uri.user_ctx = endpoint;
|
||||||
.is_websocket = handler->isWebSocket(),
|
my_uri.is_websocket = handler->isWebSocket();
|
||||||
.supported_subprotocol = handler->getSubprotocol()
|
my_uri.supported_subprotocol = handler->getSubprotocol();
|
||||||
};
|
|
||||||
|
|
||||||
// Register endpoint with ESP-IDF server
|
// save it to our 'real' handlers for later.
|
||||||
esp_err_t ret = httpd_register_uri_handler(this->server, &my_uri);
|
_esp_idf_endpoints.push_back(my_uri);
|
||||||
if (ret != ESP_OK)
|
}
|
||||||
ESP_LOGE(PH_TAG, "Add endpoint failed (%s)", esp_err_to_name(ret));
|
|
||||||
|
|
||||||
//save it for later
|
// 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);
|
_endpoints.push_back(endpoint);
|
||||||
|
|
||||||
return endpoint;
|
return endpoint;
|
||||||
@@ -160,7 +333,7 @@ PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHttpRequestCallba
|
|||||||
return on(uri, HTTP_GET, fn);
|
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
|
// these basic requests need a basic web handler
|
||||||
PsychicWebHandler* handler = new PsychicWebHandler();
|
PsychicWebHandler* handler = new PsychicWebHandler();
|
||||||
@@ -174,7 +347,7 @@ PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicJsonRequestCallba
|
|||||||
return on(uri, HTTP_GET, fn);
|
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
|
// these basic requests need a basic web handler
|
||||||
PsychicJsonHandler* handler = new PsychicJsonHandler();
|
PsychicJsonHandler* handler = new PsychicJsonHandler();
|
||||||
@@ -183,6 +356,79 @@ PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, Psyc
|
|||||||
return on(uri, method, handler);
|
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)
|
void PsychicHttpServer::onNotFound(PsychicHttpRequestCallback fn)
|
||||||
{
|
{
|
||||||
PsychicWebHandler* handler = new PsychicWebHandler();
|
PsychicWebHandler* handler = new PsychicWebHandler();
|
||||||
@@ -191,42 +437,97 @@ void PsychicHttpServer::onNotFound(PsychicHttpRequestCallback fn)
|
|||||||
this->defaultEndpoint->setHandler(handler);
|
this->defaultEndpoint->setHandler(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PsychicHttpServer::_rewriteRequest(PsychicRequest* request)
|
||||||
|
{
|
||||||
|
for (auto* r : _rewrites) {
|
||||||
|
if (r->match(request)) {
|
||||||
|
request->_setUri(r->toUrl().c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t PsychicHttpServer::requestHandler(httpd_req_t* req)
|
||||||
|
{
|
||||||
|
PsychicHttpServer* server = (PsychicHttpServer*)httpd_get_global_user_ctx(req->handle);
|
||||||
|
PsychicRequest request(server, req);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
esp_err_t PsychicHttpServer::notFoundHandler(httpd_req_t* req, httpd_err_code_t err)
|
||||||
{
|
{
|
||||||
PsychicHttpServer* server = (PsychicHttpServer*)httpd_get_global_user_ctx(req->handle);
|
PsychicHttpServer* server = (PsychicHttpServer*)httpd_get_global_user_ctx(req->handle);
|
||||||
PsychicRequest request(server, req);
|
PsychicRequest request(server, req);
|
||||||
|
|
||||||
//loop through our global handlers and see if anyone wants it
|
// pull up our default handler / endpoint
|
||||||
for(auto *handler: server->_handlers)
|
|
||||||
{
|
|
||||||
//are we capable of handling this?
|
|
||||||
if (handler->filter(&request) && handler->canHandle(&request))
|
|
||||||
{
|
|
||||||
//check our credentials
|
|
||||||
if (handler->needsAuthentication(&request))
|
|
||||||
return handler->authenticate(&request);
|
|
||||||
else
|
|
||||||
return handler->handleRequest(&request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//nothing found, give it to our defaultEndpoint
|
|
||||||
PsychicHandler* handler = server->defaultEndpoint->handler();
|
PsychicHandler* handler = server->defaultEndpoint->handler();
|
||||||
if (handler->filter(&request) && handler->canHandle(&request))
|
if (!handler)
|
||||||
return handler->handleRequest(&request);
|
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.
|
// not sure how we got this far.
|
||||||
return ESP_ERR_HTTPD_INVALID_REQ;
|
return request.response()->send(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest *request)
|
esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest* request, PsychicResponse* response)
|
||||||
{
|
{
|
||||||
request->reply(404, "text/html", "That URI does not exist.");
|
return response->send(404, "text/html", "That URI does not exist.");
|
||||||
|
|
||||||
return ESP_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PsychicHttpServer::onOpen(PsychicClientCallback handler) {
|
void PsychicHttpServer::onOpen(PsychicClientCallback handler)
|
||||||
|
{
|
||||||
this->_onOpen = handler;
|
this->_onOpen = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,8 +540,7 @@ esp_err_t PsychicHttpServer::openCallback(httpd_handle_t hd, int sockfd)
|
|||||||
|
|
||||||
// lookup our client
|
// lookup our client
|
||||||
PsychicClient* client = server->getClient(sockfd);
|
PsychicClient* client = server->getClient(sockfd);
|
||||||
if (client == NULL)
|
if (client == NULL) {
|
||||||
{
|
|
||||||
client = new PsychicClient(hd, sockfd);
|
client = new PsychicClient(hd, sockfd);
|
||||||
server->addClient(client);
|
server->addClient(client);
|
||||||
}
|
}
|
||||||
@@ -252,7 +552,8 @@ esp_err_t PsychicHttpServer::openCallback(httpd_handle_t hd, int sockfd)
|
|||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PsychicHttpServer::onClose(PsychicClientCallback handler) {
|
void PsychicHttpServer::onClose(PsychicClientCallback handler)
|
||||||
|
{
|
||||||
this->_onClose = handler;
|
this->_onClose = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,11 +565,9 @@ void PsychicHttpServer::closeCallback(httpd_handle_t hd, int sockfd)
|
|||||||
|
|
||||||
// lookup our client
|
// lookup our client
|
||||||
PsychicClient* client = server->getClient(sockfd);
|
PsychicClient* client = server->getClient(sockfd);
|
||||||
if (client != NULL)
|
if (client != NULL) {
|
||||||
{
|
|
||||||
// give our handlers a chance to handle a disconnect first
|
// give our handlers a chance to handle a disconnect first
|
||||||
for (PsychicEndpoint * endpoint : server->_endpoints)
|
for (PsychicEndpoint* endpoint : server->_endpoints) {
|
||||||
{
|
|
||||||
PsychicHandler* handler = endpoint->handler();
|
PsychicHandler* handler = endpoint->handler();
|
||||||
handler->checkForClosedClient(client);
|
handler->checkForClosedClient(client);
|
||||||
}
|
}
|
||||||
@@ -279,8 +578,7 @@ void PsychicHttpServer::closeCallback(httpd_handle_t hd, int sockfd)
|
|||||||
|
|
||||||
// remove it from our list
|
// remove it from our list
|
||||||
server->removeClient(client);
|
server->removeClient(client);
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
ESP_LOGE(PH_TAG, "No client record %d", sockfd);
|
ESP_LOGE(PH_TAG, "No client record %d", sockfd);
|
||||||
|
|
||||||
// finally close it out.
|
// finally close it out.
|
||||||
@@ -295,16 +593,19 @@ PsychicStaticFileHandler* PsychicHttpServer::serveStatic(const char* uri, fs::FS
|
|||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PsychicHttpServer::addClient(PsychicClient *client) {
|
void PsychicHttpServer::addClient(PsychicClient* client)
|
||||||
|
{
|
||||||
_clients.push_back(client);
|
_clients.push_back(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PsychicHttpServer::removeClient(PsychicClient *client) {
|
void PsychicHttpServer::removeClient(PsychicClient* client)
|
||||||
|
{
|
||||||
_clients.remove(client);
|
_clients.remove(client);
|
||||||
delete client;
|
delete client;
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicClient * PsychicHttpServer::getClient(int socket) {
|
PsychicClient* PsychicHttpServer::getClient(int socket)
|
||||||
|
{
|
||||||
for (PsychicClient* client : _clients)
|
for (PsychicClient* client : _clients)
|
||||||
if (client->socket() == socket)
|
if (client->socket() == socket)
|
||||||
return client;
|
return client;
|
||||||
@@ -312,32 +613,29 @@ PsychicClient * PsychicHttpServer::getClient(int socket) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicClient * PsychicHttpServer::getClient(httpd_req_t *req) {
|
PsychicClient* PsychicHttpServer::getClient(httpd_req_t* req)
|
||||||
|
{
|
||||||
return getClient(httpd_req_to_sockfd(req));
|
return getClient(httpd_req_to_sockfd(req));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PsychicHttpServer::hasClient(int socket) {
|
bool PsychicHttpServer::hasClient(int socket)
|
||||||
|
{
|
||||||
return getClient(socket) != NULL;
|
return getClient(socket) != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::list<PsychicClient*>& PsychicHttpServer::getClientList() {
|
const std::list<PsychicClient*>& PsychicHttpServer::getClientList()
|
||||||
|
{
|
||||||
return _clients;
|
return _clients;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ON_STA_FILTER(PsychicRequest *request) {
|
bool ON_STA_FILTER(PsychicRequest* request)
|
||||||
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
{
|
||||||
return WiFi.localIP() == request->client()->localIP();
|
return WiFi.localIP() == request->client()->localIP();
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ON_AP_FILTER(PsychicRequest *request) {
|
bool ON_AP_FILTER(PsychicRequest* request)
|
||||||
#ifndef CONFIG_IDF_TARGET_ESP32H2
|
{
|
||||||
return WiFi.softAPIP() == request->client()->localIP();
|
return WiFi.softAPIP() == request->client()->localIP();
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String urlDecode(const char* encoded)
|
String urlDecode(const char* encoded)
|
||||||
@@ -372,3 +670,24 @@ String urlDecode(const char* encoded)
|
|||||||
|
|
||||||
return output;
|
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
|
#ifndef PsychicHttpServer_h
|
||||||
#define PsychicHttpServer_h
|
#define PsychicHttpServer_h
|
||||||
|
|
||||||
#include "PsychicCore.h"
|
|
||||||
#include "PsychicClient.h"
|
#include "PsychicClient.h"
|
||||||
|
#include "PsychicCore.h"
|
||||||
#include "PsychicHandler.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 PsychicEndpoint;
|
||||||
class PsychicHandler;
|
class PsychicHandler;
|
||||||
@@ -12,21 +23,41 @@ class PsychicStaticFileHandler;
|
|||||||
class PsychicHttpServer
|
class PsychicHttpServer
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
bool _use_ssl = false;
|
std::list<httpd_uri_t> _esp_idf_endpoints;
|
||||||
std::list<PsychicEndpoint*> _endpoints;
|
std::list<PsychicEndpoint*> _endpoints;
|
||||||
std::list<PsychicHandler*> _handlers;
|
std::list<PsychicHandler*> _handlers;
|
||||||
std::list<PsychicClient*> _clients;
|
std::list<PsychicClient*> _clients;
|
||||||
|
std::list<PsychicRewrite*> _rewrites;
|
||||||
|
std::list<PsychicRequestFilterFunction> _filters;
|
||||||
|
|
||||||
PsychicClientCallback _onOpen;
|
PsychicClientCallback _onOpen = nullptr;
|
||||||
PsychicClientCallback _onClose;
|
PsychicClientCallback _onClose = nullptr;
|
||||||
|
PsychicMiddlewareChain* _chain = nullptr;
|
||||||
|
|
||||||
esp_err_t _start();
|
esp_err_t _start();
|
||||||
virtual esp_err_t _startServer();
|
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:
|
public:
|
||||||
PsychicHttpServer();
|
PsychicHttpServer(uint16_t port = 80);
|
||||||
virtual ~PsychicHttpServer();
|
virtual ~PsychicHttpServer();
|
||||||
|
|
||||||
|
// 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
|
// esp-idf specific stuff
|
||||||
httpd_handle_t server;
|
httpd_handle_t server;
|
||||||
httpd_config_t config;
|
httpd_config_t config;
|
||||||
@@ -39,11 +70,25 @@ class PsychicHttpServer
|
|||||||
|
|
||||||
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 isConnected();
|
||||||
|
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 removeHandler(PsychicHandler* handler);
|
||||||
|
|
||||||
void addClient(PsychicClient* client);
|
void addClient(PsychicClient* client);
|
||||||
@@ -55,27 +100,47 @@ class PsychicHttpServer
|
|||||||
const std::list<PsychicClient*>& getClientList();
|
const std::list<PsychicClient*>& getClientList();
|
||||||
|
|
||||||
PsychicEndpoint* on(const char* uri);
|
PsychicEndpoint* on(const char* uri);
|
||||||
PsychicEndpoint* on(const char* uri, http_method method);
|
PsychicEndpoint* on(const char* uri, int method);
|
||||||
PsychicEndpoint* on(const char* uri, PsychicHandler* handler);
|
PsychicEndpoint* on(const char* uri, PsychicHandler* handler);
|
||||||
PsychicEndpoint* on(const char* uri, http_method method, PsychicHandler *handler);
|
PsychicEndpoint* on(const char* uri, int method, PsychicHandler* handler);
|
||||||
PsychicEndpoint* on(const char* uri, PsychicHttpRequestCallback onRequest);
|
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, PsychicJsonRequestCallback onRequest);
|
||||||
PsychicEndpoint* on(const char* uri, http_method method, PsychicJsonRequestCallback onRequest);
|
PsychicEndpoint* on(const char* uri, int method, PsychicJsonRequestCallback onRequest);
|
||||||
|
|
||||||
|
bool removeEndpoint(const char* uri, int method);
|
||||||
|
bool removeEndpoint(PsychicEndpoint* endpoint);
|
||||||
|
|
||||||
|
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 notFoundHandler(httpd_req_t* req, httpd_err_code_t err);
|
||||||
static esp_err_t defaultNotFoundHandler(PsychicRequest *request);
|
static esp_err_t defaultNotFoundHandler(PsychicRequest* request, PsychicResponse* response);
|
||||||
void onNotFound(PsychicHttpRequestCallback fn);
|
|
||||||
|
|
||||||
void onOpen(PsychicClientCallback handler);
|
|
||||||
void onClose(PsychicClientCallback handler);
|
|
||||||
static esp_err_t openCallback(httpd_handle_t hd, int sockfd);
|
static esp_err_t openCallback(httpd_handle_t hd, int sockfd);
|
||||||
static void closeCallback(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);
|
PsychicStaticFileHandler* serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL);
|
||||||
};
|
};
|
||||||
|
|
||||||
bool ON_STA_FILTER(PsychicRequest* request);
|
bool ON_STA_FILTER(PsychicRequest* request);
|
||||||
bool ON_AP_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
|
#endif // PsychicHttpServer_h
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
|
#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_SSL_CONFIG_DEFAULT();
|
||||||
@@ -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.
|
// 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.
|
// not to mention there is no heap left over for the program itself.
|
||||||
ssl_config.httpd.max_open_sockets = 2;
|
ssl_config.httpd.max_open_sockets = 2;
|
||||||
|
|
||||||
|
setPort(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicHttpsServer::~PsychicHttpsServer() {}
|
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;
|
this->ssl_config.port_secure = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t PsychicHttpsServer::getPort()
|
||||||
|
{
|
||||||
|
return this->ssl_config.port_secure;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2)
|
||||||
this->ssl_config.servercert = (uint8_t *)cert;
|
this->ssl_config.servercert = cert;
|
||||||
this->ssl_config.servercert_len = strlen(cert)+1;
|
this->ssl_config.servercert_len = cert_size;
|
||||||
#else
|
#else
|
||||||
this->ssl_config.cacert_pem = (uint8_t *)cert;
|
this->ssl_config.cacert_pem = cert;
|
||||||
this->ssl_config.cacert_len = strlen(cert)+1;
|
this->ssl_config.cacert_len = cert_size;
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
this->ssl_config.prvtkey_pem = (uint8_t *)private_key;
|
if (private_key) {
|
||||||
this->ssl_config.prvtkey_len = strlen(private_key)+1;
|
this->ssl_config.prvtkey_pem = private_key;
|
||||||
|
this->ssl_config.prvtkey_len = private_key_size;
|
||||||
return this->_start();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t PsychicHttpsServer::_startServer()
|
esp_err_t PsychicHttpsServer::_startServer()
|
||||||
{
|
{
|
||||||
if (this->_use_ssl)
|
|
||||||
return httpd_ssl_start(&this->server, &this->ssl_config);
|
return httpd_ssl_start(&this->server, &this->ssl_config);
|
||||||
else
|
|
||||||
return httpd_start(&this->server, &this->config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PsychicHttpsServer::stop()
|
esp_err_t PsychicHttpsServer::_stopServer()
|
||||||
{
|
{
|
||||||
if (this->_use_ssl)
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2)
|
||||||
|
return httpd_ssl_stop(this->server);
|
||||||
|
#else
|
||||||
httpd_ssl_stop(this->server);
|
httpd_ssl_stop(this->server);
|
||||||
else
|
return ESP_OK;
|
||||||
httpd_stop(this->server);
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE
|
#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE
|
||||||
@@ -12,27 +12,33 @@
|
|||||||
#error PsychicHttpsServer cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration
|
#error PsychicHttpsServer cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef PSY_ENABLE_SSL
|
||||||
#define PSY_ENABLE_SSL // you can use this define in your code to enable/disable these features
|
#define PSY_ENABLE_SSL // you can use this define in your code to enable/disable these features
|
||||||
|
#endif
|
||||||
|
|
||||||
class PsychicHttpsServer : public PsychicHttpServer
|
class PsychicHttpsServer : public PsychicHttpServer
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
bool _use_ssl = false;
|
virtual esp_err_t _startServer() override final;
|
||||||
|
virtual esp_err_t _stopServer() override final;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PsychicHttpsServer();
|
PsychicHttpsServer(uint16_t port = 443);
|
||||||
~PsychicHttpsServer();
|
~PsychicHttpsServer();
|
||||||
|
|
||||||
httpd_ssl_config_t ssl_config;
|
httpd_ssl_config_t ssl_config;
|
||||||
|
|
||||||
using PsychicHttpServer::listen; //keep the regular version
|
// using PsychicHttpServer::listen; // keep the regular version
|
||||||
esp_err_t listen(uint16_t port, const char *cert, const char *private_key);
|
virtual void setPort(uint16_t port) override final;
|
||||||
|
virtual uint16_t getPort() override final;
|
||||||
virtual esp_err_t _startServer() override final;
|
// Pointer to certificate data in PEM format
|
||||||
virtual void stop() override final;
|
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
|
#else
|
||||||
#warning ESP-IDF https server support not enabled.
|
#warning ESP-IDF https server support not enabled.
|
||||||
#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE
|
#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE
|
||||||
|
|
||||||
#endif // PsychicHttpsServer_h
|
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
#include "PsychicJson.h"
|
#include "PsychicJson.h"
|
||||||
|
|
||||||
#ifdef ARDUINOJSON_6_COMPATIBILITY
|
#ifdef ARDUINOJSON_6_COMPATIBILITY
|
||||||
PsychicJsonResponse::PsychicJsonResponse(PsychicRequest *request, bool isArray, size_t maxJsonBufferSize) :
|
PsychicJsonResponse::PsychicJsonResponse(PsychicResponse* response, bool isArray, size_t maxJsonBufferSize) : __response(response),
|
||||||
PsychicResponse(request),
|
|
||||||
_jsonBuffer(maxJsonBufferSize)
|
_jsonBuffer(maxJsonBufferSize)
|
||||||
{
|
{
|
||||||
setContentType(JSON_MIMETYPE);
|
response->setContentType(JSON_MIMETYPE);
|
||||||
if (isArray)
|
if (isArray)
|
||||||
_root = _jsonBuffer.createNestedArray();
|
_root = _jsonBuffer.createNestedArray();
|
||||||
else
|
else
|
||||||
_root = _jsonBuffer.createNestedObject();
|
_root = _jsonBuffer.createNestedObject();
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
PsychicJsonResponse::PsychicJsonResponse(PsychicRequest *request, bool isArray) : PsychicResponse(request)
|
PsychicJsonResponse::PsychicJsonResponse(PsychicResponse* response, bool isArray) : PsychicResponseDelegate(response)
|
||||||
{
|
{
|
||||||
setContentType(JSON_MIMETYPE);
|
setContentType(JSON_MIMETYPE);
|
||||||
if (isArray)
|
if (isArray)
|
||||||
@@ -22,7 +21,10 @@
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
JsonVariant &PsychicJsonResponse::getRoot() { return _root; }
|
JsonVariant& PsychicJsonResponse::getRoot()
|
||||||
|
{
|
||||||
|
return _root;
|
||||||
|
}
|
||||||
|
|
||||||
size_t PsychicJsonResponse::getLength()
|
size_t PsychicJsonResponse::getLength()
|
||||||
{
|
{
|
||||||
@@ -44,27 +46,23 @@ esp_err_t PsychicJsonResponse::send()
|
|||||||
|
|
||||||
buffer = (char*)malloc(buffer_size);
|
buffer = (char*)malloc(buffer_size);
|
||||||
if (buffer == NULL) {
|
if (buffer == NULL) {
|
||||||
httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
|
return error(HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory.");
|
||||||
return ESP_FAIL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// send it in one shot or no?
|
// send it in one shot or no?
|
||||||
if (length < JSON_BUFFER_SIZE)
|
if (length < JSON_BUFFER_SIZE) {
|
||||||
{
|
|
||||||
serializeJson(_root, buffer, buffer_size);
|
serializeJson(_root, buffer, buffer_size);
|
||||||
|
|
||||||
this->setContent((uint8_t *)buffer, length);
|
setContent((uint8_t*)buffer, length);
|
||||||
this->setContentType(JSON_MIMETYPE);
|
setContentType(JSON_MIMETYPE);
|
||||||
|
|
||||||
err = PsychicResponse::send();
|
err = send();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// helper class that acts as a stream to print chunked responses
|
// helper class that acts as a stream to print chunked responses
|
||||||
ChunkPrinter dest(this, (uint8_t *)buffer, buffer_size);
|
ChunkPrinter dest(_response, (uint8_t*)buffer, buffer_size);
|
||||||
|
|
||||||
// keep our headers
|
// keep our headers
|
||||||
this->sendHeaders();
|
sendHeaders();
|
||||||
|
|
||||||
serializeJson(_root, dest);
|
serializeJson(_root, dest);
|
||||||
|
|
||||||
@@ -72,7 +70,7 @@ esp_err_t PsychicJsonResponse::send()
|
|||||||
dest.flush();
|
dest.flush();
|
||||||
|
|
||||||
// done with our chunked response too
|
// done with our chunked response too
|
||||||
err = this->finishChunking();
|
err = finishChunking();
|
||||||
}
|
}
|
||||||
|
|
||||||
// let the buffer go
|
// let the buffer go
|
||||||
@@ -82,52 +80,49 @@ esp_err_t PsychicJsonResponse::send()
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ARDUINOJSON_6_COMPATIBILITY
|
#ifdef ARDUINOJSON_6_COMPATIBILITY
|
||||||
PsychicJsonHandler::PsychicJsonHandler(size_t maxJsonBufferSize) :
|
PsychicJsonHandler::PsychicJsonHandler(size_t maxJsonBufferSize) : _onRequest(NULL),
|
||||||
_onRequest(NULL),
|
_maxJsonBufferSize(maxJsonBufferSize) {};
|
||||||
_maxJsonBufferSize(maxJsonBufferSize)
|
|
||||||
{};
|
|
||||||
|
|
||||||
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize) :
|
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize) : _onRequest(onRequest),
|
||||||
_onRequest(onRequest),
|
|
||||||
_maxJsonBufferSize(maxJsonBufferSize)
|
_maxJsonBufferSize(maxJsonBufferSize)
|
||||||
{}
|
{
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
PsychicJsonHandler::PsychicJsonHandler() :
|
PsychicJsonHandler::PsychicJsonHandler() : _onRequest(NULL) {};
|
||||||
_onRequest(NULL)
|
|
||||||
{};
|
|
||||||
|
|
||||||
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest) :
|
PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest) : _onRequest(onRequest)
|
||||||
_onRequest(onRequest)
|
{
|
||||||
{}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void PsychicJsonHandler::onRequest(PsychicJsonRequestCallback fn) { _onRequest = fn; }
|
void PsychicJsonHandler::onRequest(PsychicJsonRequestCallback fn)
|
||||||
|
{
|
||||||
|
_onRequest = fn;
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t PsychicJsonHandler::handleRequest(PsychicRequest *request)
|
esp_err_t PsychicJsonHandler::handleRequest(PsychicRequest* request, PsychicResponse* response)
|
||||||
{
|
{
|
||||||
// process basic stuff
|
// process basic stuff
|
||||||
PsychicWebHandler::handleRequest(request);
|
PsychicWebHandler::handleRequest(request, response);
|
||||||
|
|
||||||
if (_onRequest)
|
if (_onRequest) {
|
||||||
{
|
|
||||||
#ifdef ARDUINOJSON_6_COMPATIBILITY
|
#ifdef ARDUINOJSON_6_COMPATIBILITY
|
||||||
DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize);
|
DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize);
|
||||||
DeserializationError error = deserializeJson(jsonBuffer, request->body());
|
DeserializationError error = deserializeJson(jsonBuffer, request->body());
|
||||||
if (error)
|
if (error)
|
||||||
return request->reply(400);
|
return response->send(400);
|
||||||
|
|
||||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||||
#else
|
#else
|
||||||
JsonDocument jsonBuffer;
|
JsonDocument jsonBuffer;
|
||||||
DeserializationError error = deserializeJson(jsonBuffer, request->body());
|
DeserializationError error = deserializeJson(jsonBuffer, request->body());
|
||||||
if (error)
|
if (error)
|
||||||
return request->reply(400);
|
return response->send(400);
|
||||||
|
|
||||||
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
JsonVariant json = jsonBuffer.as<JsonVariant>();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return _onRequest(request, json);
|
return _onRequest(request, response, json);
|
||||||
}
|
} else
|
||||||
else
|
return response->send(500);
|
||||||
return request->reply(500);
|
|
||||||
}
|
}
|
||||||
@@ -8,9 +8,9 @@
|
|||||||
#ifndef PSYCHIC_JSON_H_
|
#ifndef PSYCHIC_JSON_H_
|
||||||
#define PSYCHIC_JSON_H_
|
#define PSYCHIC_JSON_H_
|
||||||
|
|
||||||
|
#include "ChunkPrinter.h"
|
||||||
#include "PsychicRequest.h"
|
#include "PsychicRequest.h"
|
||||||
#include "PsychicWebHandler.h"
|
#include "PsychicWebHandler.h"
|
||||||
#include "ChunkPrinter.h"
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
#if ARDUINOJSON_VERSION_MAJOR == 6
|
#if ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#ifndef JSON_BUFFER_SIZE
|
#ifndef JSON_BUFFER_SIZE
|
||||||
#define JSON_BUFFER_SIZE 4 * 1024
|
#define JSON_BUFFER_SIZE 4 * 1024
|
||||||
#endif
|
#endif
|
||||||
@@ -31,7 +30,7 @@ constexpr const char *JSON_MIMETYPE = "application/json";
|
|||||||
* Json Response
|
* Json Response
|
||||||
* */
|
* */
|
||||||
|
|
||||||
class PsychicJsonResponse : public PsychicResponse
|
class PsychicJsonResponse : public PsychicResponseDelegate
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
@@ -47,19 +46,21 @@ class PsychicJsonResponse : public PsychicResponse
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
#ifdef ARDUINOJSON_5_COMPATIBILITY
|
||||||
PsychicJsonResponse(PsychicRequest *request, bool isArray = false);
|
PsychicJsonResponse(PsychicResponse* response, bool isArray = false);
|
||||||
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
#elif ARDUINOJSON_VERSION_MAJOR == 6
|
||||||
PsychicJsonResponse(PsychicRequest *request, bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
PsychicJsonResponse(PsychicResponse* response, bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE);
|
||||||
#else
|
#else
|
||||||
PsychicJsonResponse(PsychicRequest *request, bool isArray = false);
|
PsychicJsonResponse(PsychicResponse* response, bool isArray = false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
~PsychicJsonResponse() {}
|
~PsychicJsonResponse()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
JsonVariant& getRoot();
|
JsonVariant& getRoot();
|
||||||
size_t getLength();
|
size_t getLength();
|
||||||
|
|
||||||
virtual esp_err_t send() override;
|
esp_err_t send();
|
||||||
};
|
};
|
||||||
|
|
||||||
class PsychicJsonHandler : public PsychicWebHandler
|
class PsychicJsonHandler : public PsychicWebHandler
|
||||||
@@ -83,7 +84,7 @@ class PsychicJsonHandler : public PsychicWebHandler
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
void onRequest(PsychicJsonRequestCallback fn);
|
void onRequest(PsychicJsonRequestCallback fn);
|
||||||
virtual esp_err_t handleRequest(PsychicRequest *request) override;
|
virtual esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#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,15 +1,16 @@
|
|||||||
#include "PsychicRequest.h"
|
#include "PsychicRequest.h"
|
||||||
#include "http_status.h"
|
#include "MultipartProcessor.h"
|
||||||
#include "PsychicHttpServer.h"
|
#include "PsychicHttpServer.h"
|
||||||
|
#include "http_status.h"
|
||||||
|
|
||||||
|
PsychicRequest::PsychicRequest(PsychicHttpServer* server, httpd_req_t* req) : _server(server),
|
||||||
PsychicRequest::PsychicRequest(PsychicHttpServer *server, httpd_req_t *req) :
|
|
||||||
_server(server),
|
|
||||||
_req(req),
|
_req(req),
|
||||||
|
_endpoint(nullptr),
|
||||||
_method(HTTP_GET),
|
_method(HTTP_GET),
|
||||||
|
_uri(""),
|
||||||
_query(""),
|
_query(""),
|
||||||
_body(""),
|
_body(""),
|
||||||
_tempObject(NULL)
|
_tempObject(nullptr)
|
||||||
{
|
{
|
||||||
// load up our client.
|
// load up our client.
|
||||||
this->_client = server->getClient(req);
|
this->_client = server->getClient(req);
|
||||||
@@ -17,8 +18,7 @@ PsychicRequest::PsychicRequest(PsychicHttpServer *server, httpd_req_t *req) :
|
|||||||
// handle our session data
|
// handle our session data
|
||||||
if (req->sess_ctx != NULL)
|
if (req->sess_ctx != NULL)
|
||||||
this->_session = (SessionData*)req->sess_ctx;
|
this->_session = (SessionData*)req->sess_ctx;
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
this->_session = new SessionData();
|
this->_session = new SessionData();
|
||||||
req->sess_ctx = this->_session;
|
req->sess_ctx = this->_session;
|
||||||
}
|
}
|
||||||
@@ -26,8 +26,10 @@ PsychicRequest::PsychicRequest(PsychicHttpServer *server, httpd_req_t *req) :
|
|||||||
// callback for freeing the session later
|
// callback for freeing the session later
|
||||||
req->free_ctx = this->freeSession;
|
req->free_ctx = this->freeSession;
|
||||||
|
|
||||||
//load up some data
|
// load and parse our uri.
|
||||||
this->_uri = String(this->_req->uri);
|
this->_setUri(this->_req->uri);
|
||||||
|
|
||||||
|
_response = new PsychicResponse(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicRequest::~PsychicRequest()
|
PsychicRequest::~PsychicRequest()
|
||||||
@@ -40,34 +42,63 @@ PsychicRequest::~PsychicRequest()
|
|||||||
for (auto* param : _params)
|
for (auto* param : _params)
|
||||||
delete (param);
|
delete (param);
|
||||||
_params.clear();
|
_params.clear();
|
||||||
|
|
||||||
|
delete _response;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PsychicRequest::freeSession(void* ctx)
|
void PsychicRequest::freeSession(void* ctx)
|
||||||
{
|
{
|
||||||
if (ctx != NULL)
|
if (ctx != NULL) {
|
||||||
{
|
|
||||||
SessionData* session = (SessionData*)ctx;
|
SessionData* session = (SessionData*)ctx;
|
||||||
delete session;
|
delete session;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicHttpServer * PsychicRequest::server() {
|
PsychicHttpServer* PsychicRequest::server()
|
||||||
|
{
|
||||||
return _server;
|
return _server;
|
||||||
}
|
}
|
||||||
|
|
||||||
httpd_req_t * PsychicRequest::request() {
|
httpd_req_t* PsychicRequest::request()
|
||||||
|
{
|
||||||
return _req;
|
return _req;
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicClient * PsychicRequest::client() {
|
PsychicClient* PsychicRequest::client()
|
||||||
|
{
|
||||||
return _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()
|
const String PsychicRequest::getFilename()
|
||||||
{
|
{
|
||||||
// parse the content-disposition header
|
// parse the content-disposition header
|
||||||
if (this->hasHeader("Content-Disposition"))
|
if (this->hasHeader("Content-Disposition")) {
|
||||||
{
|
|
||||||
ContentDisposition cd = this->getContentDisposition();
|
ContentDisposition cd = this->getContentDisposition();
|
||||||
if (cd.filename != "")
|
if (cd.filename != "")
|
||||||
return cd.filename;
|
return cd.filename;
|
||||||
@@ -107,15 +138,13 @@ const ContentDisposition PsychicRequest::getContentDisposition()
|
|||||||
cd.disposition = NONE;
|
cd.disposition = NONE;
|
||||||
|
|
||||||
start = header.indexOf("filename=");
|
start = header.indexOf("filename=");
|
||||||
if (start)
|
if (start) {
|
||||||
{
|
|
||||||
end = header.indexOf('"', start + 10);
|
end = header.indexOf('"', start + 10);
|
||||||
cd.filename = header.substring(start + 10, end - 1);
|
cd.filename = header.substring(start + 10, end - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
start = header.indexOf("name=");
|
start = header.indexOf("name=");
|
||||||
if (start)
|
if (start) {
|
||||||
{
|
|
||||||
end = header.indexOf('"', start + 6);
|
end = header.indexOf('"', start + 6);
|
||||||
cd.name = header.substring(start + 6, end - 1);
|
cd.name = header.substring(start + 6, end - 1);
|
||||||
}
|
}
|
||||||
@@ -125,7 +154,14 @@ const ContentDisposition PsychicRequest::getContentDisposition()
|
|||||||
|
|
||||||
esp_err_t PsychicRequest::loadBody()
|
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();
|
this->_body = String();
|
||||||
|
|
||||||
@@ -134,7 +170,7 @@ esp_err_t PsychicRequest::loadBody()
|
|||||||
char* buf = (char*)malloc(remaining + 1);
|
char* buf = (char*)malloc(remaining + 1);
|
||||||
if (buf == NULL) {
|
if (buf == NULL) {
|
||||||
ESP_LOGE(PH_TAG, "Failed to allocate memory for body");
|
ESP_LOGE(PH_TAG, "Failed to allocate memory for body");
|
||||||
return ESP_FAIL;
|
return _bodyParsed = ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (remaining > 0) {
|
while (remaining > 0) {
|
||||||
@@ -142,10 +178,9 @@ esp_err_t PsychicRequest::loadBody()
|
|||||||
|
|
||||||
if (received == HTTPD_SOCK_ERR_TIMEOUT) {
|
if (received == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||||
continue;
|
continue;
|
||||||
}
|
} else if (received == HTTPD_SOCK_ERR_FAIL) {
|
||||||
else if (received == HTTPD_SOCK_ERR_FAIL) {
|
|
||||||
ESP_LOGE(PH_TAG, "Failed to receive data.");
|
ESP_LOGE(PH_TAG, "Failed to receive data.");
|
||||||
err = ESP_FAIL;
|
_bodyParsed = ESP_FAIL;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,18 +191,24 @@ esp_err_t PsychicRequest::loadBody()
|
|||||||
buf[actuallyReceived] = '\0';
|
buf[actuallyReceived] = '\0';
|
||||||
this->_body = String(buf);
|
this->_body = String(buf);
|
||||||
free(buf);
|
free(buf);
|
||||||
return err;
|
|
||||||
|
_bodyParsed = ESP_OK;
|
||||||
|
|
||||||
|
return _bodyParsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
http_method PsychicRequest::method() {
|
http_method PsychicRequest::method()
|
||||||
|
{
|
||||||
return (http_method)this->_req->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));
|
return String(http_method_str((http_method)this->_req->method));
|
||||||
}
|
}
|
||||||
|
|
||||||
const String PsychicRequest::path() {
|
const String PsychicRequest::path()
|
||||||
|
{
|
||||||
int index = _uri.indexOf("?");
|
int index = _uri.indexOf("?");
|
||||||
if (index == -1)
|
if (index == -1)
|
||||||
return _uri;
|
return _uri;
|
||||||
@@ -175,11 +216,13 @@ const String PsychicRequest::path() {
|
|||||||
return _uri.substring(0, index);
|
return _uri.substring(0, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
const String& PsychicRequest::uri() {
|
const String& PsychicRequest::uri()
|
||||||
|
{
|
||||||
return this->_uri;
|
return this->_uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
const String& PsychicRequest::query() {
|
const String& PsychicRequest::query()
|
||||||
|
{
|
||||||
return this->_query;
|
return this->_query;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,13 +236,11 @@ const String PsychicRequest::header(const char *name)
|
|||||||
size_t header_len = httpd_req_get_hdr_value_len(this->_req, 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 we've got one, allocated it and load it
|
||||||
if (header_len)
|
if (header_len) {
|
||||||
{
|
|
||||||
char header[header_len + 1];
|
char header[header_len + 1];
|
||||||
httpd_req_get_hdr_value_str(this->_req, name, header, sizeof(header));
|
httpd_req_get_hdr_value_str(this->_req, name, header, sizeof(header));
|
||||||
return String(header);
|
return String(header);
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,15 +249,18 @@ bool PsychicRequest::hasHeader(const char *name)
|
|||||||
return httpd_req_get_hdr_value_len(this->_req, name) > 0;
|
return httpd_req_get_hdr_value_len(this->_req, name) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const String PsychicRequest::host() {
|
const String PsychicRequest::host()
|
||||||
|
{
|
||||||
return this->header("Host");
|
return this->header("Host");
|
||||||
}
|
}
|
||||||
|
|
||||||
const String PsychicRequest::contentType() {
|
const String PsychicRequest::contentType()
|
||||||
|
{
|
||||||
return header("Content-Type");
|
return header("Content-Type");
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t PsychicRequest::contentLength() {
|
size_t PsychicRequest::contentLength()
|
||||||
|
{
|
||||||
return this->_req->content_len;
|
return this->_req->content_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,71 +276,111 @@ bool PsychicRequest::isMultipart()
|
|||||||
return (this->contentType().indexOf("multipart/form-data") >= 0);
|
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);
|
char buffer;
|
||||||
response.setCode(301);
|
|
||||||
response.addHeader("Location", url);
|
|
||||||
|
|
||||||
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];
|
return httpd_req_get_cookie_val(this->_req, key, buffer, size);
|
||||||
size_t cookieSize = MAX_COOKIE_SIZE;
|
|
||||||
esp_err_t err = httpd_req_get_cookie_val(this->_req, key, cookie, &cookieSize);
|
|
||||||
|
|
||||||
//did we get anything?
|
|
||||||
if (err == ESP_OK)
|
|
||||||
return true;
|
|
||||||
else if (err == ESP_ERR_HTTPD_RESULT_TRUNC)
|
|
||||||
ESP_LOGE(PH_TAG, "cookie too large (%d bytes).\n", cookieSize);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const String PsychicRequest::getCookie(const char *key)
|
String PsychicRequest::getCookie(const char* key)
|
||||||
{
|
{
|
||||||
char cookie[MAX_COOKIE_SIZE];
|
String cookie = "";
|
||||||
size_t cookieSize = MAX_COOKIE_SIZE;
|
|
||||||
esp_err_t err = httpd_req_get_cookie_val(this->_req, key, cookie, &cookieSize);
|
|
||||||
|
|
||||||
//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)
|
if (err == ESP_OK)
|
||||||
return String(cookie);
|
cookie.concat(buf);
|
||||||
else
|
|
||||||
return "";
|
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()
|
void PsychicRequest::loadParams()
|
||||||
{
|
{
|
||||||
//did we get a query string?
|
if (_paramsParsed != ESP_ERR_NOT_FINISHED)
|
||||||
size_t query_len = httpd_req_get_url_query_len(_req);
|
return;
|
||||||
if (query_len)
|
|
||||||
{
|
|
||||||
char query[query_len+1];
|
|
||||||
httpd_req_get_url_query_str(_req, query, sizeof(query));
|
|
||||||
_query.concat(query);
|
|
||||||
|
|
||||||
|
// 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.
|
// parse them.
|
||||||
|
_query = _uri.substring(index + 1);
|
||||||
_addParams(_query, false);
|
_addParams(_query, false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//did we get form data as body?
|
void PsychicRequest::_addParams(const String& params, bool post)
|
||||||
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;
|
size_t start = 0;
|
||||||
while (start < params.length()) {
|
while (start < params.length()) {
|
||||||
int end = params.indexOf('&', start);
|
int end = params.indexOf('&', start);
|
||||||
if (end < 0) end = params.length();
|
if (end < 0)
|
||||||
|
end = params.length();
|
||||||
int equal = params.indexOf('=', start);
|
int equal = params.indexOf('=', start);
|
||||||
if (equal < 0 || equal > end) equal = end;
|
if (equal < 0 || equal > end)
|
||||||
|
equal = end;
|
||||||
String name = params.substring(start, equal);
|
String name = params.substring(start, equal);
|
||||||
String value = equal + 1 < end ? params.substring(equal + 1, end) : String();
|
String value = equal + 1 < end ? params.substring(equal + 1, end) : String();
|
||||||
addParam(name, value, true, post);
|
addParam(name, value, true, post);
|
||||||
@@ -312,22 +396,23 @@ PsychicWebParameter * PsychicRequest::addParam(const String &name, const String
|
|||||||
return addParam(new PsychicWebParameter(name, value, post));
|
return addParam(new PsychicWebParameter(name, value, post));
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicWebParameter * PsychicRequest::addParam(PsychicWebParameter *param) {
|
PsychicWebParameter* PsychicRequest::addParam(PsychicWebParameter* param)
|
||||||
|
{
|
||||||
// ESP_LOGD(PH_TAG, "Adding param: '%s' = '%s'", param->name().c_str(), param->value().c_str());
|
// ESP_LOGD(PH_TAG, "Adding param: '%s' = '%s'", param->name().c_str(), param->value().c_str());
|
||||||
_params.push_back(param);
|
_params.push_back(param);
|
||||||
return param;
|
return param;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PsychicRequest::params()
|
|
||||||
{
|
|
||||||
return _params.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PsychicRequest::hasParam(const char* key)
|
bool PsychicRequest::hasParam(const char* key)
|
||||||
{
|
{
|
||||||
return getParam(key) != NULL;
|
return getParam(key) != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PsychicRequest::hasParam(const char* key, bool isPost, bool isFile)
|
||||||
|
{
|
||||||
|
return getParam(key, isPost, isFile) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
PsychicWebParameter* PsychicRequest::getParam(const char* key)
|
PsychicWebParameter* PsychicRequest::getParam(const char* key)
|
||||||
{
|
{
|
||||||
for (auto* param : _params)
|
for (auto* param : _params)
|
||||||
@@ -337,15 +422,11 @@ PsychicWebParameter * PsychicRequest::getParam(const char *key)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicWebParameter * PsychicRequest::getParam(int index)
|
PsychicWebParameter* PsychicRequest::getParam(const char* key, bool isPost, bool isFile)
|
||||||
{
|
{
|
||||||
if (_params.size() > index){
|
for (auto* param : _params)
|
||||||
std::list<PsychicWebParameter*>::iterator it = _params.begin();
|
if (param->name().equals(key) && isPost == param->isPost() && isFile == param->isFile())
|
||||||
for(int i=0; i<index; i++){
|
return param;
|
||||||
++it;
|
|
||||||
}
|
|
||||||
return *it;
|
|
||||||
}
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +449,8 @@ void PsychicRequest::setSessionKey(const String& key, const String& value)
|
|||||||
this->_session->insert(std::pair<String, String>(key, 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();
|
MD5Builder md5 = MD5Builder();
|
||||||
md5.begin();
|
md5.begin();
|
||||||
md5.add(in);
|
md5.add(in);
|
||||||
@@ -378,8 +460,7 @@ static const String md5str(const String &in){
|
|||||||
|
|
||||||
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");
|
String authReq = header("Authorization");
|
||||||
if (authReq.startsWith("Basic")) {
|
if (authReq.startsWith("Basic")) {
|
||||||
authReq = authReq.substring(6);
|
authReq = authReq.substring(6);
|
||||||
@@ -405,9 +486,7 @@ bool PsychicRequest::authenticate(const char * username, const char * password)
|
|||||||
}
|
}
|
||||||
delete[] toencode;
|
delete[] toencode;
|
||||||
delete[] encoded;
|
delete[] encoded;
|
||||||
}
|
} else if (authReq.startsWith(F("Digest"))) {
|
||||||
else if(authReq.startsWith(F("Digest")))
|
|
||||||
{
|
|
||||||
authReq = authReq.substring(7);
|
authReq = authReq.substring(7);
|
||||||
String _username = _extractParam(authReq, F("username=\""), '\"');
|
String _username = _extractParam(authReq, F("username=\""), '\"');
|
||||||
if (!_username.length() || _username != String(username)) {
|
if (!_username.length() || _username != String(username)) {
|
||||||
@@ -417,16 +496,15 @@ bool PsychicRequest::authenticate(const char * username, const char * password)
|
|||||||
// extracting required parameters for RFC 2069 simpler Digest
|
// extracting required parameters for RFC 2069 simpler Digest
|
||||||
String _realm = _extractParam(authReq, F("realm=\""), '\"');
|
String _realm = _extractParam(authReq, F("realm=\""), '\"');
|
||||||
String _nonce = _extractParam(authReq, F("nonce=\""), '\"');
|
String _nonce = _extractParam(authReq, F("nonce=\""), '\"');
|
||||||
String _uri = _extractParam(authReq, F("uri=\""),'\"');
|
String _url = _extractParam(authReq, F("uri=\""), '\"');
|
||||||
String _resp = _extractParam(authReq, F("response=\""), '\"');
|
String _resp = _extractParam(authReq, F("response=\""), '\"');
|
||||||
String _opaque = _extractParam(authReq, F("opaque=\""), '\"');
|
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 = "";
|
authReq = "";
|
||||||
return false;
|
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 = "";
|
authReq = "";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -442,15 +520,15 @@ bool PsychicRequest::authenticate(const char * username, const char * password)
|
|||||||
|
|
||||||
String _H2 = "";
|
String _H2 = "";
|
||||||
if (_method == HTTP_GET) {
|
if (_method == HTTP_GET) {
|
||||||
_H2 = md5str(String(F("GET:")) + _uri);
|
_H2 = md5str(String(F("GET:")) + _url);
|
||||||
} else if (_method == HTTP_POST) {
|
} else if (_method == HTTP_POST) {
|
||||||
_H2 = md5str(String(F("POST:")) + _uri);
|
_H2 = md5str(String(F("POST:")) + _url);
|
||||||
} else if (_method == HTTP_PUT) {
|
} else if (_method == HTTP_PUT) {
|
||||||
_H2 = md5str(String(F("PUT:")) + _uri);
|
_H2 = md5str(String(F("PUT:")) + _url);
|
||||||
} else if (_method == HTTP_DELETE) {
|
} else if (_method == HTTP_DELETE) {
|
||||||
_H2 = md5str(String(F("DELETE:")) + _uri);
|
_H2 = md5str(String(F("DELETE:")) + _url);
|
||||||
} else {
|
} else {
|
||||||
_H2 = md5str(String(F("GET:")) + _uri);
|
_H2 = md5str(String(F("GET:")) + _url);
|
||||||
}
|
}
|
||||||
// ESP_LOGD(PH_TAG, "Hash of GET:uri=%s", _H2.c_str());
|
// ESP_LOGD(PH_TAG, "Hash of GET:uri=%s", _H2.c_str());
|
||||||
|
|
||||||
@@ -502,13 +580,10 @@ esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, const char*
|
|||||||
String authStr;
|
String authStr;
|
||||||
|
|
||||||
// what kind of auth?
|
// what kind of auth?
|
||||||
if(mode == BASIC_AUTH)
|
if (mode == BASIC_AUTH) {
|
||||||
{
|
|
||||||
authStr = "Basic realm=\"" + this->getSessionKey("realm") + "\"";
|
authStr = "Basic realm=\"" + this->getSessionKey("realm") + "\"";
|
||||||
response.addHeader("WWW-Authenticate", authStr.c_str());
|
response.addHeader("WWW-Authenticate", authStr.c_str());
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// only make new ones if we havent sent them yet
|
// only make new ones if we havent sent them yet
|
||||||
if (this->getSessionKey("nonce").isEmpty())
|
if (this->getSessionKey("nonce").isEmpty())
|
||||||
this->setSessionKey("nonce", _getRandomHexString());
|
this->setSessionKey("nonce", _getRandomHexString());
|
||||||
@@ -521,39 +596,6 @@ esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, const char*
|
|||||||
|
|
||||||
response.setCode(401);
|
response.setCode(401);
|
||||||
response.setContentType("text/html");
|
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();
|
return response.send();
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,24 @@
|
|||||||
#ifndef PsychicRequest_h
|
#ifndef PsychicRequest_h
|
||||||
#define PsychicRequest_h
|
#define PsychicRequest_h
|
||||||
|
|
||||||
#include "PsychicCore.h"
|
|
||||||
#include "PsychicHttpServer.h"
|
|
||||||
#include "PsychicClient.h"
|
#include "PsychicClient.h"
|
||||||
|
#include "PsychicCore.h"
|
||||||
|
#include "PsychicEndpoint.h"
|
||||||
|
#include "PsychicHttpServer.h"
|
||||||
#include "PsychicWebParameter.h"
|
#include "PsychicWebParameter.h"
|
||||||
#include "PsychicResponse.h"
|
|
||||||
|
#ifdef PSY_ENABLE_REGEX
|
||||||
|
#include <regex>
|
||||||
|
#endif
|
||||||
|
|
||||||
typedef std::map<String, String> SessionData;
|
typedef std::map<String, String> SessionData;
|
||||||
|
|
||||||
enum Disposition { NONE, INLINE, ATTACHMENT, FORM_DATA};
|
enum Disposition {
|
||||||
|
NONE,
|
||||||
|
INLINE,
|
||||||
|
ATTACHMENT,
|
||||||
|
FORM_DATA
|
||||||
|
};
|
||||||
|
|
||||||
struct ContentDisposition {
|
struct ContentDisposition {
|
||||||
Disposition disposition;
|
Disposition disposition;
|
||||||
@@ -17,22 +26,30 @@ struct ContentDisposition {
|
|||||||
String name;
|
String name;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PsychicRequest {
|
class PsychicRequest
|
||||||
|
{
|
||||||
friend PsychicHttpServer;
|
friend PsychicHttpServer;
|
||||||
|
friend PsychicResponse;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
PsychicHttpServer* _server;
|
PsychicHttpServer* _server;
|
||||||
httpd_req_t* _req;
|
httpd_req_t* _req;
|
||||||
SessionData* _session;
|
SessionData* _session;
|
||||||
PsychicClient* _client;
|
PsychicClient* _client;
|
||||||
|
PsychicEndpoint* _endpoint;
|
||||||
|
|
||||||
http_method _method;
|
http_method _method;
|
||||||
String _uri;
|
String _uri;
|
||||||
String _query;
|
String _query;
|
||||||
String _body;
|
String _body;
|
||||||
|
esp_err_t _bodyParsed = ESP_ERR_NOT_FINISHED;
|
||||||
|
esp_err_t _paramsParsed = ESP_ERR_NOT_FINISHED;
|
||||||
|
|
||||||
std::list<PsychicWebParameter*> _params;
|
std::list<PsychicWebParameter*> _params;
|
||||||
|
|
||||||
|
PsychicResponse* _response;
|
||||||
|
|
||||||
|
void _setUri(const char* uri);
|
||||||
void _addParams(const String& params, bool post);
|
void _addParams(const String& params, bool post);
|
||||||
void _parseGETParams();
|
void _parseGETParams();
|
||||||
void _parsePOSTParams();
|
void _parsePOSTParams();
|
||||||
@@ -50,6 +67,13 @@ class PsychicRequest {
|
|||||||
httpd_req_t* request();
|
httpd_req_t* request();
|
||||||
virtual PsychicClient* client();
|
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();
|
bool isMultipart();
|
||||||
esp_err_t loadBody();
|
esp_err_t loadBody();
|
||||||
|
|
||||||
@@ -61,8 +85,32 @@ class PsychicRequest {
|
|||||||
const String getSessionKey(const String& key);
|
const String getSessionKey(const String& key);
|
||||||
void setSessionKey(const String& key, const String& value);
|
void setSessionKey(const String& key, const String& value);
|
||||||
|
|
||||||
bool hasCookie(const char * key);
|
bool hasCookie(const char* key, size_t* size = nullptr);
|
||||||
const String getCookie(const char * key);
|
|
||||||
|
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)
|
http_method method(); // returns the HTTP method used as enum value (eg. HTTP_GET)
|
||||||
const String methodStr(); // returns the HTTP method used as a string (eg. "GET")
|
const String methodStr(); // returns the HTTP method used as a string (eg. "GET")
|
||||||
@@ -74,6 +122,7 @@ class PsychicRequest {
|
|||||||
size_t contentLength(); // returns the Content-Length header value
|
size_t contentLength(); // returns the Content-Length header value
|
||||||
const String& body(); // returns the body of the request
|
const String& body(); // returns the body of the request
|
||||||
const ContentDisposition getContentDisposition();
|
const ContentDisposition getContentDisposition();
|
||||||
|
const char* version() { return "HTTP/1.1"; }
|
||||||
|
|
||||||
const String& queryString() { return query(); } // compatability function. same as query()
|
const String& queryString() { return query(); } // compatability function. same as query()
|
||||||
const String& url() { return uri(); } // compatability function. same as uri()
|
const String& url() { return uri(); } // compatability function. same as uri()
|
||||||
@@ -81,20 +130,15 @@ class PsychicRequest {
|
|||||||
void loadParams();
|
void loadParams();
|
||||||
PsychicWebParameter* addParam(PsychicWebParameter* param);
|
PsychicWebParameter* addParam(PsychicWebParameter* param);
|
||||||
PsychicWebParameter* addParam(const String& name, const String& value, bool decode = true, bool post = false);
|
PsychicWebParameter* addParam(const String& name, const String& value, bool decode = true, bool post = false);
|
||||||
int params();
|
|
||||||
bool hasParam(const char* key);
|
bool hasParam(const char* key);
|
||||||
|
bool hasParam(const char* key, bool isPost, bool isFile = false);
|
||||||
PsychicWebParameter* getParam(const char* name);
|
PsychicWebParameter* getParam(const char* name);
|
||||||
PsychicWebParameter * getParam(int index);
|
PsychicWebParameter* getParam(const char* name, bool isPost, bool isFile = false);
|
||||||
|
|
||||||
const String getFilename();
|
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 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
|
#endif // PsychicRequest_h
|
||||||
@@ -2,37 +2,35 @@
|
|||||||
#include "PsychicRequest.h"
|
#include "PsychicRequest.h"
|
||||||
#include <http_status.h>
|
#include <http_status.h>
|
||||||
|
|
||||||
PsychicResponse::PsychicResponse(PsychicRequest *request) :
|
PsychicResponse::PsychicResponse(PsychicRequest* request) : _request(request),
|
||||||
_request(request),
|
|
||||||
_code(200),
|
_code(200),
|
||||||
_status(""),
|
_status(""),
|
||||||
|
_contentType(emptyString),
|
||||||
_contentLength(0),
|
_contentLength(0),
|
||||||
_body("")
|
_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()
|
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();
|
_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
|
// erase any existing ones.
|
||||||
HTTPHeader header;
|
for (auto itr = _headers.begin(); itr != _headers.end();) {
|
||||||
header.field =(char *)malloc(strlen(field)+1);
|
if (itr->field.equalsIgnoreCase(field))
|
||||||
header.value = (char *)malloc(strlen(value)+1);
|
itr = _headers.erase(itr);
|
||||||
|
else
|
||||||
|
itr++;
|
||||||
|
}
|
||||||
|
|
||||||
strlcpy(header.field, field, strlen(field)+1);
|
// now add it.
|
||||||
strlcpy(header.value, value, strlen(value)+1);
|
_headers.push_back({field, value});
|
||||||
|
|
||||||
_headers.push_back(header);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@@ -46,8 +44,7 @@ void PsychicResponse::setCookie(const char *name, const char *value, unsigned lo
|
|||||||
if (now < 1700000000)
|
if (now < 1700000000)
|
||||||
output += "; Max-Age=" + String(secondsFromNow);
|
output += "; Max-Age=" + String(secondsFromNow);
|
||||||
// otherwise, set an expiration date
|
// otherwise, set an expiration date
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
time_t expirationTimestamp = now + secondsFromNow;
|
time_t expirationTimestamp = now + secondsFromNow;
|
||||||
|
|
||||||
// Convert the expiration timestamp to a formatted string for the "expires" attribute
|
// Convert the expiration timestamp to a formatted string for the "expires" attribute
|
||||||
@@ -72,7 +69,7 @@ void PsychicResponse::setCode(int 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)
|
||||||
@@ -103,6 +100,9 @@ esp_err_t PsychicResponse::send()
|
|||||||
sprintf(_status, "%u %s", _code, http_status_reason(_code));
|
sprintf(_status, "%u %s", _code, http_status_reason(_code));
|
||||||
httpd_resp_set_status(_request->request(), _status);
|
httpd_resp_set_status(_request->request(), _status);
|
||||||
|
|
||||||
|
// set the content type
|
||||||
|
httpd_resp_set_type(_request->request(), _contentType.c_str());
|
||||||
|
|
||||||
// our headers too
|
// our headers too
|
||||||
this->sendHeaders();
|
this->sendHeaders();
|
||||||
|
|
||||||
@@ -118,38 +118,21 @@ esp_err_t PsychicResponse::send()
|
|||||||
|
|
||||||
void PsychicResponse::sendHeaders()
|
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
|
// now do our individual headers
|
||||||
for (HTTPHeader header : _headers)
|
for (auto& header : _headers)
|
||||||
httpd_resp_set_hdr(this->_request->request(), header.field, header.value);
|
httpd_resp_set_hdr(this->_request->request(), header.field.c_str(), header.value.c_str());
|
||||||
|
|
||||||
// DO NOT RELEASE HEADERS HERE... released in the PsychicResponse destructor after they have been sent.
|
|
||||||
// httpd_resp_set_hdr just passes on the pointer, but its needed after this call.
|
|
||||||
// clean up our header variables after send
|
|
||||||
// for (HTTPHeader header : _headers)
|
|
||||||
// {
|
|
||||||
// free(header.field);
|
|
||||||
// free(header.value);
|
|
||||||
// }
|
|
||||||
// _headers.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t PsychicResponse::sendChunk(uint8_t* chunk, size_t chunksize)
|
esp_err_t PsychicResponse::sendChunk(uint8_t* chunk, size_t chunksize)
|
||||||
{
|
{
|
||||||
/* Send the buffer contents as HTTP response chunk */
|
/* Send the buffer contents as HTTP response chunk */
|
||||||
esp_err_t err = httpd_resp_send_chunk(this->_request->request(), (char *)chunk, chunksize);
|
ESP_LOGD(PH_TAG, "Sending chunk: %d", chunksize);
|
||||||
if (err != ESP_OK)
|
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));
|
ESP_LOGE(PH_TAG, "File sending failed (%s)", esp_err_to_name(err));
|
||||||
|
|
||||||
/* Abort sending file */
|
/* Abort sending file */
|
||||||
httpd_resp_sendstr_chunk(this->_request->request(), NULL);
|
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;
|
return err;
|
||||||
@@ -160,3 +143,62 @@ esp_err_t PsychicResponse::finishChunking()
|
|||||||
/* Respond with an empty chunk to signal HTTP response completion */
|
/* Respond with an empty chunk to signal HTTP response completion */
|
||||||
return httpd_resp_send_chunk(this->_request->request(), NULL, 0);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class PsychicResponse
|
|||||||
int _code;
|
int _code;
|
||||||
char _status[60];
|
char _status[60];
|
||||||
std::list<HTTPHeader> _headers;
|
std::list<HTTPHeader> _headers;
|
||||||
|
String _contentType;
|
||||||
int64_t _contentLength;
|
int64_t _contentLength;
|
||||||
const char* _body;
|
const char* _body;
|
||||||
|
|
||||||
@@ -21,13 +22,19 @@ class PsychicResponse
|
|||||||
PsychicResponse(PsychicRequest* request);
|
PsychicResponse(PsychicRequest* request);
|
||||||
virtual ~PsychicResponse();
|
virtual ~PsychicResponse();
|
||||||
|
|
||||||
|
const char* version() { return "HTTP/1.1"; }
|
||||||
|
|
||||||
void setCode(int code);
|
void setCode(int code);
|
||||||
|
int getCode() { return _code; }
|
||||||
|
|
||||||
void setContentType(const char* contentType);
|
void setContentType(const char* contentType);
|
||||||
|
String& getContentType() { return _contentType; }
|
||||||
|
|
||||||
void setContentLength(int64_t contentLength) { _contentLength = contentLength; }
|
void setContentLength(int64_t contentLength) { _contentLength = contentLength; }
|
||||||
int64_t getContentLength(int64_t contentLength) { return _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 = "");
|
||||||
|
|
||||||
@@ -41,6 +48,62 @@ class PsychicResponse
|
|||||||
void sendHeaders();
|
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 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
|
#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();
|
||||||
|
}
|
||||||
30
lib/PsychicHttp/src/PsychicRewrite.h
Normal file
30
lib/PsychicHttp/src/PsychicRewrite.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#ifndef PsychicRewrite_h
|
||||||
|
#define PsychicRewrite_h
|
||||||
|
|
||||||
|
#include "PsychicCore.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* REWRITE :: One instance can be handle any Request (done by the Server)
|
||||||
|
* */
|
||||||
|
|
||||||
|
class PsychicRewrite {
|
||||||
|
protected:
|
||||||
|
String _fromPath;
|
||||||
|
String _toUri;
|
||||||
|
String _toPath;
|
||||||
|
String _toParams;
|
||||||
|
PsychicRequestFilterFunction _filter;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PsychicRewrite(const char* from, const char* to);
|
||||||
|
virtual ~PsychicRewrite();
|
||||||
|
|
||||||
|
PsychicRewrite* setFilter(PsychicRequestFilterFunction fn);
|
||||||
|
bool filter(PsychicRequest *request) const;
|
||||||
|
const String& from(void) const;
|
||||||
|
const String& toUrl(void) const;
|
||||||
|
const String& params(void) const;
|
||||||
|
virtual bool match(PsychicRequest *request);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -8,8 +8,10 @@ PsychicStaticFileHandler::PsychicStaticFileHandler(const char* uri, FS& fs, cons
|
|||||||
: _fs(fs), _uri(uri), _path(path), _default_file("index.html"), _cache_control(cache_control), _last_modified("")
|
: _fs(fs), _uri(uri), _path(path), _default_file("index.html"), _cache_control(cache_control), _last_modified("")
|
||||||
{
|
{
|
||||||
// Ensure leading '/'
|
// Ensure leading '/'
|
||||||
if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri;
|
if (_uri.length() == 0 || _uri[0] != '/')
|
||||||
if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path;
|
_uri = "/" + _uri;
|
||||||
|
if (_path.length() == 0 || _path[0] != '/')
|
||||||
|
_path = "/" + _path;
|
||||||
|
|
||||||
// If path ends with '/' we assume a hint that this is a directory to improve performance.
|
// If path ends with '/' we assume a hint that this is a directory to improve performance.
|
||||||
// However - if it does not end with '/' we, can't assume a file, path can still be a directory.
|
// However - if it does not end with '/' we, can't assume a file, path can still be a directory.
|
||||||
@@ -17,35 +19,42 @@ PsychicStaticFileHandler::PsychicStaticFileHandler(const char* uri, FS& fs, cons
|
|||||||
|
|
||||||
// Remove the trailing '/' so we can handle default file
|
// Remove the trailing '/' so we can handle default file
|
||||||
// Notice that root will be "" not "/"
|
// Notice that root will be "" not "/"
|
||||||
if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1);
|
if (_uri[_uri.length() - 1] == '/')
|
||||||
if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1);
|
_uri = _uri.substring(0, _uri.length() - 1);
|
||||||
|
if (_path[_path.length() - 1] == '/')
|
||||||
|
_path = _path.substring(0, _path.length() - 1);
|
||||||
|
|
||||||
// Reset stats
|
// Reset stats
|
||||||
_gzipFirst = false;
|
_gzipFirst = false;
|
||||||
_gzipStats = 0xF8;
|
_gzipStats = 0xF8;
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicStaticFileHandler& PsychicStaticFileHandler::setIsDir(bool isDir){
|
PsychicStaticFileHandler* PsychicStaticFileHandler::setIsDir(bool isDir)
|
||||||
|
{
|
||||||
_isDir = isDir;
|
_isDir = isDir;
|
||||||
return *this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicStaticFileHandler& PsychicStaticFileHandler::setDefaultFile(const char* filename){
|
PsychicStaticFileHandler* PsychicStaticFileHandler::setDefaultFile(const char* filename)
|
||||||
_default_file = String(filename);
|
{
|
||||||
return *this;
|
_default_file = filename;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicStaticFileHandler& PsychicStaticFileHandler::setCacheControl(const char* cache_control){
|
PsychicStaticFileHandler* PsychicStaticFileHandler::setCacheControl(const char* cache_control)
|
||||||
_cache_control = String(cache_control);
|
{
|
||||||
return *this;
|
_cache_control = cache_control;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicStaticFileHandler& PsychicStaticFileHandler::setLastModified(const char* last_modified){
|
PsychicStaticFileHandler* PsychicStaticFileHandler::setLastModified(const char* last_modified)
|
||||||
|
{
|
||||||
_last_modified = String(last_modified);
|
_last_modified = String(last_modified);
|
||||||
return *this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
PsychicStaticFileHandler& PsychicStaticFileHandler::setLastModified(struct tm* last_modified){
|
PsychicStaticFileHandler* PsychicStaticFileHandler::setLastModified(struct tm* last_modified)
|
||||||
|
{
|
||||||
char result[30];
|
char result[30];
|
||||||
strftime(result, 30, "%a, %d %b %Y %H:%M:%S %Z", last_modified);
|
strftime(result, 30, "%a, %d %b %Y %H:%M:%S %Z", last_modified);
|
||||||
return setLastModified((const char*)result);
|
return setLastModified((const char*)result);
|
||||||
@@ -53,12 +62,21 @@ PsychicStaticFileHandler& PsychicStaticFileHandler::setLastModified(struct tm* l
|
|||||||
|
|
||||||
bool PsychicStaticFileHandler::canHandle(PsychicRequest* request)
|
bool PsychicStaticFileHandler::canHandle(PsychicRequest* request)
|
||||||
{
|
{
|
||||||
if(request->method() != HTTP_GET || !request->uri().startsWith(_uri) )
|
if (request->method() != HTTP_GET) {
|
||||||
|
ESP_LOGD(PH_TAG, "Request %s refused by PsychicStaticFileHandler: %s", request->uri().c_str(), request->methodStr().c_str());
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (_getFile(request))
|
if (!request->uri().startsWith(_uri)) {
|
||||||
|
ESP_LOGD(PH_TAG, "Request %s refused by PsychicStaticFileHandler: does not start with %s", request->uri().c_str(), _uri.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_getFile(request)) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(PH_TAG, "Request %s refused by PsychicStaticFileHandler: file not found", request->uri().c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,17 +133,21 @@ bool PsychicStaticFileHandler::_fileExists(const String& path)
|
|||||||
|
|
||||||
bool found = fileFound || gzipFound;
|
bool found = fileFound || gzipFound;
|
||||||
|
|
||||||
if (found)
|
if (found) {
|
||||||
{
|
|
||||||
_filename = path;
|
_filename = path;
|
||||||
|
|
||||||
// Calculate gzip statistic
|
// Calculate gzip statistic
|
||||||
_gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0);
|
_gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0);
|
||||||
if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip
|
if (_gzipStats == 0x00)
|
||||||
else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip
|
_gzipFirst = false; // All files are not gzip
|
||||||
else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first
|
else if (_gzipStats == 0xFF)
|
||||||
|
_gzipFirst = true; // All files are gzip
|
||||||
|
else
|
||||||
|
_gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(PH_TAG, "PsychicStaticFileHandler _fileExists(%s): %d", path.c_str(), found);
|
||||||
|
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,36 +155,32 @@ uint8_t PsychicStaticFileHandler::_countBits(const uint8_t value) const
|
|||||||
{
|
{
|
||||||
uint8_t w = value;
|
uint8_t w = value;
|
||||||
uint8_t n;
|
uint8_t n;
|
||||||
for (n=0; w!=0; n++) w&=w-1;
|
for (n = 0; w != 0; n++)
|
||||||
|
w &= w - 1;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest *request)
|
esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest* request, PsychicResponse* res)
|
||||||
{
|
|
||||||
if (_file == true)
|
|
||||||
{
|
{
|
||||||
|
if (_file == true) {
|
||||||
// is it not modified?
|
// is it not modified?
|
||||||
String etag = String(_file.size());
|
String etag = String(_file.size());
|
||||||
if (_last_modified.length() && _last_modified == request->header("If-Modified-Since"))
|
if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) {
|
||||||
{
|
|
||||||
_file.close();
|
_file.close();
|
||||||
request->reply(304); // Not modified
|
res->send(304); // Not modified
|
||||||
}
|
}
|
||||||
// does our Etag match?
|
// does our Etag match?
|
||||||
else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag))
|
else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) {
|
||||||
{
|
|
||||||
_file.close();
|
_file.close();
|
||||||
|
|
||||||
PsychicResponse response(request);
|
res->addHeader("Cache-Control", _cache_control.c_str());
|
||||||
response.addHeader("Cache-Control", _cache_control.c_str());
|
res->addHeader("ETag", etag.c_str());
|
||||||
response.addHeader("ETag", etag.c_str());
|
res->setCode(304);
|
||||||
response.setCode(304);
|
res->send();
|
||||||
response.send();
|
|
||||||
}
|
}
|
||||||
// nope, send them the full file.
|
// nope, send them the full file.
|
||||||
else
|
else {
|
||||||
{
|
PsychicFileResponse response(res, _fs, _filename);
|
||||||
PsychicFileResponse response(request, _fs, _filename);
|
|
||||||
|
|
||||||
if (_last_modified.length())
|
if (_last_modified.length())
|
||||||
response.addHeader("Last-Modified", _last_modified.c_str());
|
response.addHeader("Last-Modified", _last_modified.c_str());
|
||||||
@@ -174,7 +192,7 @@ esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest *request)
|
|||||||
return response.send();
|
return response.send();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return request->reply(404);
|
return res->send(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user