diff --git a/README.md b/README.md index 8edd8d5..3a662fb 100644 --- a/README.md +++ b/README.md @@ -52,20 +52,16 @@ See the "[Connecting via Ethernet](#connecting-via-ethernet-optional)" section f ## Recommended ESP32 devices -- If WIFI6 is absolutely required: ESP32-C6 -- If PoE is required: Any of the above mentioned devices with PoE or any other ESP device in combination with a SPI Ethernet module ([W5500](https://www.aliexpress.com/w/wholesale-w5500.html)) and [PoE to Ethernet and USB type B/C splitter](https://aliexpress.com/w/wholesale-poe-splitter-usb-c.html) -- If you want maximum performance and intend to run any or multiple of the following: - - a Nuki Lock and Nuki Opener and/or - - MQTT SSL and/or - - HTTP SSL and/or - - large amounts of keypad codes, timecontrol or authorization entries - - Developing/debugging Nuki devices and/or Nuki Hub +We don't recommend using single-core ESP32 devices (ESP32-C3, ESP32-C6, ESP32-H2, ESP32-Solo1).
+Although NukiHub supports single-core devices, NukiHub uses both CPU cores (if available) to process tasks (e.g. HTTP server/MQTT client/BLE scanner/BLE client) and thus runs much better on dual-core devices.
- An ESP32-S3 with 2MB of PSRAM or more (look for an ESP32-S3 with the designation N>=4 and R>=2 such as an ESP32-S3 N16R8) - -- In general when buying a new device when size and a couple of dollars more or less are not an issue: An ESP32-S3 with 2MB of PSRAM or more.
+When buying a new device in 2025 we can only recommend the ESP32-S3 with PSRAM (look for an ESP32-S3 with the designation N>=4 and R>=2 such as an ESP32-S3 N16R8).
+The ESP32-S3 is a dual-core CPU with many GPIO's, ability to enlarge RAM using PSRAM, ability to connect Ethernet modules over SPI and optionally power the device with a PoE splitter.
+The only functions missing from the ESP32-S3 as compared to other ESP devices is the ability to use some Ethernet modules only supported by the original ESP32 (and ESP32-P4) and the ability to connect over WIFI6 (C6 or ESP32-P4 with C6 module) -The ESP32-S3 is a dual-core CPU with many GPIO's, ability to enlarge RAM using PSRAM, ability to connect Ethernet modules over SPI and optionally power the device with a PoE splitter. The only functions missing from the ESP32-S3 as compared to other ESP devices is the ability to use some Ethernet modules only supported by the original ESP32 and the ability to connect over WIFI6 (C6) +Other considerations: +- If PoE is required: An ESP32-S3 with PSRAM in combination with a SPI Ethernet module ([W5500](https://www.aliexpress.com/w/wholesale-w5500.html)) and [PoE to Ethernet and USB type B/C splitter](https://aliexpress.com/w/wholesale-poe-splitter-usb-c.html) or the M5stack Atom S3R with the M5stack AtomPoe W5500 module +- If WIFI6 is absolutely required (it probably isn't): ESP32-C6 ## Feature comparison @@ -194,6 +190,8 @@ In a browser navigate to the IP address assigned to the ESP32. - RSSI Publish interval: Set to a positive integer to set the amount of seconds between updates to the maintenance/wifiRssi MQTT topic with the current Wi-Fi RSSI, set to -1 to disable, default 60. - Restart on disconnect: Enable to restart the Nuki Hub when disconnected from the network. - Check for Firmware Updates every 24h: Enable to allow the Nuki Hub to check the latest release of the Nuki Hub firmware on boot and every 24 hours. Requires the Nuki Hub to be able to connect to github.com. The latest version will be published to MQTT and will be visible on the main page of the Web Configurator. +- HTTP SSL Certificate (PSRAM enabled devices only): Optionally set to the SSL certificate of the HTTPS server, see the "[HTTPS Server](#https-server-optional-psram-enabled-devices-only)" section of this README. +- HTTP SSL Key (PSRAM enabled devices only): Optionally set to the SSL key of the HTTPS server, see the "[HTTPS Server](#https-server-optional-psram-enabled-devices-only)" section of this README. #### IP Address assignment @@ -274,8 +272,9 @@ In a browser navigate to the IP address assigned to the ESP32. #### Credentials -- User: Pick a username to enable HTTP Basic authentication for the Web Configuration, Set to "#" to disable authentication. -- Password/Retype password: Pick a password to enable HTTP Basic authentication for the Web Configuration. +- User: Pick a username to enable HTTP authentication for the Web Configuration, Set to "#" to disable authentication. +- Password/Retype password: Pick a password to enable HTTP authentication for the Web Configuration. +- Use Digest Authentication (more secure): Enable to use HTTP Digest Authentication instead of HTTP Basic Authentication. Digest authentication is more secure, especially over unencrypted (HTTP) connections. #### Nuki Lock PIN / Nuki Opener PIN @@ -574,6 +573,23 @@ openssl req -new -key server.key -out server.csr -subj "/C=US/ST=YourState/L=You # sign it openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650 ``` + +## HTTPS Server (optional, PSRAM enabled devices only) + +The Webconfigurator can use/force HTTPS on PSRAM enabled devices.
+To enable SSL encryption, supply the certificate and key in the Network configuration page and reboot NukiHub.
+ +Example self-signed certificate creation for your HTTPS server: +```console + +# make a Certificate and key pair, MAKE SURE THE CN MATCHES THE DOMAIN AT WHICH NUKIHUB IS AVAILABLE +openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout nukihub.key -out nukihub.crt -subj "/C=US/ST=YourState/L=YourCity/O=YourOrganization/OU=YourUnit/CN=YourCAName" + +``` + +Although you can use the HTTPS server in this way your client device will not trust the certificate by default. +An option would be to configure a proxy SSL server (such as Caddy, Traefik, nginx) with a non-self signed (e.g. let's encrypt) SSL certificate and have this proxy server connect to NukiHub over SSL and trust the self-signed NukiHub certificate for this connection. + ## Home Assistant Discovery (optional) This software supports [MQTT Discovery](https://www.home-assistant.io/docs/mqtt/discovery/) for integrating Nuki Hub with Home Assistant.
diff --git a/lib/PsychicHttp/.clang-format b/lib/PsychicHttp/.clang-format new file mode 100644 index 0000000..b582997 --- /dev/null +++ b/lib/PsychicHttp/.clang-format @@ -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 + diff --git a/lib/PsychicHttp/.gitignore b/lib/PsychicHttp/.gitignore new file mode 100644 index 0000000..221fd8a --- /dev/null +++ b/lib/PsychicHttp/.gitignore @@ -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 \ No newline at end of file diff --git a/lib/PsychicHttp/.gitmodules b/lib/PsychicHttp/.gitmodules new file mode 100644 index 0000000..7f2753d --- /dev/null +++ b/lib/PsychicHttp/.gitmodules @@ -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 diff --git a/lib/PsychicHttp/CHANGELOG.md b/lib/PsychicHttp/CHANGELOG.md index ca99a11..9b526f4 100644 --- a/lib/PsychicHttp/CHANGELOG.md +++ b/lib/PsychicHttp/CHANGELOG.md @@ -1,3 +1,23 @@ +# v2.0 + +* Modified the request handling to bring initial url matching and filtering into PsychicHttpServer itself. + * Fixed a bug with filter() where endpoint is matched, but filter fails and it doesn't continue matching further endpoints on same uri (checks were in different codebases) + * HTTP_ANY support + * unlimited endpoints (no more need to manually set config.max_uri_handlers) + * much more flexibility for future +* Endpoint Matching Updates + * Endpoint matching functions can be set on server level (```server.setURIMatchFunction()```) or endpoint level (```endpoint.setURIMatchFunction()```) + * Added convenience macros MATCH_SIMPLE, MATCH_WILDCARD, and MATCH_REGEX + * Added regex matching of URIs, enable it with define PSY_ENABLE_REGEX + * On regex matched requests, you can get match data with request->getRegexMatches() +* Ported URL rewrite functionality from ESPAsyncWS + +## Changes required from v1.x to v2.0: + +* add a ```server.begin()``` or ```server.start()``` after all your ```server.on()``` calls +* remove any calls to ```config.max_uri_handlers``` +* if you are using a custom ```server.config.uri_match_fn``` to match uris, change it to ```server.setURIMatchFunction()``` + # v1.2.1 * Fix bug with missing include preventing the HTTPS server from compiling. diff --git a/lib/PsychicHttp/README.md b/lib/PsychicHttp/README.md index 5c4289e..05589f8 100644 --- a/lib/PsychicHttp/README.md +++ b/lib/PsychicHttp/README.md @@ -23,7 +23,7 @@ PsychicHttp is a webserver library for ESP32 + Arduino framework which uses the ## Differences from ESPAsyncWebserver * No templating system (anyone actually use this?) -* No url rewriting (but you can use request->redirect) +* No url rewriting (but you can use response->redirect) # Usage @@ -120,7 +120,7 @@ If you have existing code using ESPAsyncWebserver, you will feel right at home w ## setup() Stuff -* no more server.begin(), call server.listen(80), before you add your handlers +* add your handlers and call server.begin() * server has a configurable limit on .on() endpoints. change it with ```server.config.max_uri_handlers = 20;``` as needed. * check your callback function definitions: * AsyncWebServerRequest -> PsychicRequest @@ -136,7 +136,7 @@ If you have existing code using ESPAsyncWebserver, you will feel right at home w ## Requests / Responses -* request->send is now request->reply() +* request->send is now response->send() * if you create a response, call response->send() directly, not request->send(reply) * request->headers() is not supported by ESP-IDF, you have to just check for the header you need. * No AsyncCallbackJsonWebHandler (for now... can add if needed) @@ -164,9 +164,6 @@ void setup() //connect to wifi - //start the server listening on port 80 (standard HTTP port) - server.listen(80); - //call server methods to attach endpoints and handlers server.on(...); server.serveStatic(...); @@ -198,7 +195,7 @@ The ```server.on(...)``` returns a pointer to the endpoint, which can be used to ```cpp //respond to /url only from requests to the AP -server.on("/url", HTTP_GET, request_callback)->setFilter(ON_AP_FILTER); +server.on("/url", HTTP_GET, request_callback)->addFilter(ON_AP_FILTER); //require authentication on /url server.on("/url", HTTP_GET, request_callback)->setAuthentication("user", "pass"); @@ -212,7 +209,7 @@ server.on("/ws")->attachHandler(&websocketHandler); The ```PsychicWebHandler``` class is for handling standard web requests. It provides a single callback: ```onRequest()```. This callback is called when the handler receives a valid HTTP request. -One major difference from ESPAsyncWebserver is that this callback needs to return an esp_err_t variable to let the server know the result of processing the request. The ```response->reply()``` and ```request->send()``` functions will return this. It is a good habit to return the result of these functions as sending the response will close the connection. +One major difference from ESPAsyncWebserver is that this callback needs to return an esp_err_t variable to let the server know the result of processing the request. The ```response->send()``` and ```request->send()``` functions will return this. It is a good habit to return the result of these functions as sending the response will close the connection. The function definition for the onRequest callback is: @@ -226,7 +223,7 @@ Here is a simple example that sends back the client's IP on the URL /ip server.on("/ip", [](PsychicRequest *request) { String output = "Your IP is: " + request->client()->remoteIP().toString(); - return request->reply(output.c_str()); + return response->send(output.c_str()); }); ``` @@ -294,7 +291,7 @@ It's worth noting that there is no standard way of passing in a filename for thi String url = "/" + request->getFilename(); String output = "" + url + ""; - return request->reply(output.c_str()); + return response->send(output.c_str()); }); //wildcard basic file upload - POST to /upload/filename.ext @@ -352,7 +349,7 @@ Very similar to the basic upload, with 2 key differences: output += "Param 1: " + request->getParam("param1")->value() + "
\n"; output += "Param 2: " + request->getParam("param2")->value() + "
\n"; - return request->reply(output.c_str()); + return response->send(output.c_str()); }); //upload to /multipart url @@ -374,11 +371,11 @@ The ```server.serveStatic()``` function handles creating the handler and assigni ```cpp //serve static files from LittleFS/www on / only to clients on same wifi network //this is where our /index.html file lives -server.serveStatic("/", LittleFS, "/www/")->setFilter(ON_STA_FILTER); +server.serveStatic("/", LittleFS, "/www/")->addFilter(ON_STA_FILTER); //serve static files from LittleFS/www-ap on / only to clients on SoftAP //this is where our /index.html file lives -server.serveStatic("/", LittleFS, "/www-ap/")->setFilter(ON_AP_FILTER); +server.serveStatic("/", LittleFS, "/www-ap/")->addFilter(ON_AP_FILTER); //serve static files from LittleFS/img on /img //it's more efficient to serve everything from a single www directory, but this is also possible. @@ -426,17 +423,17 @@ Here is a basic example of using WebSockets: PsychicWebSocketHandler websocketHandler(); websocketHandler.onOpen([](PsychicWebSocketClient *client) { - Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); + Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str()); client->sendMessage("Hello!"); }); websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) { Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload); - return request->reply(frame); + return response->send(frame); }); websocketHandler.onClose([](PsychicWebSocketClient *client) { - Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); + Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString().c_str()); }); //attach the handler to /ws. You can then connect to ws://ip.address/ws @@ -452,7 +449,7 @@ The onFrame() callback has 2 parameters: For sending data on the websocket connection, there are 3 methods: -* ```request->reply()``` - only available in the onFrame() callback context. +* ```response->send()``` - only available in the onFrame() callback context. * ```webSocketHandler.sendAll()``` - can be used anywhere to send websocket messages to all connected clients. * ```client->send()``` - can be used anywhere* to send a websocket message to a specific client @@ -488,12 +485,12 @@ Here is a basic example of using PsychicEventSource: PsychicEventSource eventSource; eventSource.onOpen([](PsychicEventSourceClient *client) { - Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); + Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str()); client->send("Hello user!", NULL, millis(), 1000); }); eventSource.onClose([](PsychicEventSourceClient *client) { - Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); + Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString().c_str()); }); //attach the handler to /events @@ -524,7 +521,7 @@ PsychicHttp supports HTTPS / SSL out of the box, however there are some limitati #include #include PsychicHttpsServer server; -server.listen(443, server_cert, server_key); +server.setCertificate(server_cert, server_key); ``` ```server_cert``` and ```server_key``` are both ```const char *``` parameters which contain the server certificate and private key, respectively. @@ -552,10 +549,9 @@ Last, but not least, you can create a separate HTTP server on port 80 that redir //this creates a 2nd server listening on port 80 and redirects all requests HTTPS PsychicHttpServer *redirectServer = new PsychicHttpServer(); redirectServer->config.ctrl_port = 20420; // just a random port different from the default one -redirectServer->listen(80); redirectServer->onNotFound([](PsychicRequest *request) { String url = "https://" + request->host() + request->url(); - return request->redirect(url.c_str()); + return response->redirect(url.c_str()); }); ``` @@ -775,51 +771,22 @@ With all due respect to @me-no-dev who has done some amazing work in the open so ArduinoMongoose is a good alternative, although the latency issues when it gets fully loaded can be very annoying. I believe it is also cross platform to other microcontrollers as well, but I haven't tested that. The other issue here is that it is based on an old version of a modified Mongoose library that will be difficult to update as it is a major revision behind and several security updates behind as well. Big thanks to @jeremypoulter though as PsychicHttp is a fork of ArduinoMongoose so it's built on strong bones. +# Community / Support + +The best way to get support is probably with Github issues. There is also a [Discord chat](https://discord.gg/CM5abjGG) that is pretty active. + # Roadmap -## v1.2: ESPAsyncWebserver Parity - - -Change: -Modify the request handling to bring initail url matching and filtering into PsychicHttpServer itself. - -Benefits: -* Fix a bug with filter() where endpoint is matched, but filter fails and it doesn't continue matching further endpoints (checks are in different codebases) -* HTTP_ANY support -* unlimited endpoints - * we would use a List to store endpoints - * dont have to pre-declare config.max_uri_handlers; -* much more flexibility for future - -Issues -* it would log a warning on every request as if its a 404. (httpd_uri.c:298) -* req->user_ctx is not passed in. (httpd_uri.c:309) - * but... user_ctx is something we could store in the psychicendpoint data - * Websocket support assumes an endpoint with matching url / method (httpd_uri.c:312) - * we could copy and bring this code into our own internal request processor - * would need to manually maintain more code (~100 lines?) and be more prone to esp-idf http_server updates causing problems. - -How to implement -* set config.max_uri_handlers = 1; -* possibly do not register any uri_handlers (looks like it would be fastest way to exit httpd_find_uri_handler (httpd_uri.c:94)) - * looks like 404 is set by default, so should work. -* modify PsychicEndpoint to store the stuff we would pass to http_server -* create a new function handleRequest() before PsychicHttpServer::defaultNotFoundHandler to process incoming requests. - * bring in code from PsychicHttpServer::notFoundHandler - * add new code to loop over endpoints to call match and filter -* bring code from esp-idf library - -* templating system -* regex url matching -* rewrite urls? -* What else are we missing? +## v2.0: ESPAsyncWebserver Parity +* As much ESPAsyncWebServer compatibility as possible +* Update benchmarks and get new data + * we should also track program size and memory usage ## Longterm Wants * investigate websocket performance gap * support for esp-idf framework -* support for arduino 3.0 framework * Enable worker based multithreading with esp-idf v5.x * 100-continue support? diff --git a/lib/PsychicHttp/RELEASE.md b/lib/PsychicHttp/RELEASE.md index 98db528..541d551 100644 --- a/lib/PsychicHttp/RELEASE.md +++ b/lib/PsychicHttp/RELEASE.md @@ -1,6 +1,8 @@ * Update CHANGELOG +* Bump version in src/PsychicVersion.h * Bump version in library.json * Bump version in library.properties * Make new release + tag * this will get pulled in automatically by Arduino Library Indexer -* run ```pio pkg publish``` to publish to Platform.io \ No newline at end of file +* ~~run ```pio pkg publish``` to publish to Platform.io~~ + * automatically publishes on release via .github hook \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/arduinomongoose/src/main.cpp b/lib/PsychicHttp/benchmark/arduinomongoose/src/main.cpp index f7e0a9d..c9d7ed0 100644 --- a/lib/PsychicHttp/benchmark/arduinomongoose/src/main.cpp +++ b/lib/PsychicHttp/benchmark/arduinomongoose/src/main.cpp @@ -8,18 +8,18 @@ */ #include -#include +#include +#include #include #include -#include -#include +#include -const char *ssid = ""; -const char *password = ""; +const char* ssid = ""; +const char* password = ""; MongooseHttpServer server; -const char *htmlContent = R"( +const char* htmlContent = R"( @@ -154,25 +154,23 @@ void setup() // To debug, please enable Core Debug Level to Verbose if (connectToWifi()) { - if(!LittleFS.begin()) + if (!LittleFS.begin()) { Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode"); return; } - //start our server + // start our server Mongoose.begin(); server.begin(80); - //index file - server.on("/", HTTP_GET, [](MongooseHttpServerRequest *request) - { - request->send(200, "text/html", htmlContent); - }); + // index file + server.on("/", HTTP_GET, [](MongooseHttpServerRequest* request) + { request->send(200, "text/html", htmlContent); }); - //api - parameters passed in via query eg. /api/endpoint?foo=bar - server.on("/api", HTTP_GET, [](MongooseHttpServerRequest *request) - { + // api - parameters passed in via query eg. /api/endpoint?foo=bar + server.on("/api", HTTP_GET, [](MongooseHttpServerRequest* request) + { //create a response object StaticJsonDocument<128> output; output["msg"] = "status"; @@ -189,19 +187,18 @@ void setup() //serialize and return String jsonBuffer; serializeJson(output, jsonBuffer); - request->send(200, "application/json", jsonBuffer.c_str()); - }); + request->send(200, "application/json", jsonBuffer.c_str()); }); - //websocket - server.on("/ws$")-> - onFrame([](MongooseHttpWebSocketConnection *connection, int flags, uint8_t *data, size_t len) { - connection->send(WEBSOCKET_OP_TEXT, data, len); - //server.sendAll(connection, (char *)data); - }); + // websocket + server.on("/ws$")->onFrame([](MongooseHttpWebSocketConnection* connection, int flags, uint8_t* data, size_t len) + { + connection->send(WEBSOCKET_OP_TEXT, data, len); + // server.sendAll(connection, (char *)data); + }); - //hack - no servestatic - server.on("/alien.png", HTTP_GET, [](MongooseHttpServerRequest *request) - { + // hack - no servestatic + server.on("/alien.png", HTTP_GET, [](MongooseHttpServerRequest* request) + { //open our file File fp = LittleFS.open("/www/alien.png"); size_t length = fp.size(); @@ -223,8 +220,7 @@ void setup() free(data); } else - request->send(503); - }); + request->send(503); }); } } diff --git a/lib/PsychicHttp/benchmark/espasyncwebserver/platformio.ini b/lib/PsychicHttp/benchmark/espasyncwebserver/platformio.ini index 0f883e3..5090e7f 100644 --- a/lib/PsychicHttp/benchmark/espasyncwebserver/platformio.ini +++ b/lib/PsychicHttp/benchmark/espasyncwebserver/platformio.ini @@ -11,12 +11,31 @@ [env] platform = espressif32 framework = arduino -board = esp32dev +; board = esp32dev +board = esp32-s3-devkitc-1 +upload_port = /dev/ttyACM0 +monitor_port = /dev/ttyACM1 monitor_speed = 115200 monitor_filters = esp32_exception_decoder -lib_deps = - https://github.com/me-no-dev/ESPAsyncWebServer - bblanchon/ArduinoJson + +lib_compat_mode = strict +lib_ldf_mode = chain +lib_deps = + ; mathieucarbou/AsyncTCP @ 3.2.10 + https://github.com/mathieucarbou/AsyncTCPSock/archive/refs/tags/v1.0.3-dev.zip + mathieucarbou/ESPAsyncWebServer @ 3.3.16 + bblanchon/ArduinoJson +lib_ignore = + AsyncTCP + mathieucarbou/AsyncTCP + board_build.filesystem = littlefs +build_flags = + -D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000 + -D CONFIG_ASYNC_TCP_PRIORITY=10 + -D CONFIG_ASYNC_TCP_QUEUE_SIZE=128 + -D CONFIG_ASYNC_TCP_RUNNING_CORE=1 + -D CONFIG_ASYNC_TCP_STACK_SIZE=4096 + -D WS_MAX_QUEUED_MESSAGES=128 [env:default] \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/espasyncwebserver/src/main.cpp b/lib/PsychicHttp/benchmark/espasyncwebserver/src/main.cpp index c76c1cd..16b7516 100644 --- a/lib/PsychicHttp/benchmark/espasyncwebserver/src/main.cpp +++ b/lib/PsychicHttp/benchmark/espasyncwebserver/src/main.cpp @@ -7,19 +7,29 @@ CONDITIONS OF ANY KIND, either express or implied. */ +#include "_secret.h" #include -#include +#include #include +#include #include -#include +#include -const char *ssid = ""; -const char *password = ""; +#ifndef WIFI_SSID + #error "You need to enter your wifi credentials. Copy secret.h to _secret.h and enter your credentials there." +#endif + +// Enter your WIFI credentials in secret.h +const char* ssid = WIFI_SSID; +const char* password = WIFI_PASS; + +// hostname for mdns (psychic.local) +const char* local_hostname = "psychic"; AsyncWebServer server(80); AsyncWebSocket ws("/ws"); -const char *htmlContent = R"( +const char* htmlContent = R"( @@ -79,14 +89,16 @@ const char *htmlContent = R"( )"; +const size_t htmlContentLen = strlen(htmlContent); + bool connectToWifi() { Serial.println(); Serial.print("[WiFi] Connecting to "); Serial.println(ssid); - WiFi.setSleep(false); - WiFi.useStaticBuffers(true); + // WiFi.setSleep(false); + // WiFi.useStaticBuffers(true); WiFi.begin(ssid, password); @@ -95,10 +107,8 @@ bool connectToWifi() int numberOfTries = 20; // Wait for the WiFi event - while (true) - { - switch (WiFi.status()) - { + while (true) { + switch (WiFi.status()) { case WL_NO_SSID_AVAIL: Serial.println("[WiFi] SSID not found"); break; @@ -128,15 +138,12 @@ bool connectToWifi() } delay(tryDelay); - if (numberOfTries <= 0) - { + if (numberOfTries <= 0) { Serial.print("[WiFi] Failed to connect to WiFi!"); // Use disconnect function to force stop trying to connect WiFi.disconnect(); return false; - } - else - { + } else { numberOfTries--; } } @@ -144,28 +151,29 @@ bool connectToWifi() return false; } -void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ - if(type == WS_EVT_CONNECT){ - //client connected - // Serial.printf("ws[%s][%u] connect\n", server->url(), client->id()); - // client->printf("Hello Client %u :)", client->id()); - // client->ping(); - } else if(type == WS_EVT_DISCONNECT){ - //client disconnected - // Serial.printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id()); - } else if(type == WS_EVT_ERROR){ - //error was received from the other end - // Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data); - } else if(type == WS_EVT_PONG){ - //pong message was received (in response to a ping request maybe) - // Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:""); - } else if(type == WS_EVT_DATA){ - //data packet - AwsFrameInfo * info = (AwsFrameInfo*)arg; - if(info->final && info->index == 0 && info->len == len){ - //the whole message is in a single frame and we got all of it's data - // Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len); - if(info->opcode == WS_TEXT){ +void onEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) +{ + if (type == WS_EVT_CONNECT) { + // client connected + // Serial.printf("ws[%s][%u] connect\n", server->url(), client->id()); + // client->printf("Hello Client %u :)", client->id()); + // client->ping(); + } else if (type == WS_EVT_DISCONNECT) { + // client disconnected + // Serial.printf("ws[%s][%u] disconnect: %u\n", server->url(), client->id()); + } else if (type == WS_EVT_ERROR) { + // error was received from the other end + // Serial.printf("ws[%s][%u] error(%u): %s\n", server->url(), client->id(), *((uint16_t*)arg), (char*)data); + } else if (type == WS_EVT_PONG) { + // pong message was received (in response to a ping request maybe) + // Serial.printf("ws[%s][%u] pong[%u]: %s\n", server->url(), client->id(), len, (len)?(char*)data:""); + } else if (type == WS_EVT_DATA) { + // data packet + AwsFrameInfo* info = (AwsFrameInfo*)arg; + if (info->final && info->index == 0 && info->len == len) { + // the whole message is in a single frame and we got all of it's data + // Serial.printf("ws[%s][%u] %s-message[%llu]: ", server->url(), client->id(), (info->opcode == WS_TEXT)?"text":"binary", info->len); + if (info->opcode == WS_TEXT) { data[len] = 0; // Serial.printf("%s\n", (char*)data); } else { @@ -174,22 +182,21 @@ void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp // } // Serial.printf("\n"); } - if(info->opcode == WS_TEXT) - { - client->text((char *)data, len); + if (info->opcode == WS_TEXT) { + client->text((char*)data, len); } // else // client->binary("I got your binary message"); } else { - //message is comprised of multiple frames or the frame is split into multiple packets - if(info->index == 0){ + // message is comprised of multiple frames or the frame is split into multiple packets + if (info->index == 0) { // if(info->num == 0) // Serial.printf("ws[%s][%u] %s-message start\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); // Serial.printf("ws[%s][%u] frame[%u] start[%llu]\n", server->url(), client->id(), info->num, info->len); } - Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT)?"text":"binary", info->index, info->index + len); - if(info->message_opcode == WS_TEXT){ + Serial.printf("ws[%s][%u] frame[%u] %s[%llu - %llu]: ", server->url(), client->id(), info->num, (info->message_opcode == WS_TEXT) ? "text" : "binary", info->index, info->index + len); + if (info->message_opcode == WS_TEXT) { data[len] = 0; // Serial.printf("%s\n", (char*)data); } else { @@ -199,13 +206,12 @@ void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTyp // Serial.printf("\n"); } - if((info->index + len) == info->len){ + if ((info->index + len) == info->len) { // Serial.printf("ws[%s][%u] frame[%u] end[%llu]\n", server->url(), client->id(), info->num, info->len); - if(info->final){ + if (info->final) { // Serial.printf("ws[%s][%u] %s-message end\n", server->url(), client->id(), (info->message_opcode == WS_TEXT)?"text":"binary"); - if(info->message_opcode == WS_TEXT) - { - client->text((char *)data, info->len); + if (info->message_opcode == WS_TEXT) { + client->text((char*)data, info->len); } // else // client->binary("I got your binary message"); @@ -223,40 +229,41 @@ void setup() // We start by connecting to a WiFi network // To debug, please enable Core Debug Level to Verbose - if (connectToWifi()) - { - if(!LittleFS.begin()) - { + if (connectToWifi()) { + // set up our esp32 to listen on the local_hostname.local domain + if (!MDNS.begin(local_hostname)) { + Serial.println("Error starting mDNS"); + return; + } + MDNS.addService("http", "tcp", 80); + + if (!LittleFS.begin()) { Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode"); return; } - //api - parameters passed in via query eg. /api/endpoint?foo=bar - server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) - { - request->send(200, "text/html", htmlContent); + // api - parameters passed in via query eg. /api/endpoint?foo=bar + server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { + // ESPAsyncWebServer, sending a char* does a buffer copy, unlike Psychic. + // Sending flash data is done with the uint8_t* overload. + request->send(200, "text/html", (uint8_t*)htmlContent, htmlContentLen); }); - //serve static files from LittleFS/www on / - server.serveStatic("/", LittleFS, "/www/"); - - //api - parameters passed in via query eg. /api/endpoint?foo=bar - server.on("/api", HTTP_GET, [](AsyncWebServerRequest *request) - { - //create a response object - StaticJsonDocument<128> output; + // api - parameters passed in via query eg. /api/endpoint?foo=bar + server.on("/api", HTTP_GET, [](AsyncWebServerRequest* request) { + // create a response object + JsonDocument output; output["msg"] = "status"; output["status"] = "success"; output["millis"] = millis(); - //work with some params - if (request->hasParam("foo")) - { - AsyncWebParameter* foo = request->getParam("foo"); + // work with some params + if (request->hasParam("foo")) { + const AsyncWebParameter* foo = request->getParam("foo"); output["foo"] = foo->value(); } - //serialize and return + // serialize and return String jsonBuffer; serializeJson(output, jsonBuffer); request->send(200, "application/json", jsonBuffer.c_str()); @@ -265,6 +272,10 @@ void setup() ws.onEvent(onEvent); server.addHandler(&ws); + // put this last, otherwise it clogs the other requests + // serve static files from LittleFS/www on / + server.serveStatic("/", LittleFS, "/www/"); + server.begin(); } } @@ -272,5 +283,6 @@ void setup() void loop() { ws.cleanupClients(); + Serial.printf("Free Heap: %d\n", esp_get_free_heap_size()); delay(1000); } \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/espasyncwebserver/src/secret.h b/lib/PsychicHttp/benchmark/espasyncwebserver/src/secret.h new file mode 100644 index 0000000..6d4bb15 --- /dev/null +++ b/lib/PsychicHttp/benchmark/espasyncwebserver/src/secret.h @@ -0,0 +1,2 @@ +#define WIFI_SSID "Your_SSID" +#define WIFI_PASS "Your_PASS" \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/eventsource-client-test.js b/lib/PsychicHttp/benchmark/eventsource-client-test.js index 8253789..85164f0 100644 --- a/lib/PsychicHttp/benchmark/eventsource-client-test.js +++ b/lib/PsychicHttp/benchmark/eventsource-client-test.js @@ -1,7 +1,8 @@ #!/usr/bin/env node +//stress test the client opening/closing code const EventSource = require('eventsource'); -const url = 'http://192.168.2.131/events'; +const url = 'http://psychic.local/events'; async function eventSourceClient() { console.log(`Starting test`); diff --git a/lib/PsychicHttp/benchmark/http-client-test.js b/lib/PsychicHttp/benchmark/http-client-test.js index 29588b9..591a610 100644 --- a/lib/PsychicHttp/benchmark/http-client-test.js +++ b/lib/PsychicHttp/benchmark/http-client-test.js @@ -1,8 +1,9 @@ #!/usr/bin/env node +//stress test the http request code const axios = require('axios'); -const url = 'http://192.168.2.131/api'; +const url = 'http://psychic.local/api'; const queryParams = { foo: 'bar', foo1: 'bar', diff --git a/lib/PsychicHttp/benchmark/loadtest-http.sh b/lib/PsychicHttp/benchmark/loadtest-http.sh index 4e75b84..888f04d 100644 --- a/lib/PsychicHttp/benchmark/loadtest-http.sh +++ b/lib/PsychicHttp/benchmark/loadtest-http.sh @@ -1,37 +1,42 @@ #!/usr/bin/env bash #Command to install the testers: -# npm install -g autocannon +# npm install -TEST_IP="192.168.2.131" -TEST_TIME=60 -LOG_FILE=psychic-http-loadtest.log +TEST_IP="psychic.local" +TEST_TIME=10 +#LOG_FILE=psychic-http-loadtest.log +LOG_FILE=_psychic-http-loadtest.json +RESULTS_FILE=http-loadtest-results.csv TIMEOUT=10000 +WORKERS=1 PROTOCOL=http #PROTOCOL=https -if test -f "$LOG_FILE"; then - rm $LOG_FILE -fi +echo "url,connections,rps,latency,errors" > $RESULTS_FILE for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20 -#for CONCURRENCY in 20 do printf "\n\nCLIENTS: *** $CONCURRENCY ***\n\n" >> $LOG_FILE echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/" - #loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/" --quiet >> $LOG_FILE - autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/" >> $LOG_FILE 2>&1 - printf "\n\n----------------\n\n" >> $LOG_FILE - sleep 1 + autocannon -c $CONCURRENCY -w $WORKERS -d $TEST_TIME -j "$PROTOCOL://$TEST_IP/" > $LOG_FILE + node parse-http-test.js $LOG_FILE $RESULTS_FILE + sleep 5 +done +for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20 +do echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/api" - #loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/api?foo=bar" --quiet >> $LOG_FILE - autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/api?foo=bar" >> $LOG_FILE 2>&1 - printf "\n\n----------------\n\n" >> $LOG_FILE - sleep 1 - + autocannon -c $CONCURRENCY -w $WORKERS -d $TEST_TIME -j "$PROTOCOL://$TEST_IP/api?foo=bar" > $LOG_FILE + node parse-http-test.js $LOG_FILE $RESULTS_FILE + sleep 5 +done + +for CONCURRENCY in 1 2 3 4 5 6 7 8 9 10 15 20 +do echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/alien.png" - #loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --timeout $TIMEOUT "$PROTOCOL://$TEST_IP/alien.png" --quiet >> $LOG_FILE - autocannon -c $CONCURRENCY -w 1 -d $TEST_TIME --renderStatusCodes "$PROTOCOL://$TEST_IP/alien.png" >> $LOG_FILE 2>&1 - printf "\n\n----------------\n\n" >> $LOG_FILE - sleep 1 -done \ No newline at end of file + 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 diff --git a/lib/PsychicHttp/benchmark/loadtest-websocket.sh b/lib/PsychicHttp/benchmark/loadtest-websocket.sh index a9b5a41..dcbaab0 100644 --- a/lib/PsychicHttp/benchmark/loadtest-websocket.sh +++ b/lib/PsychicHttp/benchmark/loadtest-websocket.sh @@ -1,10 +1,11 @@ #!/usr/bin/env bash #Command to install the testers: -# npm install -g loadtest +# npm install -TEST_IP="192.168.2.131" +TEST_IP="psychic.local" TEST_TIME=60 -LOG_FILE=psychic-websocket-loadtest.log +LOG_FILE=psychic-websocket-loadtest.json +RESULTS_FILE=websocket-loadtest-results.csv PROTOCOL=ws #PROTOCOL=wss @@ -12,20 +13,33 @@ if test -f "$LOG_FILE"; then rm $LOG_FILE fi -for CONCURRENCY in 1 2 3 4 5 6 7 +echo "url,clients,rps,latency,errors" > $RESULTS_FILE + +CORES=1 +for CONCURRENCY in 1 2 3 4 5 do - printf "\n\nCLIENTS: *** $CONCURRENCY ***\n\n" >> $LOG_FILE echo "Testing $CONCURRENCY clients on $PROTOCOL://$TEST_IP/ws" - loadtest -c $CONCURRENCY --cores 1 -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE - sleep 1 + loadtest -c $CONCURRENCY --cores $CORES -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE + node parse-websocket-test.js $LOG_FILE $RESULTS_FILE + sleep 2 done -for CONNECTIONS in 8 10 16 20 -#for CONNECTIONS in 20 +CORES=2 +for CONNECTIONS in 6 8 10 12 14 do CONCURRENCY=$((CONNECTIONS / 2)) - printf "\n\nCLIENTS: *** $CONNECTIONS ***\n\n" >> $LOG_FILE echo "Testing $CONNECTIONS clients on $PROTOCOL://$TEST_IP/ws" - loadtest -c $CONCURRENCY --cores 2 -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE - sleep 1 -done \ No newline at end of file + loadtest -c $CONCURRENCY --cores $CORES -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE + node parse-websocket-test.js $LOG_FILE $RESULTS_FILE + sleep 2 +done + +CORES=4 +for CONNECTIONS in 16 20 24 28 32 +do + CONCURRENCY=$((CONNECTIONS / CORES)) + echo "Testing $CONNECTIONS clients on $PROTOCOL://$TEST_IP/ws" + loadtest -c $CONCURRENCY --cores $CORES -t $TEST_TIME --insecure $PROTOCOL://$TEST_IP/ws --quiet 2> /dev/null >> $LOG_FILE + node parse-websocket-test.js $LOG_FILE $RESULTS_FILE + sleep 2 +done diff --git a/lib/PsychicHttp/benchmark/package.json b/lib/PsychicHttp/benchmark/package.json index 3237d56..c3ab2ac 100644 --- a/lib/PsychicHttp/benchmark/package.json +++ b/lib/PsychicHttp/benchmark/package.json @@ -1,7 +1,10 @@ { "dependencies": { + "autocannon": "^7.15.0", "axios": "^1.6.2", + "csv-writer": "^1.6.0", "eventsource": "^2.0.2", + "loadtest": "^8.0.9", "ws": "^8.14.2" } } diff --git a/lib/PsychicHttp/benchmark/parse-http-test.js b/lib/PsychicHttp/benchmark/parse-http-test.js new file mode 100644 index 0000000..652e117 --- /dev/null +++ b/lib/PsychicHttp/benchmark/parse-http-test.js @@ -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 '); + 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); + }); +}); + diff --git a/lib/PsychicHttp/benchmark/parse-websocket-test.js b/lib/PsychicHttp/benchmark/parse-websocket-test.js new file mode 100644 index 0000000..fc6bf7f --- /dev/null +++ b/lib/PsychicHttp/benchmark/parse-websocket-test.js @@ -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 '); + 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); +}); + diff --git a/lib/PsychicHttp/benchmark/psychichttp/platformio.ini b/lib/PsychicHttp/benchmark/psychichttp/platformio.ini index 868f0ca..2a6d734 100644 --- a/lib/PsychicHttp/benchmark/psychichttp/platformio.ini +++ b/lib/PsychicHttp/benchmark/psychichttp/platformio.ini @@ -15,8 +15,14 @@ board = esp32dev monitor_speed = 115200 monitor_filters = esp32_exception_decoder lib_deps = - https://github.com/hoeken/PsychicHttp bblanchon/ArduinoJson board_build.filesystem = littlefs [env:default] +lib_deps = https://github.com/hoeken/PsychicHttp + +[env:v2-dev] +lib_deps = https://github.com/hoeken/PsychicHttp#v2-dev +board = esp32-s3-devkitc-1 +upload_port = /dev/ttyACM0 +monitor_port = /dev/ttyACM1 \ No newline at end of file diff --git a/lib/PsychicHttp/benchmark/psychichttp/src/main.cpp b/lib/PsychicHttp/benchmark/psychichttp/src/main.cpp index c42d644..6f91624 100644 --- a/lib/PsychicHttp/benchmark/psychichttp/src/main.cpp +++ b/lib/PsychicHttp/benchmark/psychichttp/src/main.cpp @@ -7,26 +7,30 @@ CONDITIONS OF ANY KIND, either express or implied. */ -#include -#include -#include -#include -#include #include "_secret.h" +#include +#include +#include +#include +#include +#include #ifndef WIFI_SSID #error "You need to enter your wifi credentials. Copy secret.h to _secret.h and enter your credentials there." #endif -//Enter your WIFI credentials in secret.h -const char *ssid = WIFI_SSID; -const char *password = WIFI_PASS; +// Enter your WIFI credentials in secret.h +const char* ssid = WIFI_SSID; +const char* password = WIFI_PASS; + +// hostname for mdns (psychic.local) +const char* local_hostname = "psychic"; PsychicHttpServer server; PsychicWebSocketHandler websocketHandler; PsychicEventSource eventSource; -const char *htmlContent = R"( +const char* htmlContent = R"( @@ -92,8 +96,8 @@ bool connectToWifi() Serial.print("[WiFi] Connecting to "); Serial.println(ssid); - WiFi.setSleep(false); - WiFi.useStaticBuffers(true); + // WiFi.setSleep(false); + // WiFi.useStaticBuffers(true); WiFi.begin(ssid, password); @@ -102,10 +106,8 @@ bool connectToWifi() int numberOfTries = 20; // Wait for the WiFi event - while (true) - { - switch (WiFi.status()) - { + while (true) { + switch (WiFi.status()) { case WL_NO_SSID_AVAIL: Serial.println("[WiFi] SSID not found"); break; @@ -135,15 +137,12 @@ bool connectToWifi() } delay(tryDelay); - if (numberOfTries <= 0) - { + if (numberOfTries <= 0) { Serial.print("[WiFi] Failed to connect to WiFi!"); // Use disconnect function to force stop trying to connect WiFi.disconnect(); return false; - } - else - { + } else { numberOfTries--; } } @@ -157,47 +156,42 @@ void setup() delay(10); Serial.println("PsychicHTTP Benchmark"); - if (connectToWifi()) - { - if(!LittleFS.begin()) - { + if (connectToWifi()) { + // set up our esp32 to listen on the local_hostname.local domain + if (!MDNS.begin(local_hostname)) { + Serial.println("Error starting mDNS"); + return; + } + MDNS.addService("http", "tcp", 80); + + if (!LittleFS.begin()) { Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode"); return; } - //start our server - server.listen(80); + // our index + server.on("/", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->send(200, "text/html", htmlContent); }); - //our index - server.on("/", HTTP_GET, [](PsychicRequest *request) - { - return request->reply(200, "text/html", htmlContent); - }); - - //serve static files from LittleFS/www on / + // serve static files from LittleFS/www on / server.serveStatic("/", LittleFS, "/www/"); - //a websocket echo server - websocketHandler.onOpen([](PsychicWebSocketClient *client) { - client->sendMessage("Hello!"); - }); - websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) { - request->reply(frame); - return ESP_OK; + // a websocket echo server + websocketHandler.onOpen([](PsychicWebSocketClient* client) { + // client->sendMessage("Hello!"); }); + websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) { + response->send(frame); + return ESP_OK; }); server.on("/ws", &websocketHandler); - //EventSource server - eventSource.onOpen([](PsychicEventSourceClient *client) { - client->send("Hello", NULL, millis(), 1000); - }); + // EventSource server + eventSource.onOpen([](PsychicEventSourceClient* client) { client->send("Hello", NULL, millis(), 1000); }); server.on("/events", &eventSource); - //api - parameters passed in via query eg. /api/endpoint?foo=bar - server.on("/api", HTTP_GET, [](PsychicRequest *request) - { + // api - parameters passed in via query eg. /api/endpoint?foo=bar + server.on("/api", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { //create a response object - StaticJsonDocument<128> output; + JsonDocument output; output["msg"] = "status"; output["status"] = "success"; output["millis"] = millis(); @@ -212,16 +206,16 @@ void setup() //serialize and return String jsonBuffer; serializeJson(output, jsonBuffer); - return request->reply(200, "application/json", jsonBuffer.c_str()); - }); + return response->send(200, "application/json", jsonBuffer.c_str()); }); + + server.begin(); } } unsigned long last; void loop() { - if (millis() - last > 1000) - { + if (millis() - last > 1000) { Serial.printf("Free Heap: %d\n", esp_get_free_heap_size()); last = millis(); } diff --git a/lib/PsychicHttp/benchmark/psychichttps/src/main.cpp b/lib/PsychicHttp/benchmark/psychichttps/src/main.cpp index 2639758..9915c8e 100644 --- a/lib/PsychicHttp/benchmark/psychichttps/src/main.cpp +++ b/lib/PsychicHttp/benchmark/psychichttps/src/main.cpp @@ -7,22 +7,21 @@ CONDITIONS OF ANY KIND, either express or implied. */ -#include -#include -#include -#include -#include #include "_secret.h" +#include +#include +#include #include #include +#include #ifndef WIFI_SSID #error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there." #endif -//Enter your WIFI credentials in secret.h -const char *ssid = WIFI_SSID; -const char *password = WIFI_PASS; +// Enter your WIFI credentials in secret.h +const char* ssid = WIFI_SSID; +const char* password = WIFI_PASS; PsychicHttpsServer server; PsychicWebSocketHandler websocketHandler; @@ -30,7 +29,7 @@ PsychicWebSocketHandler websocketHandler; String server_cert; String server_key; -const char *htmlContent = R"( +const char* htmlContent = R"( @@ -162,52 +161,56 @@ void setup() if (connectToWifi()) { - if(!LittleFS.begin()) + if (!LittleFS.begin()) { Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode"); return; } File fp = LittleFS.open("/server.crt"); - if (fp) { + if (fp) + { server_cert = fp.readString(); - } else { + } + else + { Serial.println("server.pem not found, SSL not available"); return; } fp.close(); File fp2 = LittleFS.open("/server.key"); - if (fp2) { + if (fp2) + { server_key = fp2.readString(); - } else { + } + else + { Serial.println("server.key not found, SSL not available"); return; } fp2.close(); - //start our server - server.listen(443, server_cert.c_str(), server_key.c_str()); + // start our server + server.setCertificate(server_cert.c_str(), server_key.c_str()); - //our index - server.on("/", HTTP_GET, [](PsychicRequest *request) - { - return request->reply(200, "text/html", htmlContent); - }); + // our index + server.on("/", HTTP_GET, [](PsychicRequest* request) + { return response->send(200, "text/html", htmlContent); }); - //serve static files from LittleFS/www on / + // serve static files from LittleFS/www on / server.serveStatic("/", LittleFS, "/www/"); - //a websocket echo server - websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) { - request->reply(frame); - return ESP_OK; - }); + // a websocket echo server + websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) + { + response->send(frame); + return ESP_OK; }); server.on("/ws", &websocketHandler); - //api - parameters passed in via query eg. /api/endpoint?foo=bar - server.on("/api", HTTP_GET, [](PsychicRequest *request) - { + // api - parameters passed in via query eg. /api/endpoint?foo=bar + server.on("/api", HTTP_GET, [](PsychicRequest* request) + { //create a response object StaticJsonDocument<128> output; output["msg"] = "status"; @@ -224,8 +227,7 @@ void setup() //serialize and return String jsonBuffer; serializeJson(output, jsonBuffer); - return request->reply(200, "application/json", jsonBuffer.c_str()); - }); + return response->send(200, "application/json", jsonBuffer.c_str()); }); } } diff --git a/lib/PsychicHttp/benchmark/websocket-client-test.js b/lib/PsychicHttp/benchmark/websocket-client-test.js index 3021bb0..e46740e 100644 --- a/lib/PsychicHttp/benchmark/websocket-client-test.js +++ b/lib/PsychicHttp/benchmark/websocket-client-test.js @@ -1,8 +1,9 @@ #!/usr/bin/env node +//stress test the client open/close for websockets const WebSocket = require('ws'); -const uri = 'ws://192.168.2.131/ws'; +const uri = 'ws://psychic.local/ws'; async function websocketClient() { console.log(`Starting test`); diff --git a/lib/PsychicHttp/examples/arduino/arduino.ino b/lib/PsychicHttp/examples/arduino/arduino.ino index c0fe060..393ba02 100644 --- a/lib/PsychicHttp/examples/arduino/arduino.ino +++ b/lib/PsychicHttp/examples/arduino/arduino.ino @@ -206,30 +206,25 @@ void setup() //do we want secure or not? if (app_enable_ssl) { - server.listen(443, server_cert.c_str(), server_key.c_str()); + server.setCertificate(server_cert.c_str(), server_key.c_str()); //this creates a 2nd server listening on port 80 and redirects all requests HTTPS PsychicHttpServer *redirectServer = new PsychicHttpServer(); redirectServer->config.ctrl_port = 20424; // just a random port different from the default one - redirectServer->listen(80); - redirectServer->onNotFound([](PsychicRequest *request) { + redirectServer->onNotFound([](PsychicRequest *request, PsychicResponse *response) { String url = "https://" + request->host() + request->url(); - return request->redirect(url.c_str()); + return response->redirect(url.c_str()); }); } - else - server.listen(80); - #else - server.listen(80); #endif //serve static files from LittleFS/www on / only to clients on same wifi network //this is where our /index.html file lives - server.serveStatic("/", LittleFS, "/www/")->setFilter(ON_STA_FILTER); + server.serveStatic("/", LittleFS, "/www/")->addFilter(ON_STA_FILTER); //serve static files from LittleFS/www-ap on / only to clients on SoftAP //this is where our /index.html file lives - server.serveStatic("/", LittleFS, "/www-ap/")->setFilter(ON_AP_FILTER); + server.serveStatic("/", LittleFS, "/www-ap/")->addFilter(ON_AP_FILTER); //serve static files from LittleFS/img on /img //it's more efficient to serve everything from a single www directory, but this is also possible. @@ -240,16 +235,16 @@ void setup() //example callback everytime a connection is opened server.onOpen([](PsychicClient *client) { - Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString()); + Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str()); }); //example callback everytime a connection is closed server.onClose([](PsychicClient *client) { - Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString()); + Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); }); //api - json message passed in as post body - server.on("/api", HTTP_POST, [](PsychicRequest *request) + server.on("/api", HTTP_POST, [](PsychicRequest *request, PsychicResponse *response) { //load our JSON request StaticJsonDocument<1024> json; @@ -272,18 +267,18 @@ void setup() //serialize and return String jsonBuffer; serializeJson(output, jsonBuffer); - return request->reply(200, "application/json", jsonBuffer.c_str()); + return response->send(200, "application/json", jsonBuffer.c_str()); }); //api - parameters passed in via query eg. /api/endpoint?foo=bar - server.on("/ip", HTTP_GET, [](PsychicRequest *request) + server.on("/ip", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response) { String output = "Your IP is: " + request->client()->remoteIP().toString(); - return request->reply(output.c_str()); + return response->send(output.c_str()); }); //api - parameters passed in via query eg. /api/endpoint?foo=bar - server.on("/api", HTTP_GET, [](PsychicRequest *request) + server.on("/api", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response) { //create a response object StaticJsonDocument<128> output; @@ -301,65 +296,64 @@ void setup() //serialize and return String jsonBuffer; serializeJson(output, jsonBuffer); - return request->reply(200, "application/json", jsonBuffer.c_str()); + return response->send(200, "application/json", jsonBuffer.c_str()); }); //how to redirect a request - server.on("/redirect", HTTP_GET, [](PsychicRequest *request) + server.on("/redirect", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response) { - return request->redirect("/alien.png"); + return response->redirect("/alien.png"); }); //how to do basic auth - server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request) + server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response) { if (!request->authenticate(app_user, app_pass)) return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in."); - return request->reply("Auth Basic Success!"); + return response->send("Auth Basic Success!"); }); //how to do digest auth - server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request) + server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response) { if (!request->authenticate(app_user, app_pass)) return request->requestAuthentication(DIGEST_AUTH, app_name, "You must log in."); - return request->reply("Auth Digest Success!"); + return response->send("Auth Digest Success!"); }); //example of getting / setting cookies - server.on("/cookies", HTTP_GET, [](PsychicRequest *request) + server.on("/cookies", HTTP_GET, [](PsychicRequest *request, PsychicResponse *response) { - PsychicResponse response(request); - int counter = 0; - if (request->hasCookie("counter")) + char cookie[14]; + size_t size = 14; + if (request->getCookie("counter", cookie, &size) == ESP_OK) { - counter = std::stoi(request->getCookie("counter").c_str()); + // value is null-terminated. + counter = std::stoi(cookie); counter++; } + sprintf(cookie, "%d", counter); - char cookie[10]; - sprintf(cookie, "%i", counter); - - response.setCookie("counter", cookie); - response.setContent(cookie); - return response.send(); + response->setCookie("counter", cookie); + response->setContent(cookie); + return response->send(); }); //example of getting POST variables - server.on("/post", HTTP_POST, [](PsychicRequest *request) + server.on("/post", HTTP_POST, [](PsychicRequest *request, PsychicResponse *response) { String output; output += "Param 1: " + request->getParam("param1")->value() + "
\n"; output += "Param 2: " + request->getParam("param2")->value() + "
\n"; - return request->reply(output.c_str()); + return response->send(output.c_str()); }); //you can set up a custom 404 handler. - server.onNotFound([](PsychicRequest *request) + server.onNotFound([](PsychicRequest *request, PsychicResponse *response) { - return request->reply(404, "text/html", "Custom 404 Handler"); + return response->send(404, "text/html", "Custom 404 Handler"); }); //handle a very basic upload as post body @@ -393,12 +387,12 @@ void setup() }); //gets called after upload has been handled - uploadHandler->onRequest([](PsychicRequest *request) + uploadHandler->onRequest([](PsychicRequest *request, PsychicResponse *response) { String url = "/" + request->getFilename(); String output = "" + url + ""; - return request->reply(output.c_str()); + return response->send(output.c_str()); }); //wildcard basic file upload - POST to /upload/filename.ext @@ -435,7 +429,7 @@ void setup() }); //gets called after upload has been handled - multipartHandler->onRequest([](PsychicRequest *request) + multipartHandler->onRequest([](PsychicRequest *request, PsychicResponse *response) { PsychicWebParameter *file = request->getParam("file_upload"); @@ -447,7 +441,7 @@ void setup() output += "Param 1: " + request->getParam("param1")->value() + "
\n"; output += "Param 2: " + request->getParam("param2")->value() + "
\n"; - return request->reply(output.c_str()); + return response->send(output.c_str()); }); //wildcard basic file upload - POST to /upload/filename.ext @@ -455,7 +449,7 @@ void setup() //a websocket echo server websocketHandler.onOpen([](PsychicWebSocketClient *client) { - Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->localIP().toString()); + Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str()); client->sendMessage("Hello!"); }); websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) { @@ -463,17 +457,17 @@ void setup() return request->reply(frame); }); websocketHandler.onClose([](PsychicWebSocketClient *client) { - Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->localIP().toString()); + Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); }); server.on("/ws", &websocketHandler); //EventSource server eventSource.onOpen([](PsychicEventSourceClient *client) { - Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->localIP().toString()); + Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str()); client->send("Hello user!", NULL, millis(), 1000); }); eventSource.onClose([](PsychicEventSourceClient *client) { - Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->localIP().toString()); + Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); }); server.on("/events", &eventSource); } @@ -486,10 +480,10 @@ void loop() { if (millis() - lastUpdate > 2000) { - sprintf(output, "Millis: %d\n", millis()); + sprintf(output, "Millis: %lu\n", millis()); websocketHandler.sendAll(output); - sprintf(output, "%d", millis()); + sprintf(output, "%lu", millis()); eventSource.send(output, "millis", millis(), 0); lastUpdate = millis(); diff --git a/lib/PsychicHttp/examples/arduino/arduino_captive_portal/README.md b/lib/PsychicHttp/examples/arduino/arduino_captive_portal/README.md index f965eda..f9bbaae 100644 --- a/lib/PsychicHttp/examples/arduino/arduino_captive_portal/README.md +++ b/lib/PsychicHttp/examples/arduino/arduino_captive_portal/README.md @@ -28,7 +28,7 @@ public: esp_err_t handleRequest(PsychicRequest *request) { //PsychicFileResponse response(request, LittleFS, "/captiveportal.html"); // uncomment : for captive portal page, if any, eg "captiveportal.html" //return response.send(); // uncomment : return captive portal page - return request->reply(200,"text/html","Welcome to captive portal !"); // simple text, comment if captive portal page + return response->send(200,"text/html","Welcome to captive portal !"); // simple text, comment if captive portal page } }; CaptiveRequestHandler *captivehandler=NULL; // handler for captive portal diff --git a/lib/PsychicHttp/examples/arduino/arduino_captive_portal/arduino_captive_portal.ino b/lib/PsychicHttp/examples/arduino/arduino_captive_portal/arduino_captive_portal.ino index fbe83a3..e5f8d44 100644 --- a/lib/PsychicHttp/examples/arduino/arduino_captive_portal/arduino_captive_portal.ino +++ b/lib/PsychicHttp/examples/arduino/arduino_captive_portal/arduino_captive_portal.ino @@ -14,7 +14,7 @@ #include #include -char* TAG = "CAPTPORT"; +#define TAG "CAPTPORT" // captiveportal // credits https://github.com/me-no-dev/ESPAsyncWebServer/blob/master/examples/CaptivePortal/CaptivePortal.ino @@ -29,10 +29,10 @@ public: // ... if needed some tests ... return(false); return true; // activate captive portal } - esp_err_t handleRequest(PsychicRequest *request) { + esp_err_t handleRequest(PsychicRequest *request, PsychicResponse *response) { //PsychicFileResponse response(request, LittleFS, "/captiveportal.html"); // uncomment : for captive portal page, if any, eg "captiveportal.html" //return response.send(); // uncomment : return captive portal page - return request->reply(200,"text/html","Welcome to captive portal !"); // simple text, comment if captive portal page + return response->send(200,"text/html","Welcome to captive portal !"); // simple text, comment if captive portal page } }; CaptiveRequestHandler *captivehandler=NULL; // handler for captive portal @@ -128,7 +128,6 @@ void setup() { //setup server config stuff here server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls) - server.listen(80); DefaultHeaders::Instance().addHeader("Server", "PsychicHttp"); diff --git a/lib/PsychicHttp/examples/arduino/arduino_ota/arduino_ota.ino b/lib/PsychicHttp/examples/arduino/arduino_ota/arduino_ota.ino index 47f611f..ca5360c 100644 --- a/lib/PsychicHttp/examples/arduino/arduino_ota/arduino_ota.ino +++ b/lib/PsychicHttp/examples/arduino/arduino_ota/arduino_ota.ino @@ -8,7 +8,7 @@ CONDITIONS OF ANY KIND, either express or implied. */ -char *TAG = "OTA"; // ESP_LOG tag +#define TAG "OTA" // ESP_LOG tag // PsychicHttp #include @@ -100,7 +100,6 @@ void setup() //setup server config stuff here server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls) - server.listen(80); DefaultHeaders::Instance().addHeader("Server", "PsychicHttp"); @@ -109,8 +108,8 @@ void setup() //you can set up a custom 404 handler. // curl -i http://psychic.local/404 - server.onNotFound([](PsychicRequest *request) { - return request->reply(404, "text/html", "Custom 404 Handler"); + server.onNotFound([](PsychicRequest *request, PsychicResponse *response) { + return response->send(404, "text/html", "Custom 404 Handler"); }); // OTA @@ -177,34 +176,34 @@ void setup() } }); // end onUpload - updateHandler->onRequest([](PsychicRequest *request) { // triggered when update is completed (either OK or KO) and returns request's response (important) + updateHandler->onRequest([](PsychicRequest *request, PsychicResponse *response) { // triggered when update is completed (either OK or KO) and returns request's response (important) String result; // request result // code below is executed when update is finished if (!Update.hasError()) { // update is OK ESP_LOGI(TAG,"Update code or data OK Update.errorString() %s", Update.errorString()); result = "Update done for file."; - return request->reply(200,"text/html",result.c_str()); + return response->send(200,"text/html",result.c_str()); // ESP.restart(); // restart ESP if needed } // end update is OK else { // update is KO, send request with pretty print error result = " Update.errorString() " + String(Update.errorString()); ESP_LOGE(TAG,"ERROR : error %s",result.c_str()); - return request->reply(500, "text/html", result.c_str()); + return response->send(500, "text/html", result.c_str()); } // end update is KO }); - server.on("/update", HTTP_GET, [](PsychicRequest*request){ - PsychicFileResponse response(request, LittleFS, "/update.html"); + server.on("/update", HTTP_GET, [](PsychicRequest*request, PsychicResponse *res){ + PsychicFileResponse response(res, LittleFS, "/update.html"); return response.send(); }); server.on("/update", HTTP_POST, updateHandler); - server.on("/restart", HTTP_POST, [](PsychicRequest *request) { + server.on("/restart", HTTP_POST, [](PsychicRequest *request, PsychicResponse *response) { String output = "Restarting ..."; ESP_LOGI(TAG,"%s",output.c_str()); esprestart=true; - return request->reply(output.c_str()); + return response->send(output.c_str()); }); } // end onRequest diff --git a/lib/PsychicHttp/examples/esp-idf/main/main.cpp b/lib/PsychicHttp/examples/esp-idf/main/main.cpp index 21c18e4..97652ae 100644 --- a/lib/PsychicHttp/examples/esp-idf/main/main.cpp +++ b/lib/PsychicHttp/examples/esp-idf/main/main.cpp @@ -9,66 +9,69 @@ */ /********************************************************************************************** -* Note: this demo relies on the following libraries (Install via Library Manager) -* ArduinoJson UrlEncode -**********************************************************************************************/ + * Note: this demo relies on the following libraries (Install via Library Manager) + * ArduinoJson UrlEncode + **********************************************************************************************/ /********************************************************************************************** -* Note: this demo relies on various files to be uploaded on the LittleFS partition -* Follow instructions here: https://randomnerdtutorials.com/esp32-littlefs-arduino-ide/ -**********************************************************************************************/ + * Note: this demo relies on various files to be uploaded on the LittleFS partition + * Follow instructions here: https://randomnerdtutorials.com/esp32-littlefs-arduino-ide/ + **********************************************************************************************/ +#include "secret.h" #include -#include -#include #include #include -#include "secret.h" +#include #include -#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE //set this to y in menuconfig to enable SSL -#include +#include +#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE // set this to y in menuconfig to enable SSL + #include #endif #ifndef WIFI_SSID #error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there." #endif -//Enter your WIFI credentials in secret.h -const char *ssid = WIFI_SSID; -const char *password = WIFI_PASS; +// Enter your WIFI credentials in secret.h +const char* ssid = WIFI_SSID; +const char* password = WIFI_PASS; // Set your SoftAP credentials -const char *softap_ssid = "PsychicHttp"; -const char *softap_password = ""; -IPAddress softap_ip(10, 0, 0, 1); +const char* softap_ssid = "PsychicHttp"; +const char* softap_password = ""; +IPAddress softap_ip(10, 0, 0, 1); -//credentials for the /auth-basic and /auth-digest examples -const char *app_user = "admin"; -const char *app_pass = "admin"; -const char *app_name = "Your App"; +// credentials for the /auth-basic and /auth-digest examples +const char* app_user = "admin"; +const char* app_pass = "admin"; +const char* app_name = "Your App"; -//hostname for mdns (psychic.local) -const char *local_hostname = "psychic"; +AuthenticationMiddleware basicAuth; +AuthenticationMiddleware digestAuth; -//#define CONFIG_ESP_HTTPS_SERVER_ENABLE to enable ssl +// hostname for mdns (psychic.local) +const char* local_hostname = "psychic"; + +// #define CONFIG_ESP_HTTPS_SERVER_ENABLE to enable ssl #ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE - bool app_enable_ssl = true; - String server_cert; - String server_key; +bool app_enable_ssl = true; +String server_cert; +String server_key; #endif -//our main server object +// our main server object #ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE - PsychicHttpsServer server; +PsychicHttpsServer server; #else - PsychicHttpServer server; +PsychicHttpServer server; #endif PsychicWebSocketHandler websocketHandler; PsychicEventSource eventSource; bool connectToWifi() { - //dual client and AP mode + // dual client and AP mode WiFi.mode(WIFI_AP_STA); // Configure SoftAP @@ -92,10 +95,8 @@ bool connectToWifi() int numberOfTries = 20; // Wait for the WiFi event - while (true) - { - switch (WiFi.status()) - { + while (true) { + switch (WiFi.status()) { case WL_NO_SSID_AVAIL: Serial.println("[WiFi] SSID not found"); break; @@ -125,15 +126,12 @@ bool connectToWifi() } delay(tryDelay); - if (numberOfTries <= 0) - { + if (numberOfTries <= 0) { Serial.print("[WiFi] Failed to connect to WiFi!"); // Use disconnect function to force stop trying to connect WiFi.disconnect(); return false; - } - else - { + } else { numberOfTries--; } } @@ -148,111 +146,102 @@ void setup() // We start by connecting to a WiFi network // To debug, please enable Core Debug Level to Verbose - if (connectToWifi()) - { - //set up our esp32 to listen on the local_hostname.local domain + if (connectToWifi()) { + // set up our esp32 to listen on the local_hostname.local domain if (!MDNS.begin(local_hostname)) { Serial.println("Error starting mDNS"); return; } MDNS.addService("http", "tcp", 80); - if(!LittleFS.begin()) - { + if (!LittleFS.begin()) { Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode"); return; } - //look up our keys? - #ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE - if (app_enable_ssl) - { - File fp = LittleFS.open("/server.crt"); - if (fp) - { - server_cert = fp.readString(); +// look up our keys? +#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE + if (app_enable_ssl) { + File fp = LittleFS.open("/server.crt"); + if (fp) { + server_cert = fp.readString(); - // Serial.println("Server Cert:"); - // Serial.println(server_cert); - } - else - { - Serial.println("server.pem not found, SSL not available"); - app_enable_ssl = false; - } - fp.close(); - - File fp2 = LittleFS.open("/server.key"); - if (fp2) - { - server_key = fp2.readString(); - - // Serial.println("Server Key:"); - // Serial.println(server_key); - } - else - { - Serial.println("server.key not found, SSL not available"); - app_enable_ssl = false; - } - fp2.close(); + // Serial.println("Server Cert:"); + // Serial.println(server_cert); + } else { + Serial.println("server.pem not found, SSL not available"); + app_enable_ssl = false; } - #endif + fp.close(); - //setup server config stuff here - server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls) + File fp2 = LittleFS.open("/server.key"); + if (fp2) { + server_key = fp2.readString(); - #ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE - server.ssl_config.httpd.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls) + // Serial.println("Server Key:"); + // Serial.println(server_key); + } else { + Serial.println("server.key not found, SSL not available"); + app_enable_ssl = false; + } + fp2.close(); + } +#endif - //do we want secure or not? - if (app_enable_ssl) - { - server.listen(443, server_cert.c_str(), server_key.c_str()); - - //this creates a 2nd server listening on port 80 and redirects all requests HTTPS - PsychicHttpServer *redirectServer = new PsychicHttpServer(); - redirectServer->config.ctrl_port = 20424; // just a random port different from the default one - redirectServer->listen(80); - redirectServer->onNotFound([](PsychicRequest *request) { + // setup server config stuff here + server.config.max_uri_handlers = 20; // maximum number of uri handlers (.on() calls) + +#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE + server.ssl_config.httpd.max_uri_handlers = 20; // maximum number of uri handlers (.on() calls) + + // do we want secure or not? + if (app_enable_ssl) { + server.setCertificate(server_cert.c_str(), server_key.c_str()); + + // this creates a 2nd server listening on port 80 and redirects all requests HTTPS + PsychicHttpServer* redirectServer = new PsychicHttpServer(); + redirectServer->config.ctrl_port = 20424; // just a random port different from the default one + redirectServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) { String url = "https://" + request->host() + request->url(); - return request->redirect(url.c_str()); - }); - } - else - server.listen(80); - #else - server.listen(80); - #endif + return response->redirect(url.c_str()); }); + } +#endif - //serve static files from LittleFS/www on / only to clients on same wifi network - //this is where our /index.html file lives - server.serveStatic("/", LittleFS, "/www/")->setFilter(ON_STA_FILTER); + basicAuth.setUsername(app_user); + basicAuth.setPassword(app_pass); + basicAuth.setRealm(app_name); + basicAuth.setAuthMethod(HTTPAuthMethod::BASIC_AUTH); + basicAuth.setAuthFailureMessage("You must log in."); - //serve static files from LittleFS/www-ap on / only to clients on SoftAP - //this is where our /index.html file lives - server.serveStatic("/", LittleFS, "/www-ap/")->setFilter(ON_AP_FILTER); + digestAuth.setUsername(app_user); + digestAuth.setPassword(app_pass); + digestAuth.setRealm(app_name); + digestAuth.setAuthMethod(HTTPAuthMethod::DIGEST_AUTH); + digestAuth.setAuthFailureMessage("You must log in."); - //serve static files from LittleFS/img on /img - //it's more efficient to serve everything from a single www directory, but this is also possible. + // serve static files from LittleFS/www on / only to clients on same wifi network + // this is where our /index.html file lives + server.serveStatic("/", LittleFS, "/www/")->addFilter(ON_STA_FILTER); + + // serve static files from LittleFS/www-ap on / only to clients on SoftAP + // this is where our /index.html file lives + server.serveStatic("/", LittleFS, "/www-ap/")->addFilter(ON_AP_FILTER); + + // serve static files from LittleFS/img on /img + // it's more efficient to serve everything from a single www directory, but this is also possible. server.serveStatic("/img", LittleFS, "/img/"); - //you can also serve single files + // you can also serve single files server.serveStatic("/myfile.txt", LittleFS, "/custom.txt"); - //example callback everytime a connection is opened - server.onOpen([](PsychicClient *client) { - Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str()); - }); + // example callback everytime a connection is opened + server.onOpen([](PsychicClient* client) { Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str()); }); - //example callback everytime a connection is closed - server.onClose([](PsychicClient *client) { - Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); - }); + // example callback everytime a connection is closed + server.onClose([](PsychicClient* client) { Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); }); - //api - json message passed in as post body - server.on("/api", HTTP_POST, [](PsychicRequest *request) - { + // api - json message passed in as post body + server.on("/api", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) { //load our JSON request JsonDocument json; String body = request->body(); @@ -284,19 +273,15 @@ void setup() //serialize and return String jsonBuffer; serializeJson(output, jsonBuffer); - return request->reply(200, "application/json", jsonBuffer.c_str()); - }); + return response->send(200, "application/json", jsonBuffer.c_str()); }); - //api - parameters passed in via query eg. /api/endpoint?foo=bar - server.on("/ip", HTTP_GET, [](PsychicRequest *request) - { + // api - parameters passed in via query eg. /api/endpoint?foo=bar + server.on("/ip", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { String output = "Your IP is: " + request->client()->remoteIP().toString(); - return request->reply(output.c_str()); - }); + return response->send(output.c_str()); }); - //api - parameters passed in via query eg. /api/endpoint?foo=bar - server.on("/api", HTTP_GET, [](PsychicRequest *request) - { + // api - parameters passed in via query eg. /api/endpoint?foo=bar + server.on("/api", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { //create a response object JsonDocument output; output["msg"] = "status"; @@ -313,70 +298,48 @@ void setup() //serialize and return String jsonBuffer; serializeJson(output, jsonBuffer); - return request->reply(200, "application/json", jsonBuffer.c_str()); - }); + return response->send(200, "application/json", jsonBuffer.c_str()); }); - //how to redirect a request - server.on("/redirect", HTTP_GET, [](PsychicRequest *request) - { - return request->redirect("/alien.png"); - }); + // how to redirect a request + server.on("/redirect", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->redirect("/alien.png"); }); - //how to do basic auth - server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request) - { - if (!request->authenticate(app_user, app_pass)) - return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in."); - return request->reply("Auth Basic Success!"); - }); + // how to do basic auth + server.on("/auth-basic", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->send("Auth Basic Success!"); })->addMiddleware(&basicAuth); - //how to do digest auth - server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request) - { - if (!request->authenticate(app_user, app_pass)) - return request->requestAuthentication(DIGEST_AUTH, app_name, "You must log in."); - return request->reply("Auth Digest Success!"); - }); - - //example of getting / setting cookies - server.on("/cookies", HTTP_GET, [](PsychicRequest *request) - { - PsychicResponse response(request); + // how to do digest auth + server.on("/auth-digest", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->send("Auth Digest Success!"); })->addMiddleware(&digestAuth); + // example of getting / setting cookies + server.on("/cookies", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { int counter = 0; - if (request->hasCookie("counter")) + char cookie[14]; + size_t size = 14; + if (request->getCookie("counter", cookie, &size) == ESP_OK) { - counter = std::stoi(request->getCookie("counter").c_str()); + // value is null-terminated. + counter = std::stoi(cookie); counter++; } + sprintf(cookie, "%d", counter); - char cookie[12]; - sprintf(cookie, "%i", counter); + response->setCookie("counter", cookie); + response->setContent(cookie); + return response->send(); }); - response.setCookie("counter", cookie); - response.setContent(cookie); - return response.send(); - }); - - //example of getting POST variables - server.on("/post", HTTP_POST, [](PsychicRequest *request) - { + // example of getting POST variables + server.on("/post", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) { String output; output += "Param 1: " + request->getParam("param1")->value() + "
\n"; output += "Param 2: " + request->getParam("param2")->value() + "
\n"; - return request->reply(output.c_str()); - }); + return response->send(output.c_str()); }); - //you can set up a custom 404 handler. - server.onNotFound([](PsychicRequest *request) - { - return request->reply(404, "text/html", "Custom 404 Handler"); - }); + // you can set up a custom 404 handler. + server.onNotFound([](PsychicRequest* request, PsychicResponse* response) { return response->send(404, "text/html", "Custom 404 Handler"); }); - //handle a very basic upload as post body - PsychicUploadHandler *uploadHandler = new PsychicUploadHandler(); - uploadHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { + // handle a very basic upload as post body + PsychicUploadHandler* uploadHandler = new PsychicUploadHandler(); + uploadHandler->onUpload([](PsychicRequest* request, const String& filename, uint64_t index, uint8_t* data, size_t len, bool last) { File file; String path = "/www/" + filename; @@ -401,24 +364,21 @@ void setup() return ESP_FAIL; } - return ESP_OK; - }); + return ESP_OK; }); - //gets called after upload has been handled - uploadHandler->onRequest([](PsychicRequest *request) - { + // gets called after upload has been handled + uploadHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) { String url = "/" + request->getFilename(); String output = "" + url + ""; - return request->reply(output.c_str()); - }); + return response->send(output.c_str()); }); - //wildcard basic file upload - POST to /upload/filename.ext + // wildcard basic file upload - POST to /upload/filename.ext server.on("/upload/*", HTTP_POST, uploadHandler); - //a little bit more complicated multipart form - PsychicUploadHandler *multipartHandler = new PsychicUploadHandler(); - multipartHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { + // a little bit more complicated multipart form + PsychicUploadHandler* multipartHandler = new PsychicUploadHandler(); + multipartHandler->onUpload([](PsychicRequest* request, const String& filename, uint64_t index, uint8_t* data, size_t len, bool last) { File file; String path = "/www/" + filename; @@ -443,12 +403,10 @@ void setup() return ESP_FAIL; } - return ESP_OK; - }); + return ESP_OK; }); - //gets called after upload has been handled - multipartHandler->onRequest([](PsychicRequest *request) - { + // gets called after upload has been handled + multipartHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) { PsychicWebParameter *file = request->getParam("file_upload"); String url = "/" + file->value(); @@ -459,34 +417,26 @@ void setup() output += "Param 1: " + request->getParam("param1")->value() + "
\n"; output += "Param 2: " + request->getParam("param2")->value() + "
\n"; - return request->reply(output.c_str()); - }); + return response->send(output.c_str()); }); - //wildcard basic file upload - POST to /upload/filename.ext + // wildcard basic file upload - POST to /upload/filename.ext server.on("/multipart", HTTP_POST, multipartHandler); - //a websocket echo server - websocketHandler.onOpen([](PsychicWebSocketClient *client) { + // a websocket echo server + websocketHandler.onOpen([](PsychicWebSocketClient* client) { Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str()); - client->sendMessage("Hello!"); - }); - websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) { + client->sendMessage("Hello!"); }); + websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) { Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload); - return request->reply(frame); - }); - websocketHandler.onClose([](PsychicWebSocketClient *client) { - Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); - }); + return request->reply(frame); }); + websocketHandler.onClose([](PsychicWebSocketClient* client) { Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); }); server.on("/ws", &websocketHandler); - //EventSource server - eventSource.onOpen([](PsychicEventSourceClient *client) { + // EventSource server + eventSource.onOpen([](PsychicEventSourceClient* client) { Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str()); - client->send("Hello user!", NULL, millis(), 1000); - }); - eventSource.onClose([](PsychicEventSourceClient *client) { - Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); - }); + client->send("Hello user!", NULL, millis(), 1000); }); + eventSource.onClose([](PsychicEventSourceClient* client) { Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); }); server.on("/events", &eventSource); } } @@ -496,8 +446,7 @@ char output[60]; void loop() { - if (millis() - lastUpdate > 2000) - { + if (millis() - lastUpdate > 2000) { sprintf(output, "Millis: %lu\n", millis()); websocketHandler.sendAll(output); diff --git a/lib/PsychicHttp/examples/old/esp_ota_http_server/.gitignore b/lib/PsychicHttp/examples/old/esp_ota_http_server/.gitignore deleted file mode 100644 index be2b7e8..0000000 --- a/lib/PsychicHttp/examples/old/esp_ota_http_server/.gitignore +++ /dev/null @@ -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 \ No newline at end of file diff --git a/lib/PsychicHttp/examples/old/esp_ota_http_server/include/README b/lib/PsychicHttp/examples/old/esp_ota_http_server/include/README deleted file mode 100644 index 194dcd4..0000000 --- a/lib/PsychicHttp/examples/old/esp_ota_http_server/include/README +++ /dev/null @@ -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 diff --git a/lib/PsychicHttp/examples/old/esp_ota_http_server/lib/README b/lib/PsychicHttp/examples/old/esp_ota_http_server/lib/README deleted file mode 100644 index 6debab1..0000000 --- a/lib/PsychicHttp/examples/old/esp_ota_http_server/lib/README +++ /dev/null @@ -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 -#include - -int main (void) -{ - ... -} - -``` - -PlatformIO Library Dependency Finder will find automatically dependent -libraries scanning project source files. - -More information about PlatformIO Library Dependency Finder -- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/PsychicHttp/examples/old/esp_ota_http_server/platformio.ini b/lib/PsychicHttp/examples/old/esp_ota_http_server/platformio.ini deleted file mode 100644 index 8a0c120..0000000 --- a/lib/PsychicHttp/examples/old/esp_ota_http_server/platformio.ini +++ /dev/null @@ -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 \ No newline at end of file diff --git a/lib/PsychicHttp/examples/old/esp_ota_http_server/src/esp_ota_http_server.cpp b/lib/PsychicHttp/examples/old/esp_ota_http_server/src/esp_ota_http_server.cpp deleted file mode 100644 index 9d9d35b..0000000 --- a/lib/PsychicHttp/examples/old/esp_ota_http_server/src/esp_ota_http_server.cpp +++ /dev/null @@ -1,229 +0,0 @@ -// -// A simple server implementation showing how to: -// * serve static messages -// * read GET and POST parameters -// * handle missing pages / 404s -// - -#include -#include -#include -#include - -#ifdef ESP32 -#include -#define START_ESP_WIFI -#elif defined(ESP8266) -#include -#define START_ESP_WIFI -#else -#error Platform not supported -#endif - -MongooseHttpServer server; - -const char *ssid = "wifi"; -const char *password = "password"; - -const char *server_pem = -"-----BEGIN CERTIFICATE-----\r\n" -"MIIDDjCCAfagAwIBAgIBBDANBgkqhkiG9w0BAQsFADA/MRkwFwYDVQQDDBB0ZXN0\r\n" -"LmNlc2FudGEuY29tMRAwDgYDVQQKDAdDZXNhbnRhMRAwDgYDVQQLDAd0ZXN0aW5n\r\n" -"MB4XDTE2MTExMzEzMTgwMVoXDTI2MDgxMzEzMTgwMVowFDESMBAGA1UEAwwJbG9j\r\n" -"YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAro8CW1X0xaGm\r\n" -"GkDaMxKbXWA5Lw+seA61tioGrSIQzuqLYeJoFnwVgF0jB5PTj+3EiGMBcA/mh73V\r\n" -"AthTFmJBxj+agIp7/cvUBpgfLClmSYL2fZi6Fodz+f9mcry3XRw7O6vlamtWfTX8\r\n" -"TAmMSR6PXVBHLgjs5pDOFFmrNAsM5sLYU1/1MFvE2Z9InTI5G437IE1WchRSbpYd\r\n" -"HchC39XzpDGoInZB1a3OhcHm+xUtLpMJ0G0oE5VFEynZreZoEIY4JxspQ7LPsay9\r\n" -"fx3Tlk09gEMQgVCeCNiQwUxZdtLau2x61LNcdZCKN7FbFLJszv1U2uguELsTmi7E\r\n" -"6pHrTziosQIDAQABo0AwPjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIDqDATBgNVHSUE\r\n" -"DDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB\r\n" -"AQBUw0hbTcT6crzODO4QAXU7z4Xxn0LkxbXEsoThG1QCVgMc4Bhpx8gyz5CLyHYz\r\n" -"AiJOBFEeV0XEqoGTNMMFelR3Q5Tg9y1TYO3qwwAWxe6/brVzpts6NiG1uEMBnBFg\r\n" -"oN1x3I9x4NpOxU5MU1dlIxvKs5HQCoNJ8D0SqOX9BV/pZqwEgiCbuWDWQAlxkFpn\r\n" -"iLonlkVI5hTuybCSBsa9FEI9M6JJn9LZmlH90FYHeS4t6P8eOJCeekHL0jUG4Iae\r\n" -"DMP12h8Sd0yxIKmmZ+Q/p/D/BkuHf5Idv3hgyLkZ4mNznjK49wHaYM+BgBoL3Zeg\r\n" -"gJ2sWjUlokrbHswSBLLbUJIF\r\n" -"-----END CERTIFICATE-----\r\n"; - -const char *server_key = -"-----BEGIN PRIVATE KEY-----\r\n" -"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCujwJbVfTFoaYa\r\n" -"QNozEptdYDkvD6x4DrW2KgatIhDO6oth4mgWfBWAXSMHk9OP7cSIYwFwD+aHvdUC\r\n" -"2FMWYkHGP5qAinv9y9QGmB8sKWZJgvZ9mLoWh3P5/2ZyvLddHDs7q+Vqa1Z9NfxM\r\n" -"CYxJHo9dUEcuCOzmkM4UWas0CwzmwthTX/UwW8TZn0idMjkbjfsgTVZyFFJulh0d\r\n" -"yELf1fOkMagidkHVrc6Fweb7FS0ukwnQbSgTlUUTKdmt5mgQhjgnGylDss+xrL1/\r\n" -"HdOWTT2AQxCBUJ4I2JDBTFl20tq7bHrUs1x1kIo3sVsUsmzO/VTa6C4QuxOaLsTq\r\n" -"ketPOKixAgMBAAECggEAI+uNwpnHirue4Jwjyoqzqd1ZJxQEm5f7UIcJZKsz5kBh\r\n" -"ej0KykWybv27bZ2/1UhKPv6QlyzOdXRc1v8I6fxCKLeB5Z2Zsjo1YT4AfCfwwoPO\r\n" -"kT3SXTx2YyVpQYcP/HsIvVi8FtALtixbxJHaall9iugwHYr8pN17arihAE6d0wZC\r\n" -"JXtXRjUWwjKzXP8FoH4KhyadhHbDwIbbJe3cyLfdvp54Gr0YHha0JcOxYgDYNya4\r\n" -"OKxlCluI+hPF31iNzOmFLQVrdYynyPcR6vY5XOiANKE2iNbqCzRb54CvW9WMqObX\r\n" -"RD9t3DMOxGsbVNIwyzZndWy13HoQMGnrHfnGak9ueQKBgQDiVtOqYfLnUnTxvJ/b\r\n" -"qlQZr2ZmsYPZztxlP+DSqZGPD+WtGSo9+rozWfzjTv3KGIDLvf+GFVmjVHwlLQfd\r\n" -"u7eTemWHFc4HK68wruzPO/FdyVpQ4w9v3Usg+ll4a/PDEId0fDMjAr6kk4LC6t8y\r\n" -"9fJR0HjOz57jVnlrDt3v50G8BwKBgQDFbw+jRiUxXnBbDyXZLi+I4iGBGdC+CbaJ\r\n" -"CmsM6/TsOFc+GRsPwQF1gCGqdaURw76noIVKZJOSc8I+yiwU6izyh/xaju5JiWQd\r\n" -"kwbU1j4DE6GnxmT3ARmB7VvCxjaEZEAtICWs1QTKRz7PcTV8yr7Ng1A3VIy+NSpo\r\n" -"LFMMmk83hwKBgQDVCEwpLg/mUeHoNVVw95w4oLKNLb+gHeerFLiTDy8FrDzM88ai\r\n" -"l37yHly7xflxYia3nZkHpsi7xiUjCINC3BApKyasQoWskh1OgRY653yCfaYYQ96f\r\n" -"t3WjEH9trI2+p6wWo1+uMEMnu/9zXoW9/WeaQdGzNg+igh29+jxCNTPVuQKBgGV4\r\n" -"CN9vI5pV4QTLqjYOSJvfLDz/mYqxz0BrPE1tz3jAFAZ0PLZCCY/sBGFpCScyJQBd\r\n" -"vWNYgYeZOtGuci1llSgov4eDQfBFTlDsyWwFl+VY55IkoqtXw1ZFOQ3HdSlhpKIM\r\n" -"jZBgApA7QYq3sjeqs5lHzahCKftvs5XKgfxOKjxtAoGBALdnYe6xkDvGLvI51Yr+\r\n" -"Dy0TNcB5W84SxUKvM7DVEomy1QPB57ZpyQaoBq7adOz0pWJXfp7qo4950ZOhBGH1\r\n" -"hKbZ6c4ggwVJy2j49EgMok5NGCKvPAtabbR6H8Mz8DW9aXURxhWJvij+Qw1fWK4b\r\n" -"7G/qUI9iE5iUU7MkIcLIbTf/\r\n" -"-----END PRIVATE KEY-----\r\n"; - -const char* server_index = -"" -"
" - "" - "" -"
" -"
progress: 0%
" -""; - -#include - -bool updateCompleted = false; - -static void updateError(MongooseHttpServerRequest *request) -{ - MongooseHttpServerResponseStream *resp = request->beginResponseStream(); - resp->setCode(500); - resp->setContentType("text/plain"); - resp->printf("Error: %d", Update.getError()); - request->send(resp); - - // Anoyingly this uses Stream rather than Print... - Update.printError(Serial); -} - -void setup() -{ - Serial.begin(115200); - -#ifdef START_ESP_WIFI - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - if (WiFi.waitForConnectResult() != WL_CONNECTED) - { - Serial.printf("WiFi Failed!\n"); - return; - } - - Serial.print("IP Address: "); - Serial.println(WiFi.localIP()); - Serial.print("Hostname: "); -#ifdef ESP32 - Serial.println(WiFi.getHostname()); -#elif defined(ESP8266) - Serial.println(WiFi.hostname()); -#endif -#endif - - Mongoose.begin(); - -#ifdef SIMPLE_SERVER_SECURE - if(false == server.begin(443, server_pem, server_key)) { - Serial.print("Failed to start server"); - return; - } -#else - server.begin(80); -#endif - - server.on("/$", HTTP_GET, [](MongooseHttpServerRequest *request) { -#if defined(ADMIN_USER) && defined(ADMIN_PASS) && defined(ADMIN_REALM) - if(false == request->authenticate(ADMIN_USER, ADMIN_PASS)) { - request->requestAuthentication(ADMIN_REALM); - return; - } -#endif - - request->send(200, "text/html", server_index); - }); - - server.on("/update$", HTTP_POST)-> - onRequest([](MongooseHttpServerRequest *request) { -#if defined(ADMIN_USER) && defined(ADMIN_PASS) && defined(ADMIN_REALM) - if(false == request->authenticate(ADMIN_USER, ADMIN_PASS)) { - request->requestAuthentication(ADMIN_REALM); - return; - } -#endif - updateCompleted = false; - })-> - onUpload([](MongooseHttpServerRequest *request, int ev, MongooseString filename, uint64_t index, uint8_t *data, size_t len) - { - if(MG_EV_HTTP_PART_BEGIN == ev) { - Serial.printf("Update Start: %s\n", filename.c_str()); - - if (!Update.begin()) { //start with max available size - updateError(request); - } - } - - if(!Update.hasError()) - { - Serial.printf("Update Writing %llu\n", index); - if(Update.write(data, len) != len) { - updateError(request); - } - } - - if(MG_EV_HTTP_PART_END == ev) { - Serial.println("Data finished"); - if(Update.end(true)) { - Serial.printf("Update Success: %lluB\n", index+len); - request->send(200, "text/plain", "OK"); - updateCompleted = true; - } else { - updateError(request); - } - } - - return len; - })-> - onClose([](MongooseHttpServerRequest *request) - { - if(updateCompleted) { - ESP.restart(); - } - }); -} - -void loop() -{ - Mongoose.poll(1000); -} diff --git a/lib/PsychicHttp/examples/old/esp_ota_http_server/test/README b/lib/PsychicHttp/examples/old/esp_ota_http_server/test/README deleted file mode 100644 index df5066e..0000000 --- a/lib/PsychicHttp/examples/old/esp_ota_http_server/test/README +++ /dev/null @@ -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 diff --git a/lib/PsychicHttp/examples/old/simple_http_server/.gitignore b/lib/PsychicHttp/examples/old/simple_http_server/.gitignore deleted file mode 100644 index be2b7e8..0000000 --- a/lib/PsychicHttp/examples/old/simple_http_server/.gitignore +++ /dev/null @@ -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 \ No newline at end of file diff --git a/lib/PsychicHttp/examples/old/simple_http_server/include/README b/lib/PsychicHttp/examples/old/simple_http_server/include/README deleted file mode 100644 index 194dcd4..0000000 --- a/lib/PsychicHttp/examples/old/simple_http_server/include/README +++ /dev/null @@ -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 diff --git a/lib/PsychicHttp/examples/old/simple_http_server/lib/README b/lib/PsychicHttp/examples/old/simple_http_server/lib/README deleted file mode 100644 index 6debab1..0000000 --- a/lib/PsychicHttp/examples/old/simple_http_server/lib/README +++ /dev/null @@ -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 -#include - -int main (void) -{ - ... -} - -``` - -PlatformIO Library Dependency Finder will find automatically dependent -libraries scanning project source files. - -More information about PlatformIO Library Dependency Finder -- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/PsychicHttp/examples/old/simple_http_server/platformio.ini b/lib/PsychicHttp/examples/old/simple_http_server/platformio.ini deleted file mode 100644 index 5da8a5a..0000000 --- a/lib/PsychicHttp/examples/old/simple_http_server/platformio.ini +++ /dev/null @@ -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 \ No newline at end of file diff --git a/lib/PsychicHttp/examples/old/simple_http_server/src/simple_http_server.cpp b/lib/PsychicHttp/examples/old/simple_http_server/src/simple_http_server.cpp deleted file mode 100644 index ffa1e4d..0000000 --- a/lib/PsychicHttp/examples/old/simple_http_server/src/simple_http_server.cpp +++ /dev/null @@ -1,211 +0,0 @@ -// -// A simple server implementation showing how to: -// * serve static messages -// * read GET and POST parameters -// * handle missing pages / 404s -// - -#include -#include -#include - -#ifdef ESP32 -#include -#define START_ESP_WIFI -#elif defined(ESP8266) -#include -#define START_ESP_WIFI -#else -#error Platform not supported -#endif - -MongooseHttpServer server; - -const char *ssid = "wifi"; -const char *password = "password"; - -const char *PARAM_MESSAGE = "message"; - -const char *server_pem = -"-----BEGIN CERTIFICATE-----\r\n" -"MIIDDjCCAfagAwIBAgIBBDANBgkqhkiG9w0BAQsFADA/MRkwFwYDVQQDDBB0ZXN0\r\n" -"LmNlc2FudGEuY29tMRAwDgYDVQQKDAdDZXNhbnRhMRAwDgYDVQQLDAd0ZXN0aW5n\r\n" -"MB4XDTE2MTExMzEzMTgwMVoXDTI2MDgxMzEzMTgwMVowFDESMBAGA1UEAwwJbG9j\r\n" -"YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAro8CW1X0xaGm\r\n" -"GkDaMxKbXWA5Lw+seA61tioGrSIQzuqLYeJoFnwVgF0jB5PTj+3EiGMBcA/mh73V\r\n" -"AthTFmJBxj+agIp7/cvUBpgfLClmSYL2fZi6Fodz+f9mcry3XRw7O6vlamtWfTX8\r\n" -"TAmMSR6PXVBHLgjs5pDOFFmrNAsM5sLYU1/1MFvE2Z9InTI5G437IE1WchRSbpYd\r\n" -"HchC39XzpDGoInZB1a3OhcHm+xUtLpMJ0G0oE5VFEynZreZoEIY4JxspQ7LPsay9\r\n" -"fx3Tlk09gEMQgVCeCNiQwUxZdtLau2x61LNcdZCKN7FbFLJszv1U2uguELsTmi7E\r\n" -"6pHrTziosQIDAQABo0AwPjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIDqDATBgNVHSUE\r\n" -"DDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB\r\n" -"AQBUw0hbTcT6crzODO4QAXU7z4Xxn0LkxbXEsoThG1QCVgMc4Bhpx8gyz5CLyHYz\r\n" -"AiJOBFEeV0XEqoGTNMMFelR3Q5Tg9y1TYO3qwwAWxe6/brVzpts6NiG1uEMBnBFg\r\n" -"oN1x3I9x4NpOxU5MU1dlIxvKs5HQCoNJ8D0SqOX9BV/pZqwEgiCbuWDWQAlxkFpn\r\n" -"iLonlkVI5hTuybCSBsa9FEI9M6JJn9LZmlH90FYHeS4t6P8eOJCeekHL0jUG4Iae\r\n" -"DMP12h8Sd0yxIKmmZ+Q/p/D/BkuHf5Idv3hgyLkZ4mNznjK49wHaYM+BgBoL3Zeg\r\n" -"gJ2sWjUlokrbHswSBLLbUJIF\r\n" -"-----END CERTIFICATE-----\r\n"; - -const char *server_key = -"-----BEGIN PRIVATE KEY-----\r\n" -"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCujwJbVfTFoaYa\r\n" -"QNozEptdYDkvD6x4DrW2KgatIhDO6oth4mgWfBWAXSMHk9OP7cSIYwFwD+aHvdUC\r\n" -"2FMWYkHGP5qAinv9y9QGmB8sKWZJgvZ9mLoWh3P5/2ZyvLddHDs7q+Vqa1Z9NfxM\r\n" -"CYxJHo9dUEcuCOzmkM4UWas0CwzmwthTX/UwW8TZn0idMjkbjfsgTVZyFFJulh0d\r\n" -"yELf1fOkMagidkHVrc6Fweb7FS0ukwnQbSgTlUUTKdmt5mgQhjgnGylDss+xrL1/\r\n" -"HdOWTT2AQxCBUJ4I2JDBTFl20tq7bHrUs1x1kIo3sVsUsmzO/VTa6C4QuxOaLsTq\r\n" -"ketPOKixAgMBAAECggEAI+uNwpnHirue4Jwjyoqzqd1ZJxQEm5f7UIcJZKsz5kBh\r\n" -"ej0KykWybv27bZ2/1UhKPv6QlyzOdXRc1v8I6fxCKLeB5Z2Zsjo1YT4AfCfwwoPO\r\n" -"kT3SXTx2YyVpQYcP/HsIvVi8FtALtixbxJHaall9iugwHYr8pN17arihAE6d0wZC\r\n" -"JXtXRjUWwjKzXP8FoH4KhyadhHbDwIbbJe3cyLfdvp54Gr0YHha0JcOxYgDYNya4\r\n" -"OKxlCluI+hPF31iNzOmFLQVrdYynyPcR6vY5XOiANKE2iNbqCzRb54CvW9WMqObX\r\n" -"RD9t3DMOxGsbVNIwyzZndWy13HoQMGnrHfnGak9ueQKBgQDiVtOqYfLnUnTxvJ/b\r\n" -"qlQZr2ZmsYPZztxlP+DSqZGPD+WtGSo9+rozWfzjTv3KGIDLvf+GFVmjVHwlLQfd\r\n" -"u7eTemWHFc4HK68wruzPO/FdyVpQ4w9v3Usg+ll4a/PDEId0fDMjAr6kk4LC6t8y\r\n" -"9fJR0HjOz57jVnlrDt3v50G8BwKBgQDFbw+jRiUxXnBbDyXZLi+I4iGBGdC+CbaJ\r\n" -"CmsM6/TsOFc+GRsPwQF1gCGqdaURw76noIVKZJOSc8I+yiwU6izyh/xaju5JiWQd\r\n" -"kwbU1j4DE6GnxmT3ARmB7VvCxjaEZEAtICWs1QTKRz7PcTV8yr7Ng1A3VIy+NSpo\r\n" -"LFMMmk83hwKBgQDVCEwpLg/mUeHoNVVw95w4oLKNLb+gHeerFLiTDy8FrDzM88ai\r\n" -"l37yHly7xflxYia3nZkHpsi7xiUjCINC3BApKyasQoWskh1OgRY653yCfaYYQ96f\r\n" -"t3WjEH9trI2+p6wWo1+uMEMnu/9zXoW9/WeaQdGzNg+igh29+jxCNTPVuQKBgGV4\r\n" -"CN9vI5pV4QTLqjYOSJvfLDz/mYqxz0BrPE1tz3jAFAZ0PLZCCY/sBGFpCScyJQBd\r\n" -"vWNYgYeZOtGuci1llSgov4eDQfBFTlDsyWwFl+VY55IkoqtXw1ZFOQ3HdSlhpKIM\r\n" -"jZBgApA7QYq3sjeqs5lHzahCKftvs5XKgfxOKjxtAoGBALdnYe6xkDvGLvI51Yr+\r\n" -"Dy0TNcB5W84SxUKvM7DVEomy1QPB57ZpyQaoBq7adOz0pWJXfp7qo4950ZOhBGH1\r\n" -"hKbZ6c4ggwVJy2j49EgMok5NGCKvPAtabbR6H8Mz8DW9aXURxhWJvij+Qw1fWK4b\r\n" -"7G/qUI9iE5iUU7MkIcLIbTf/\r\n" -"-----END PRIVATE KEY-----\r\n"; - -static void notFound(MongooseHttpServerRequest *request); - -#include - -void setup() -{ - Serial.begin(115200); - -#ifdef START_ESP_WIFI - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - if (WiFi.waitForConnectResult() != WL_CONNECTED) - { - Serial.printf("WiFi Failed!\n"); - return; - } - - Serial.print("IP Address: "); - Serial.println(WiFi.localIP()); - Serial.print("Hostname: "); -#ifdef ESP32 - Serial.println(WiFi.getHostname()); -#elif defined(ESP8266) - Serial.println(WiFi.hostname()); -#endif -#endif - - Mongoose.begin(); - -#ifdef SIMPLE_SERVER_SECURE - if(false == server.begin(443, server_pem, server_key)) { - Serial.print("Failed to start server"); - return; - } -#else - server.begin(80); -#endif - - server.on("/$", HTTP_GET, [](MongooseHttpServerRequest *request) { - request->send(200, "text/plain", "Hello world"); - }); - - // Send a GET request to /get?message= - server.on("/get$", HTTP_GET, [](MongooseHttpServerRequest *request) { - String message; - if (request->hasParam(PARAM_MESSAGE)) - { - message = request->getParam(PARAM_MESSAGE); - } - else - { - message = "No message sent"; - } - request->send(200, "text/plain", "Hello, GET: " + message); - }); - - // Send a POST request to /post with a form field message set to - server.on("/post$", HTTP_POST, [](MongooseHttpServerRequest *request) { - String message; - if (request->hasParam(PARAM_MESSAGE)) - { - message = request->getParam(PARAM_MESSAGE); - } - else - { - message = "No message sent"; - } - request->send(200, "text/plain", "Hello, POST: " + message); - }); - - // Test the basic response class - server.on("/basic$", HTTP_GET, [](MongooseHttpServerRequest *request) { - MongooseHttpServerResponseBasic *resp = request->beginResponse(); - resp->setCode(200); - resp->setContentType("text/html"); - resp->addHeader("Cache-Control", "max-age=300"); - resp->addHeader("X-hello", "world"); - resp->setContent( - "\n" - "\n" - "Basic Page\n" - "\n" - "\n" - "

Basic Page

\n" - "

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

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

Stream Page

"); - resp->println("

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

"); - resp->println("

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

"); - resp->println(""); - resp->println(""); - - request->send(resp); - }); - - server.onNotFound(notFound); -} - -void loop() -{ - Mongoose.poll(1000); - Serial.printf("Free memory %u\n", ESP.getFreeHeap()); -} - -static void notFound(MongooseHttpServerRequest *request) -{ - request->send(404, "text/plain", "Not found"); -} diff --git a/lib/PsychicHttp/examples/old/simple_http_server/test/README b/lib/PsychicHttp/examples/old/simple_http_server/test/README deleted file mode 100644 index df5066e..0000000 --- a/lib/PsychicHttp/examples/old/simple_http_server/test/README +++ /dev/null @@ -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 diff --git a/lib/PsychicHttp/examples/old/simple_http_server/test/tests.rest b/lib/PsychicHttp/examples/old/simple_http_server/test/tests.rest deleted file mode 100644 index 4072428..0000000 --- a/lib/PsychicHttp/examples/old/simple_http_server/test/tests.rest +++ /dev/null @@ -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 diff --git a/lib/PsychicHttp/examples/old/simplest_web_server_esp/.gitignore b/lib/PsychicHttp/examples/old/simplest_web_server_esp/.gitignore deleted file mode 100644 index be2b7e8..0000000 --- a/lib/PsychicHttp/examples/old/simplest_web_server_esp/.gitignore +++ /dev/null @@ -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 \ No newline at end of file diff --git a/lib/PsychicHttp/examples/old/simplest_web_server_esp/.travis.yml b/lib/PsychicHttp/examples/old/simplest_web_server_esp/.travis.yml deleted file mode 100644 index 7c486f1..0000000 --- a/lib/PsychicHttp/examples/old/simplest_web_server_esp/.travis.yml +++ /dev/null @@ -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 diff --git a/lib/PsychicHttp/examples/old/simplest_web_server_esp/include/README b/lib/PsychicHttp/examples/old/simplest_web_server_esp/include/README deleted file mode 100644 index 194dcd4..0000000 --- a/lib/PsychicHttp/examples/old/simplest_web_server_esp/include/README +++ /dev/null @@ -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 diff --git a/lib/PsychicHttp/examples/old/simplest_web_server_esp/lib/README b/lib/PsychicHttp/examples/old/simplest_web_server_esp/lib/README deleted file mode 100644 index 6debab1..0000000 --- a/lib/PsychicHttp/examples/old/simplest_web_server_esp/lib/README +++ /dev/null @@ -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 -#include - -int main (void) -{ - ... -} - -``` - -PlatformIO Library Dependency Finder will find automatically dependent -libraries scanning project source files. - -More information about PlatformIO Library Dependency Finder -- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/PsychicHttp/examples/old/simplest_web_server_esp/platformio.ini b/lib/PsychicHttp/examples/old/simplest_web_server_esp/platformio.ini deleted file mode 100644 index d02c3bc..0000000 --- a/lib/PsychicHttp/examples/old/simplest_web_server_esp/platformio.ini +++ /dev/null @@ -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} diff --git a/lib/PsychicHttp/examples/old/simplest_web_server_esp/src/simplest_web_server.cpp b/lib/PsychicHttp/examples/old/simplest_web_server_esp/src/simplest_web_server.cpp deleted file mode 100644 index cedb9c2..0000000 --- a/lib/PsychicHttp/examples/old/simplest_web_server_esp/src/simplest_web_server.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2015 Cesanta Software Limited -// All rights reserved - -#include - -#ifdef ESP32 -#include -#elif defined(ESP8266) -#include -#else -#error Platform not supported -#endif - -#include "mongoose.h" - -const char* ssid = "my-ssid"; -const char* password = "my-password"; - -static const char *s_http_port = "80"; -//static struct mg_serve_http_opts s_http_server_opts; - -static void ev_handler(struct mg_connection *nc, int ev, void *p, void *d) { - static const char *reply_fmt = - "HTTP/1.0 200 OK\r\n" - "Connection: close\r\n" - "Content-Type: text/plain\r\n" - "\r\n" - "Hello %s\n"; - - switch (ev) { - case MG_EV_ACCEPT: { - char addr[32]; - mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr), - MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT); - Serial.printf("Connection %p from %s\n", nc, addr); - break; - } - case MG_EV_HTTP_REQUEST: { - char addr[32]; - struct http_message *hm = (struct http_message *) p; - mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr), - MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT); - Serial.printf("HTTP request from %s: %.*s %.*s\n", addr, (int) hm->method.len, - hm->method.p, (int) hm->uri.len, hm->uri.p); - mg_printf(nc, reply_fmt, addr); - nc->flags |= MG_F_SEND_AND_CLOSE; - break; - } - case MG_EV_CLOSE: { - Serial.printf("Connection %p closed\n", nc); - break; - } - } -} - -struct mg_mgr mgr; -struct mg_connection *nc; - -void setup() -{ - Serial.begin(115200); - - Serial.print("Connecting to "); - Serial.println(ssid); - - WiFi.begin(ssid, password); - - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.println("WiFi connected"); - Serial.println("IP address: "); - Serial.println(WiFi.localIP()); - - mg_mgr_init(&mgr, NULL); - Serial.printf("Starting web server on port %s\n", s_http_port); - nc = mg_bind(&mgr, s_http_port, ev_handler, NULL); - if (nc == NULL) { - Serial.printf("Failed to create listener\n"); - return; - } - - // Set up HTTP server parameters - mg_set_protocol_http_websocket(nc); -// s_http_server_opts.document_root = "."; // Serve current directory -// s_http_server_opts.enable_directory_listing = "yes"; -} - -static uint32_t count = 0; -void loop() -{ - mg_mgr_poll(&mgr, 1000); - //Serial.println(count++); -} diff --git a/lib/PsychicHttp/examples/old/simplest_web_server_esp/test/README b/lib/PsychicHttp/examples/old/simplest_web_server_esp/test/README deleted file mode 100644 index df5066e..0000000 --- a/lib/PsychicHttp/examples/old/simplest_web_server_esp/test/README +++ /dev/null @@ -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 diff --git a/lib/PsychicHttp/examples/old/websocket_chat/.gitignore b/lib/PsychicHttp/examples/old/websocket_chat/.gitignore deleted file mode 100644 index be2b7e8..0000000 --- a/lib/PsychicHttp/examples/old/websocket_chat/.gitignore +++ /dev/null @@ -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 \ No newline at end of file diff --git a/lib/PsychicHttp/examples/old/websocket_chat/include/README b/lib/PsychicHttp/examples/old/websocket_chat/include/README deleted file mode 100644 index 194dcd4..0000000 --- a/lib/PsychicHttp/examples/old/websocket_chat/include/README +++ /dev/null @@ -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 diff --git a/lib/PsychicHttp/examples/old/websocket_chat/lib/README b/lib/PsychicHttp/examples/old/websocket_chat/lib/README deleted file mode 100644 index 6debab1..0000000 --- a/lib/PsychicHttp/examples/old/websocket_chat/lib/README +++ /dev/null @@ -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 -#include - -int main (void) -{ - ... -} - -``` - -PlatformIO Library Dependency Finder will find automatically dependent -libraries scanning project source files. - -More information about PlatformIO Library Dependency Finder -- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/PsychicHttp/examples/old/websocket_chat/platformio.ini b/lib/PsychicHttp/examples/old/websocket_chat/platformio.ini deleted file mode 100644 index 165b7ac..0000000 --- a/lib/PsychicHttp/examples/old/websocket_chat/platformio.ini +++ /dev/null @@ -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 \ No newline at end of file diff --git a/lib/PsychicHttp/examples/old/websocket_chat/src/websocket_chat.cpp b/lib/PsychicHttp/examples/old/websocket_chat/src/websocket_chat.cpp deleted file mode 100644 index 47a04cf..0000000 --- a/lib/PsychicHttp/examples/old/websocket_chat/src/websocket_chat.cpp +++ /dev/null @@ -1,235 +0,0 @@ -// -// A simple server implementation showing how to: -// * serve static messages -// * read GET and POST parameters -// * handle missing pages / 404s -// - -#include -#include -#include - -#ifdef ESP32 -#include -#define START_ESP_WIFI -#elif defined(ESP8266) -#include -#define START_ESP_WIFI -#else -#error Platform not supported -#endif - -MongooseHttpServer server; - -const char *ssid = "wifi"; -const char *password = "password"; - -const char *server_pem = -"-----BEGIN CERTIFICATE-----\r\n" -"MIIDDjCCAfagAwIBAgIBBDANBgkqhkiG9w0BAQsFADA/MRkwFwYDVQQDDBB0ZXN0\r\n" -"LmNlc2FudGEuY29tMRAwDgYDVQQKDAdDZXNhbnRhMRAwDgYDVQQLDAd0ZXN0aW5n\r\n" -"MB4XDTE2MTExMzEzMTgwMVoXDTI2MDgxMzEzMTgwMVowFDESMBAGA1UEAwwJbG9j\r\n" -"YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAro8CW1X0xaGm\r\n" -"GkDaMxKbXWA5Lw+seA61tioGrSIQzuqLYeJoFnwVgF0jB5PTj+3EiGMBcA/mh73V\r\n" -"AthTFmJBxj+agIp7/cvUBpgfLClmSYL2fZi6Fodz+f9mcry3XRw7O6vlamtWfTX8\r\n" -"TAmMSR6PXVBHLgjs5pDOFFmrNAsM5sLYU1/1MFvE2Z9InTI5G437IE1WchRSbpYd\r\n" -"HchC39XzpDGoInZB1a3OhcHm+xUtLpMJ0G0oE5VFEynZreZoEIY4JxspQ7LPsay9\r\n" -"fx3Tlk09gEMQgVCeCNiQwUxZdtLau2x61LNcdZCKN7FbFLJszv1U2uguELsTmi7E\r\n" -"6pHrTziosQIDAQABo0AwPjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIDqDATBgNVHSUE\r\n" -"DDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB\r\n" -"AQBUw0hbTcT6crzODO4QAXU7z4Xxn0LkxbXEsoThG1QCVgMc4Bhpx8gyz5CLyHYz\r\n" -"AiJOBFEeV0XEqoGTNMMFelR3Q5Tg9y1TYO3qwwAWxe6/brVzpts6NiG1uEMBnBFg\r\n" -"oN1x3I9x4NpOxU5MU1dlIxvKs5HQCoNJ8D0SqOX9BV/pZqwEgiCbuWDWQAlxkFpn\r\n" -"iLonlkVI5hTuybCSBsa9FEI9M6JJn9LZmlH90FYHeS4t6P8eOJCeekHL0jUG4Iae\r\n" -"DMP12h8Sd0yxIKmmZ+Q/p/D/BkuHf5Idv3hgyLkZ4mNznjK49wHaYM+BgBoL3Zeg\r\n" -"gJ2sWjUlokrbHswSBLLbUJIF\r\n" -"-----END CERTIFICATE-----\r\n"; - -const char *server_key = -"-----BEGIN PRIVATE KEY-----\r\n" -"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCujwJbVfTFoaYa\r\n" -"QNozEptdYDkvD6x4DrW2KgatIhDO6oth4mgWfBWAXSMHk9OP7cSIYwFwD+aHvdUC\r\n" -"2FMWYkHGP5qAinv9y9QGmB8sKWZJgvZ9mLoWh3P5/2ZyvLddHDs7q+Vqa1Z9NfxM\r\n" -"CYxJHo9dUEcuCOzmkM4UWas0CwzmwthTX/UwW8TZn0idMjkbjfsgTVZyFFJulh0d\r\n" -"yELf1fOkMagidkHVrc6Fweb7FS0ukwnQbSgTlUUTKdmt5mgQhjgnGylDss+xrL1/\r\n" -"HdOWTT2AQxCBUJ4I2JDBTFl20tq7bHrUs1x1kIo3sVsUsmzO/VTa6C4QuxOaLsTq\r\n" -"ketPOKixAgMBAAECggEAI+uNwpnHirue4Jwjyoqzqd1ZJxQEm5f7UIcJZKsz5kBh\r\n" -"ej0KykWybv27bZ2/1UhKPv6QlyzOdXRc1v8I6fxCKLeB5Z2Zsjo1YT4AfCfwwoPO\r\n" -"kT3SXTx2YyVpQYcP/HsIvVi8FtALtixbxJHaall9iugwHYr8pN17arihAE6d0wZC\r\n" -"JXtXRjUWwjKzXP8FoH4KhyadhHbDwIbbJe3cyLfdvp54Gr0YHha0JcOxYgDYNya4\r\n" -"OKxlCluI+hPF31iNzOmFLQVrdYynyPcR6vY5XOiANKE2iNbqCzRb54CvW9WMqObX\r\n" -"RD9t3DMOxGsbVNIwyzZndWy13HoQMGnrHfnGak9ueQKBgQDiVtOqYfLnUnTxvJ/b\r\n" -"qlQZr2ZmsYPZztxlP+DSqZGPD+WtGSo9+rozWfzjTv3KGIDLvf+GFVmjVHwlLQfd\r\n" -"u7eTemWHFc4HK68wruzPO/FdyVpQ4w9v3Usg+ll4a/PDEId0fDMjAr6kk4LC6t8y\r\n" -"9fJR0HjOz57jVnlrDt3v50G8BwKBgQDFbw+jRiUxXnBbDyXZLi+I4iGBGdC+CbaJ\r\n" -"CmsM6/TsOFc+GRsPwQF1gCGqdaURw76noIVKZJOSc8I+yiwU6izyh/xaju5JiWQd\r\n" -"kwbU1j4DE6GnxmT3ARmB7VvCxjaEZEAtICWs1QTKRz7PcTV8yr7Ng1A3VIy+NSpo\r\n" -"LFMMmk83hwKBgQDVCEwpLg/mUeHoNVVw95w4oLKNLb+gHeerFLiTDy8FrDzM88ai\r\n" -"l37yHly7xflxYia3nZkHpsi7xiUjCINC3BApKyasQoWskh1OgRY653yCfaYYQ96f\r\n" -"t3WjEH9trI2+p6wWo1+uMEMnu/9zXoW9/WeaQdGzNg+igh29+jxCNTPVuQKBgGV4\r\n" -"CN9vI5pV4QTLqjYOSJvfLDz/mYqxz0BrPE1tz3jAFAZ0PLZCCY/sBGFpCScyJQBd\r\n" -"vWNYgYeZOtGuci1llSgov4eDQfBFTlDsyWwFl+VY55IkoqtXw1ZFOQ3HdSlhpKIM\r\n" -"jZBgApA7QYq3sjeqs5lHzahCKftvs5XKgfxOKjxtAoGBALdnYe6xkDvGLvI51Yr+\r\n" -"Dy0TNcB5W84SxUKvM7DVEomy1QPB57ZpyQaoBq7adOz0pWJXfp7qo4950ZOhBGH1\r\n" -"hKbZ6c4ggwVJy2j49EgMok5NGCKvPAtabbR6H8Mz8DW9aXURxhWJvij+Qw1fWK4b\r\n" -"7G/qUI9iE5iUU7MkIcLIbTf/\r\n" -"-----END PRIVATE KEY-----\r\n"; - -const char *index_page = -"\n" -"\n" -"\n" -" \n" -" WebSocket Test\n" -" \n" -" \n" -"\n" -"\n" -"\n" -"\n" -"
\n" -"

Websocket PubSub Demonstration

\n" -"\n" -"

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

\n" -"\n" -"
\n" -"
\n" -"\n" -"

\n" -" \n" -" \n" -"

\n" -"
\n" -"\n" -"\n"; - -#include - -void broadcast(MongooseHttpWebSocketConnection *from, MongooseString msg) -{ - char buf[500]; - char addr[32]; - mg_sock_addr_to_str(from->getRemoteAddress(), addr, sizeof(addr), - MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT); - - snprintf(buf, sizeof(buf), "%s %.*s", addr, (int) msg.length(), msg.c_str()); - printf("%s\n", buf); - server.sendAll(from, buf); -} - -void setup() -{ - Serial.begin(115200); - -#ifdef START_ESP_WIFI - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - if (WiFi.waitForConnectResult() != WL_CONNECTED) - { - Serial.printf("WiFi Failed!\n"); - return; - } - - Serial.print("IP Address: "); - Serial.println(WiFi.localIP()); - Serial.print("Hostname: "); -#ifdef ESP32 - Serial.println(WiFi.getHostname()); -#elif defined(ESP8266) - Serial.println(WiFi.hostname()); -#endif -#endif - - Mongoose.begin(); - -#ifdef SIMPLE_SERVER_SECURE - if(false == server.begin(443, server_pem, server_key)) { - Serial.print("Failed to start server"); - return; - } -#else - server.begin(80); -#endif - - server.on("/$", HTTP_GET, [](MongooseHttpServerRequest *request) { - request->send(200, "text/html", index_page); - }); - - // Test the stream response class - server.on("/ws$")-> - onConnect([](MongooseHttpWebSocketConnection *connection) { - broadcast(connection, MongooseString("++ joined")); - })-> - onClose([](MongooseHttpServerRequest *c) { - MongooseHttpWebSocketConnection *connection = static_cast(c); - broadcast(connection, MongooseString("++ left")); - })-> - onFrame([](MongooseHttpWebSocketConnection *connection, int flags, uint8_t *data, size_t len) { - broadcast(connection, MongooseString((const char *)data, len)); - }); -} - -void loop() -{ - Mongoose.poll(1000); - Serial.printf("Free memory %u\n", ESP.getFreeHeap()); -} diff --git a/lib/PsychicHttp/examples/old/websocket_chat/test/README b/lib/PsychicHttp/examples/old/websocket_chat/test/README deleted file mode 100644 index df5066e..0000000 --- a/lib/PsychicHttp/examples/old/websocket_chat/test/README +++ /dev/null @@ -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 diff --git a/lib/PsychicHttp/examples/old/websocket_chat/test/tests.rest b/lib/PsychicHttp/examples/old/websocket_chat/test/tests.rest deleted file mode 100644 index 4072428..0000000 --- a/lib/PsychicHttp/examples/old/websocket_chat/test/tests.rest +++ /dev/null @@ -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 diff --git a/lib/PsychicHttp/examples/platformio/data/www/index.html b/lib/PsychicHttp/examples/platformio/data/www/index.html index 76f14a9..9c6ed91 100644 --- a/lib/PsychicHttp/examples/platformio/data/www/index.html +++ b/lib/PsychicHttp/examples/platformio/data/www/index.html @@ -1,66 +1,73 @@ - - - - PsychicHTTP Demo - - - -
-

Basic Request Examples

- -

Static Serving

-

- - -

-

Text File

+ + + + PsychicHTTP Demo + + -

Simple POST Form

-
- - -
- - -
- -
+ +
+

Basic Request Examples

+ -

Basic File Upload

- - - - - - - - - - - - -
- - - -
- - - -
- -
- + -

Multipart POST Form

-
- - -
+

Multipart POST Form

+ + + +
- - -
+ + +
- - -
- - -
+ + +
-

Websocket Demo

- - - -
- -
+ + - - -

EventSource Demo

- -
- -
- - -
- + } + + +

EventSource Demo

+ +
+ +
+ + +
+ + \ No newline at end of file diff --git a/lib/PsychicHttp/examples/platformio/data/www/websocket-test.html b/lib/PsychicHttp/examples/platformio/data/www/websocket-test.html new file mode 100644 index 0000000..f457a23 --- /dev/null +++ b/lib/PsychicHttp/examples/platformio/data/www/websocket-test.html @@ -0,0 +1,135 @@ + + + + + + + WebSocket Message Rate Test + + + +

WebSocket Message Rate Test

+

Time Remaining: 0

+

Messages Count: 0

+

Messages per second: 0

+ + +

+ + +

+

+ + + + + \ No newline at end of file diff --git a/lib/PsychicHttp/examples/platformio/include/README b/lib/PsychicHttp/examples/platformio/include/README deleted file mode 100644 index 194dcd4..0000000 --- a/lib/PsychicHttp/examples/platformio/include/README +++ /dev/null @@ -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 diff --git a/lib/PsychicHttp/examples/platformio/lib/README b/lib/PsychicHttp/examples/platformio/lib/README deleted file mode 100644 index 6debab1..0000000 --- a/lib/PsychicHttp/examples/platformio/lib/README +++ /dev/null @@ -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 -#include - -int main (void) -{ - ... -} - -``` - -PlatformIO Library Dependency Finder will find automatically dependent -libraries scanning project source files. - -More information about PlatformIO Library Dependency Finder -- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/PsychicHttp/examples/platformio/platformio.ini b/lib/PsychicHttp/examples/platformio/platformio.ini index 77a33bc..84015bd 100644 --- a/lib/PsychicHttp/examples/platformio/platformio.ini +++ b/lib/PsychicHttp/examples/platformio/platformio.ini @@ -12,19 +12,44 @@ platform = espressif32 framework = arduino board = esp32-s3-devkitc-1 +upload_port = /dev/ttyACM0 +monitor_port = /dev/ttyACM1 monitor_speed = 115200 monitor_filters = esp32_exception_decoder lib_deps = - ; devmode: with this disabled make a symlink from platformio/lib to the PsychicHttp directory - ;hoeken/PsychicHttp - bblanchon/ArduinoJson + ; hoeken/PsychicHttp + ; PIO is not able to consider installed project in CI + ;../.. board_build.filesystem = littlefs - -[env:default] build_flags = - -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_WARN - ;-D ENABLE_ASYNC + -Wall + -Wextra -; [env:arduino3] -; platform = https://github.com/platformio/platform-espressif32.git -; platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32#master \ No newline at end of file +[env:arduino2] +platform = espressif32@6.8.1 + +[env:arduino2-ssl] +platform = espressif32@6.8.1 +build_flags = -D PSY_ENABLE_SSL + +[env:arduino2-regex] +platform = espressif32@6.8.1 +build_flags = -D PSY_ENABLE_REGEX + +[env:arduino3] +platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip + +[env:arduino3-ssl] +platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip +build_flags = -D PSY_ENABLE_SSL + +[env:arduino3-regex] +platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip +build_flags = -D PSY_ENABLE_REGEX + +[env:waveshare-4-3-touchscreen] +lib_deps = ${env.lib_deps} + https://github.com/esp-arduino-libs/ESP32_IO_Expander +build_flags = + -D PSY_ENABLE_SDCARD + -D WAVESHARE_43_TOUCH diff --git a/lib/PsychicHttp/examples/platformio/src/main.cpp b/lib/PsychicHttp/examples/platformio/src/main.cpp index eb52479..403cc81 100644 --- a/lib/PsychicHttp/examples/platformio/src/main.cpp +++ b/lib/PsychicHttp/examples/platformio/src/main.cpp @@ -9,66 +9,113 @@ */ /********************************************************************************************** -* Note: this demo relies on various files to be uploaded on the LittleFS partition -* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image -**********************************************************************************************/ + * Note: this demo relies on various files to be uploaded on the LittleFS partition + * PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image + **********************************************************************************************/ +#include "_secret.h" #include -#include -#include #include #include -#include -#include "_secret.h" +#include #include -//#include //uncomment this to enable HTTPS / SSL +#include +#include + +// #define this to enable SD card support +#ifdef PSY_ENABLE_SDCARD + + #ifdef WAVESHARE_43_TOUCH + #include + // 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 + #include + #include +#endif + +// #define this to enable SSL at build (or switch to the 'ssl' build target in vscode) +#ifdef PSY_ENABLE_SSL + #include +#endif + +// debugging library +#ifdef PSY_DEVMODE + #include +#endif #ifndef WIFI_SSID #error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there." #endif -//Enter your WIFI credentials in secret.h -const char *ssid = WIFI_SSID; -const char *password = WIFI_PASS; +// Enter your WIFI credentials in secret.h +const char* ssid = WIFI_SSID; +const char* password = WIFI_PASS; // Set your SoftAP credentials -const char *softap_ssid = "PsychicHttp"; -const char *softap_password = ""; -IPAddress softap_ip(10, 0, 0, 1); +const char* softap_ssid = "PsychicHttp"; +const char* softap_password = ""; +IPAddress softap_ip(10, 0, 0, 1); -//credentials for the /auth-basic and /auth-digest examples -const char *app_user = "admin"; -const char *app_pass = "admin"; -const char *app_name = "Your App"; +// credentials for the /auth-basic and /auth-digest examples +const char* app_user = "admin"; +const char* app_pass = "admin"; +const char* app_name = "Your App"; -//hostname for mdns (psychic.local) -const char *local_hostname = "psychic"; +LoggingMiddleware loggingMiddleware; +AuthenticationMiddleware basicAuth; +AuthenticationMiddleware digestAuth; -//#define PSY_ENABLE_SSL to enable ssl +// hostname for mdns (psychic.local) +const char* local_hostname = "psychic"; + +// #define PSY_ENABLE_SSL to enable ssl #ifdef PSY_ENABLE_SSL - bool app_enable_ssl = true; - String server_cert; - String server_key; +bool app_enable_ssl = true; +String server_cert; +String server_key; #endif -//our main server object +// our main server object #ifdef PSY_ENABLE_SSL - PsychicHttpsServer server; +PsychicHttpsServer server; #else - PsychicHttpServer server; +PsychicHttpServer server; #endif PsychicWebSocketHandler websocketHandler; PsychicEventSource eventSource; +CorsMiddleware corsMiddleware; -//NTP server stuff -const char *ntpServer1 = "pool.ntp.org"; -const char *ntpServer2 = "time.nist.gov"; +// NTP server stuff +const char* ntpServer1 = "pool.ntp.org"; +const char* ntpServer2 = "time.nist.gov"; const long gmtOffset_sec = 0; const int daylightOffset_sec = 0; struct tm timeinfo; // Callback function (gets called when time adjusts via NTP) -void timeAvailable(struct timeval *t) +void timeAvailable(struct timeval* t) { if (!getLocalTime(&timeinfo)) { Serial.println("Failed to obtain time"); @@ -83,10 +130,12 @@ void timeAvailable(struct timeval *t) bool connectToWifi() { - //dual client and AP mode - WiFi.mode(WIFI_AP_STA); + // WiFi.mode(WIFI_AP); // ap only mode + // WiFi.mode(WIFI_STA); // client only mode + WiFi.mode(WIFI_AP_STA); // ap and client // Configure SoftAP + // dual client and AP mode WiFi.softAPConfig(softap_ip, softap_ip, IPAddress(255, 255, 255, 0)); // subnet FF FF FF 00 WiFi.softAP(softap_ssid, softap_password); IPAddress myIP = WiFi.softAPIP(); @@ -107,10 +156,8 @@ bool connectToWifi() int numberOfTries = 20; // Wait for the WiFi event - while (true) - { - switch (WiFi.status()) - { + while (true) { + switch (WiFi.status()) { case WL_NO_SSID_AVAIL: Serial.println("[WiFi] SSID not found"); break; @@ -140,15 +187,12 @@ bool connectToWifi() } delay(tryDelay); - if (numberOfTries <= 0) - { + if (numberOfTries <= 0) { Serial.print("[WiFi] Failed to connect to WiFi!"); // Use disconnect function to force stop trying to connect WiFi.disconnect(); return false; - } - else - { + } else { numberOfTries--; } } @@ -156,198 +200,247 @@ bool connectToWifi() return false; } +#ifdef PSY_ENABLE_SDCARD +bool setupSDCard() +{ + #ifdef WAVESHARE_43_TOUCH + ESP_IOExpander* expander = new ESP_IOExpander_CH422G((i2c_port_t)I2C_MASTER_NUM, ESP_IO_EXPANDER_I2C_CH422G_ADDRESS_000, I2C_MASTER_SCL_IO, I2C_MASTER_SDA_IO); + expander->init(); + expander->begin(); + expander->multiPinMode(TP_RST | LCD_BL | LCD_RST | SD_CS | USB_SEL, OUTPUT); + expander->multiDigitalWrite(TP_RST | LCD_BL | LCD_RST, HIGH); + + // use extend GPIO for SD card + expander->digitalWrite(SD_CS, LOW); + SPI.setHwCs(false); + #endif + + SPI.begin(SD_CLK, SD_MISO, SD_MOSI, SD_SS); + if (!SD.begin()) { + Serial.println("SD Card Mount Failed"); + return false; + } + uint8_t cardType = SD.cardType(); + + if (cardType == CARD_NONE) { + Serial.println("No SD card attached"); + return false; + } + + Serial.print("SD Card Type: "); + if (cardType == CARD_MMC) { + Serial.println("MMC"); + } else if (cardType == CARD_SD) { + Serial.println("SDSC"); + } else if (cardType == CARD_SDHC) { + Serial.println("SDHC"); + } else { + Serial.println("UNKNOWN"); + } + + uint64_t cardSize = SD.cardSize() / (1024 * 1024); + Serial.printf("SD Card Size: %lluMB\n", cardSize); + Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024)); + Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024)); + + return true; +} +#endif + void setup() { + esp_log_level_set(PH_TAG, ESP_LOG_DEBUG); + esp_log_level_set("httpd_uri", ESP_LOG_DEBUG); + Serial.begin(115200); delay(10); + Serial.printf("ESP-IDF Version: %s\n", esp_get_idf_version()); + +#ifdef ESP_ARDUINO_VERSION_STR + Serial.printf("Arduino Version: %s\n", ESP_ARDUINO_VERSION_STR); +#else + Serial.printf("Arduino Version: %d.%d.%d\n", ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATCH); +#endif + Serial.printf("PsychicHttp Version: %s\n", PSYCHIC_VERSION_STR); + // We start by connecting to a WiFi network // To debug, please enable Core Debug Level to Verbose - if (connectToWifi()) - { - //Setup our NTP to get the current time. + if (connectToWifi()) { + // Setup our NTP to get the current time. sntp_set_time_sync_notification_cb(timeAvailable); - sntp_servermode_dhcp(1); // (optional) +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2) + esp_sntp_servermode_dhcp(1); // (optional) +#else + sntp_servermode_dhcp(1); // (optional) +#endif configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1, ntpServer2); - //set up our esp32 to listen on the local_hostname.local domain - if (!MDNS.begin(local_hostname)) { + // set up our esp32 to listen on the psychic.local domain + if (MDNS.begin(local_hostname)) + MDNS.addService("http", "tcp", 80); + else Serial.println("Error starting mDNS"); - return; - } - MDNS.addService("http", "tcp", 80); - if(!LittleFS.begin()) - { + if (!LittleFS.begin()) { Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode"); return; } - //look up our keys? - #ifdef PSY_ENABLE_SSL - if (app_enable_ssl) - { - File fp = LittleFS.open("/server.crt"); - if (fp) - { - server_cert = fp.readString(); +#ifdef PSY_ENABLE_SSL + // look up our keys? + if (app_enable_ssl) { + File fp = LittleFS.open("/server.crt"); + if (fp) { + server_cert = fp.readString(); - // Serial.println("Server Cert:"); - // Serial.println(server_cert); - } - else - { - Serial.println("server.pem not found, SSL not available"); - app_enable_ssl = false; - } - fp.close(); - - File fp2 = LittleFS.open("/server.key"); - if (fp2) - { - server_key = fp2.readString(); - - // Serial.println("Server Key:"); - // Serial.println(server_key); - } - else - { - Serial.println("server.key not found, SSL not available"); - app_enable_ssl = false; - } - fp2.close(); + // Serial.println("Server Cert:"); + // Serial.println(server_cert); + } else { + Serial.println("server.pem not found, SSL not available"); + app_enable_ssl = false; } - #endif + fp.close(); - //setup server config stuff here - server.config.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls) + File fp2 = LittleFS.open("/server.key"); + if (fp2) { + server_key = fp2.readString(); - #ifdef PSY_ENABLE_SSL - server.ssl_config.httpd.max_uri_handlers = 20; //maximum number of uri handlers (.on() calls) - - //do we want secure or not? - if (app_enable_ssl) - { - server.listen(443, server_cert.c_str(), server_key.c_str()); - - //this creates a 2nd server listening on port 80 and redirects all requests HTTPS - PsychicHttpServer *redirectServer = new PsychicHttpServer(); - redirectServer->config.ctrl_port = 20424; // just a random port different from the default one - redirectServer->listen(80); - redirectServer->onNotFound([](PsychicRequest *request) { - String url = "https://" + request->host() + request->url(); - return request->redirect(url.c_str()); - }); + // Serial.println("Server Key:"); + // Serial.println(server_key); + } else { + Serial.println("server.key not found, SSL not available"); + app_enable_ssl = false; } - else - server.listen(80); - #else - server.listen(80); - #endif + fp2.close(); + } + + // do we want secure or not? + if (app_enable_ssl) { + server.setCertificate(server_cert.c_str(), server_key.c_str()); + + // this creates a 2nd server listening on port 80 and redirects all requests HTTPS + PsychicHttpServer* redirectServer = new PsychicHttpServer(); + redirectServer->config.ctrl_port = 20424; // just a random port different from the default one + redirectServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) { + String url = "https://" + request->host() + request->url(); + return response->redirect(url.c_str()); }); + } +#endif DefaultHeaders::Instance().addHeader("Server", "PsychicHttp"); - //serve static files from LittleFS/www on / only to clients on same wifi network - //this is where our /index.html file lives - // curl -i http://psychic.local/ - PsychicStaticFileHandler* handler = server.serveStatic("/", LittleFS, "/www/"); - handler->setFilter(ON_STA_FILTER); - handler->setCacheControl("max-age=60"); + loggingMiddleware.setOutput(Serial); - //serve static files from LittleFS/www-ap on / only to clients on SoftAP - //this is where our /index.html file lives - server.serveStatic("/", LittleFS, "/www-ap/")->setFilter(ON_AP_FILTER); + basicAuth.setUsername(app_user); + basicAuth.setPassword(app_pass); + basicAuth.setRealm(app_name); + basicAuth.setAuthMethod(HTTPAuthMethod::BASIC_AUTH); + basicAuth.setAuthFailureMessage("You must log in."); - //serve static files from LittleFS/img on /img - //it's more efficient to serve everything from a single www directory, but this is also possible. - // curl -i http://psychic.local/img/request_flow.png + digestAuth.setUsername(app_user); + digestAuth.setPassword(app_pass); + digestAuth.setRealm(app_name); + digestAuth.setAuthMethod(HTTPAuthMethod::DIGEST_AUTH); + digestAuth.setAuthFailureMessage("You must log in."); + + // corsMiddleware.setAllowCredentials(true); + // corsMiddleware.setOrigin("http://www.example.com,https://www.example.com,http://api.example.com,https://api.example.com"); + // corsMiddleware.setHeaders("Origin,X-Requested-With,Content-Type,Accept,Content-Type,Authorization,X-Access-Token"); + + server.addMiddleware(&loggingMiddleware); + // this will send CORS headers on every HTTP_OPTIONS request that contains the Origin: header + server.addMiddleware(&corsMiddleware); + + // rewrites! + server.rewrite("/rewrite", "/api?foo=rewrite"); + + // serve static files from LittleFS/www on / only to clients on same wifi network + // this is where our /index.html file lives + // curl -i http://psychic.local/ + server.serveStatic("/", LittleFS, "/www/") + ->setCacheControl("max-age=60") + ->addFilter(ON_STA_FILTER); + + // serve static files from LittleFS/www-ap on / only to clients on SoftAP + // this is where our /index.html file lives + server.serveStatic("/", LittleFS, "/www-ap/")->addFilter(ON_AP_FILTER); + + // serve static files from LittleFS/img on /img + // it's more efficient to serve everything from a single www directory, but this is also possible. + // curl -i http://psychic.local/img/request_flow.png server.serveStatic("/img", LittleFS, "/img/"); - //you can also serve single files - // curl -i http://psychic.local/myfile.txt +#ifdef PSY_ENABLE_SDCARD + // if we detect an SD card, serve all files from sd:/ on http://psychic.local/sd + if (setupSDCard()) + server.serveStatic("/sd", SD, "/"); +#endif + + // you can also serve single files + // curl -i http://psychic.local/myfile.txt server.serveStatic("/myfile.txt", LittleFS, "/custom.txt"); - //example callback everytime a connection is opened - server.onOpen([](PsychicClient *client) { - Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); - }); + // example callback everytime a connection is opened + server.onOpen([](PsychicClient* client) { Serial.printf("[http] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str()); }); - //example callback everytime a connection is closed - server.onClose([](PsychicClient *client) { - Serial.printf("[http] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); - }); + // example callback everytime a connection is closed + server.onClose([](PsychicClient* client) { Serial.printf("[http] connection #%u closed\n", client->socket()); }); - //api - json message passed in as post body - // curl -i -X POST -H "Content-Type: application/json" -d '{"foo":"bar"}' http://psychic.local/api - server.on("/api", HTTP_POST, [](PsychicRequest *request, JsonVariant &json) - { + // api - json message passed in as post body + // curl -i -X POST -H "Content-Type: application/json" -d '{"foo":"bar"}' http://psychic.local/api + server.on("/api", HTTP_POST, [](PsychicRequest* request, PsychicResponse* resp, JsonVariant& json) { JsonObject input = json.as(); - //create our response json - PsychicJsonResponse response = PsychicJsonResponse(request); + // create our response json + PsychicJsonResponse response(resp); JsonObject output = response.getRoot(); output["msg"] = "status"; output["status"] = "success"; output["millis"] = millis(); + output["method"] = request->methodStr(); - //work with some params - if (input.containsKey("foo")) - { + // work with some params + if (input.containsKey("foo")) { String foo = input["foo"]; output["foo"] = foo; } - + return response.send(); }); - //ip - get info about the client - // curl -i http://psychic.local/ip - server.on("/ip", HTTP_GET, [](PsychicRequest *request) - { + // ip - get info about the client + // curl -i http://psychic.local/ip + server.on("/ip", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { String output = "Your IP is: " + request->client()->remoteIP().toString(); - return request->reply(output.c_str()); + return response->send(output.c_str()); }); - //client connect/disconnect to a url - // curl -i http://psychic.local/handler - PsychicWebHandler *connectionHandler = new PsychicWebHandler(); - connectionHandler->onRequest([](PsychicRequest *request) - { - return request->reply("OK"); - }); - connectionHandler->onOpen([](PsychicClient *client) { - Serial.printf("[handler] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); - }); - connectionHandler->onClose([](PsychicClient *client) { - Serial.printf("[handler] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); - }); + // client connect/disconnect to a url + // curl -i http://psychic.local/handler + PsychicWebHandler* connectionHandler = new PsychicWebHandler(); + connectionHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) { return response->send("OK"); }); + connectionHandler->onOpen([](PsychicClient* client) { Serial.printf("[handler] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str()); }); + connectionHandler->onClose([](PsychicClient* client) { Serial.printf("[handler] connection #%u closed\n", client->socket()); }); - //add it to our server + // add it to our server server.on("/handler", connectionHandler); - //api - parameters passed in via query eg. /api?foo=bar - // curl -i 'http://psychic.local/api?foo=bar' - server.on("/api", HTTP_GET, [](PsychicRequest *request) - { - //showcase some of the variables - Serial.println(request->host()); - Serial.println(request->uri()); - Serial.println(request->path()); - Serial.println(request->queryString()); - - //create a response object - //create our response json - PsychicJsonResponse response = PsychicJsonResponse(request); + // api - parameters passed in via query eg. /api?foo=bar + // curl -i 'http://psychic.local/api?foo=bar' + server.on("/api", HTTP_GET, [](PsychicRequest* request, PsychicResponse* resp) { + // create our response json + PsychicJsonResponse response = PsychicJsonResponse(resp); JsonObject output = response.getRoot(); output["msg"] = "status"; output["status"] = "success"; output["millis"] = millis(); + output["method"] = request->methodStr(); - //work with some params - if (request->hasParam("foo")) - { + // work with some params + if (request->hasParam("foo")) { String foo = request->getParam("foo")->value(); output["foo"] = foo; } @@ -355,112 +448,135 @@ void setup() return response.send(); }); - //JsonResponse example - // curl -i http://psychic.local/json - server.on("/json", HTTP_GET, [](PsychicRequest *request) - { - PsychicJsonResponse response = PsychicJsonResponse(request); + // curl -i -X GET 'http://psychic.local/any' + // curl -i -X POST 'http://psychic.local/any' + server.on("/any", HTTP_ANY, [](PsychicRequest* request, PsychicResponse* resp) { + // create our response json + PsychicJsonResponse response = PsychicJsonResponse(resp); + JsonObject output = response.getRoot(); + + output["msg"] = "status"; + output["status"] = "success"; + output["millis"] = millis(); + output["method"] = request->methodStr(); + + return response.send(); + }); + + // curl -i 'http://psychic.local/simple' + server.on("/simple", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { + return response->send("Simple"); + }) + ->setURIMatchFunction(MATCH_SIMPLE); + +#ifdef PSY_ENABLE_REGEX + // curl -i 'http://psychic.local/regex/23' + // curl -i 'http://psychic.local/regex/4223' + server.on("^/regex/([\\d]+)/?$", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { + // look up our regex matches + std::smatch matches; + if (request->getRegexMatches(matches)) { + String output; + output += "Matches: " + String(matches.size()) + "
\n"; + output += "Matched URI: " + String(matches.str(0).c_str()) + "
\n"; + output += "Match 1: " + String(matches.str(1).c_str()) + "
\n"; + + return response->send(output.c_str()); + } else + return response->send("No regex match."); + }) + ->setURIMatchFunction(MATCH_REGEX); +#endif + + // JsonResponse example + // curl -i http://psychic.local/json + server.on("/json", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { + PsychicJsonResponse jsonResponse = PsychicJsonResponse(response); char key[16]; char value[32]; - JsonObject root = response.getRoot(); - for (int i=0; i<100; i++) - { + JsonObject root = jsonResponse.getRoot(); + for (int i = 0; i < 100; i++) { sprintf(key, "key%d", i); sprintf(value, "value is %d", i); root[key] = value; } - return response.send(); - }); - - //how to redirect a request - // curl -i http://psychic.local/redirect - server.on("/redirect", HTTP_GET, [](PsychicRequest *request) - { - return request->redirect("/alien.png"); + return jsonResponse.send(); }); - //how to do basic auth - // curl -i --user admin:admin http://psychic.local/auth-basic - server.on("/auth-basic", HTTP_GET, [](PsychicRequest *request) - { - if (!request->authenticate(app_user, app_pass)) - return request->requestAuthentication(BASIC_AUTH, app_name, "You must log in."); - return request->reply("Auth Basic Success!"); - }); + // how to redirect a request + // curl -i http://psychic.local/redirect + server.on("/redirect", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->redirect("/alien.png"); }); - //how to do digest auth - // curl -i --user admin:admin http://psychic.local/auth-digest - server.on("/auth-digest", HTTP_GET, [](PsychicRequest *request) - { - if (!request->authenticate(app_user, app_pass)) - return request->requestAuthentication(DIGEST_AUTH, app_name, "You must log in."); - return request->reply("Auth Digest Success!"); - }); + // how to do basic auth + // curl -i --user admin:admin http://psychic.local/auth-basic + server.on("/auth-basic", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { + return response->send("Auth Basic Success!"); + })->addMiddleware(&basicAuth); - //example of getting / setting cookies - // curl -i -b cookie.txt -c cookie.txt http://psychic.local/cookies - server.on("/cookies", HTTP_GET, [](PsychicRequest *request) - { - PsychicResponse response(request); + // how to do digest auth + // curl -i --user admin:admin http://psychic.local/auth-digest + server.on("/auth-digest", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { + return response->send("Auth Digest Success!"); + })->addMiddleware(&digestAuth); + // example of getting / setting cookies + // curl -i -b cookie.txt -c cookie.txt http://psychic.local/cookies + server.on("/cookies", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { int counter = 0; - if (request->hasCookie("counter")) - { - counter = std::stoi(request->getCookie("counter").c_str()); + char cookie[14]; + size_t size = sizeof(cookie); + if (request->getCookie("counter", cookie, &size) == ESP_OK) { + // value is null-terminated. + counter = std::stoi(cookie); counter++; } + sprintf(cookie, "%d", counter); - char cookie[10]; - sprintf(cookie, "%i", counter); - - response.setCookie("counter", cookie); - response.setContent(cookie); - return response.send(); + response->setCookie("counter", cookie); + response->setContent(cookie); + return response->send(); }); - //example of getting POST variables - // curl -i -d "param1=value1¶m2=value2" -X POST http://psychic.local/post - server.on("/post", HTTP_POST, [](PsychicRequest *request) - { + // example of getting POST variables + // curl -i -d "param1=value1¶m2=value2" -X POST http://psychic.local/post + // curl -F "param1=value1" -F "param2=value2" -X POST http://psychic.local/post + server.on("/post", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) { String output; output += "Param 1: " + request->getParam("param1")->value() + "
\n"; output += "Param 2: " + request->getParam("param2")->value() + "
\n"; - return request->reply(output.c_str()); + return response->send(output.c_str()); }); - //you can set up a custom 404 handler. - // curl -i http://psychic.local/404 - server.onNotFound([](PsychicRequest *request) - { - return request->reply(404, "text/html", "Custom 404 Handler"); - }); + // you can set up a custom 404 handler. + // curl -i http://psychic.local/404 + server.onNotFound([](PsychicRequest* request, PsychicResponse* response) { return response->send(404, "text/html", "Custom 404 Handler"); }); - //handle a very basic upload as post body - PsychicUploadHandler *uploadHandler = new PsychicUploadHandler(); - uploadHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { + // handle a very basic upload as post body + PsychicUploadHandler* uploadHandler = new PsychicUploadHandler(); + uploadHandler->onUpload([](PsychicRequest* request, const String& filename, uint64_t index, uint8_t* data, size_t len, bool last) { File file; String path = "/www/" + filename; - Serial.printf("Writing %d/%d bytes to: %s\n", (int)index+(int)len, request->contentLength(), path.c_str()); + Serial.printf("Writing %d/%d bytes to: %s\n", (int)index + (int)len, request->contentLength(), path.c_str()); if (last) - Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len); + Serial.printf("%s is finished. Total bytes: %llu\n", path.c_str(), (uint64_t)index + (uint64_t)len); - //our first call? + // our first call? if (!index) file = LittleFS.open(path, FILE_WRITE); else file = LittleFS.open(path, FILE_APPEND); - - if(!file) { + + if (!file) { Serial.println("Failed to open file"); return ESP_FAIL; } - if(!file.write(data, len)) { + if (!file.write(data, len)) { Serial.println("Write failed"); return ESP_FAIL; } @@ -468,42 +584,41 @@ void setup() return ESP_OK; }); - //gets called after upload has been handled - uploadHandler->onRequest([](PsychicRequest *request) - { + // gets called after upload has been handled + uploadHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) { String url = "/" + request->getFilename(); String output = "" + url + ""; - return request->reply(output.c_str()); + return response->send(output.c_str()); }); - //wildcard basic file upload - POST to /upload/filename.ext - // use http://psychic.local/ to test + // wildcard basic file upload - POST to /upload/filename.ext + // use http://psychic.local/ to test server.on("/upload/*", HTTP_POST, uploadHandler); - //a little bit more complicated multipart form - PsychicUploadHandler *multipartHandler = new PsychicUploadHandler(); - multipartHandler->onUpload([](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { + // a little bit more complicated multipart form + PsychicUploadHandler* multipartHandler = new PsychicUploadHandler(); + multipartHandler->onUpload([](PsychicRequest* request, const String& filename, uint64_t index, uint8_t* data, size_t len, bool last) { File file; String path = "/www/" + filename; - //some progress over serial. - Serial.printf("Writing %d bytes to: %s\n", (int)len, path.c_str()); + // some progress over serial. + Serial.printf("Writing %d bytes to: %s @ index %llu\n", (int)len, path.c_str(), index); if (last) - Serial.printf("%s is finished. Total bytes: %d\n", path.c_str(), (int)index+(int)len); + Serial.printf("%s is finished. Total bytes: %llu\n", path.c_str(), (uint64_t)index + (uint64_t)len); - //our first call? + // our first call? if (!index) file = LittleFS.open(path, FILE_WRITE); else file = LittleFS.open(path, FILE_APPEND); - - if(!file) { + + if (!file) { Serial.println("Failed to open file"); return ESP_FAIL; } - if(!file.write(data, len)) { + if (!file.write(data, len)) { Serial.println("Write failed"); return ESP_FAIL; } @@ -511,57 +626,83 @@ void setup() return ESP_OK; }); - //gets called after upload has been handled - multipartHandler->onRequest([](PsychicRequest *request) - { - if (request->hasParam("file_upload")) - { - PsychicWebParameter *file = request->getParam("file_upload"); + // gets called after upload has been handled + multipartHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) { + String output; + if (request->hasParam("file_upload")) { + PsychicWebParameter* file = request->getParam("file_upload"); String url = "/" + file->value(); - String output; - output += "" + url + "
\n"; output += "Bytes: " + String(file->size()) + "
\n"; - output += "Param 1: " + request->getParam("param1")->value() + "
\n"; - output += "Param 2: " + request->getParam("param2")->value() + "
\n"; - - return request->reply(output.c_str()); } - else - return request->reply("No upload."); + + if (request->hasParam("param1")) + output += "Param 1: " + request->getParam("param1")->value() + "
\n"; + if (request->hasParam("param2")) + output += "Param 2: " + request->getParam("param2")->value() + "
\n"; + + return response->send(output.c_str()); }); - //wildcard basic file upload - POST to /upload/filename.ext - // use http://psychic.local/ to test + // wildcard basic file upload - POST to /upload/filename.ext + // use http://psychic.local/ to test + // just multipart data: curl -F "param1=multi" -F "param2=part" http://psychic.local/multipart server.on("/multipart", HTTP_POST, multipartHandler); - //a websocket echo server - // npm install -g wscat - // wscat -c ws://psychic.local/ws - websocketHandler.onOpen([](PsychicWebSocketClient *client) { - Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); + // form only multipart handler + // curl -F "param1=multi" -F "param2=part" http://psychic.local/multipart-data + PsychicUploadHandler* multipartFormHandler = new PsychicUploadHandler(); + multipartFormHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) { + String output; + if (request->hasParam("param1")) + output += "Param 1: " + request->getParam("param1")->value() + "
\n"; + if (request->hasParam("param2")) + output += "Param 2: " + request->getParam("param2")->value() + "
\n"; + + return response->send(output.c_str()); + }); + server.on("/multipart-data", HTTP_POST, multipartFormHandler); + + // a websocket echo server + // npm install -g wscat + // Plaintext: wscat -c ws://psychic.local/ws + // SSL: wscat -n -c wss://psychic.local/ws + websocketHandler.onOpen([](PsychicWebSocketClient* client) { + Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str()); client->sendMessage("Hello!"); }); - websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) { - Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload); + websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) { + // Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), String((char*)frame->payload, frame->len).c_str()); return request->reply(frame); }); - websocketHandler.onClose([](PsychicWebSocketClient *client) { - Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); - }); + websocketHandler.onClose([](PsychicWebSocketClient* client) { Serial.printf("[socket] connection #%u closed\n", client->socket()); }); server.on("/ws", &websocketHandler); - //EventSource server - // curl -i -N http://psychic.local/events - eventSource.onOpen([](PsychicEventSourceClient *client) { - Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); + // EventSource server + // curl -i -N http://psychic.local/events + eventSource.onOpen([](PsychicEventSourceClient* client) { + Serial.printf("[eventsource] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str()); client->send("Hello user!", NULL, millis(), 1000); }); - eventSource.onClose([](PsychicEventSourceClient *client) { - Serial.printf("[eventsource] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); - }); + eventSource.onClose([](PsychicEventSourceClient* client) { Serial.printf("[eventsource] connection #%u closed\n", client->socket()); }); server.on("/events", &eventSource); + + // example of using POST data inside the filter + // works: curl -F "secret=password" http://psychic.local/post-filter + // 404: curl -F "foo=bar" http://psychic.local/post-filter + server.on("/post-filter", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) { + String output; + output += "Secret: " + request->getParam("secret")->value() + "
\n"; + + return response->send(output.c_str()); + }) + ->addFilter([](PsychicRequest* request) { + request->loadParams(); + return request->hasParam("secret"); + }); + + server.begin(); } } @@ -570,14 +711,21 @@ char output[60]; void loop() { - if (millis() - lastUpdate > 2000) - { - sprintf(output, "Millis: %d\n", millis()); + if (millis() - lastUpdate > 1000) { + sprintf(output, "Millis: %lu\n", millis()); websocketHandler.sendAll(output); - sprintf(output, "%d", millis()); + sprintf(output, "%lu", millis()); eventSource.send(output, "millis", millis(), 0); lastUpdate = millis(); } + + // just some dev code to test that starting / stopping the server works okay. + // delay(5000); + // Serial.println("Stopping Server"); + // server.stop(); + // delay(5000); + // Serial.println("Starting Server"); + // server.start(); } \ No newline at end of file diff --git a/lib/PsychicHttp/examples/platformio/test/README b/lib/PsychicHttp/examples/platformio/test/README deleted file mode 100644 index 9b1e87b..0000000 --- a/lib/PsychicHttp/examples/platformio/test/README +++ /dev/null @@ -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 diff --git a/lib/PsychicHttp/examples/websockets/src/main.cpp b/lib/PsychicHttp/examples/websockets/src/main.cpp index a7c79b4..2b04d32 100644 --- a/lib/PsychicHttp/examples/websockets/src/main.cpp +++ b/lib/PsychicHttp/examples/websockets/src/main.cpp @@ -9,38 +9,39 @@ */ /********************************************************************************************** -* Note: this demo relies on various files to be uploaded on the LittleFS partition -* PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image -**********************************************************************************************/ + * Note: this demo relies on various files to be uploaded on the LittleFS partition + * PlatformIO -> Build Filesystem Image and then PlatformIO -> Upload Filesystem Image + **********************************************************************************************/ +#include "_secret.h" #include -#include -#include #include #include -#include -#include "_secret.h" +#include #include +#include +#include #include #ifndef WIFI_SSID #error "You need to enter your wifi credentials. Rename secret.h to _secret.h and enter your credentials there." #endif -//Enter your WIFI credentials in secret.h -const char *ssid = WIFI_SSID; -const char *password = WIFI_PASS; +// Enter your WIFI credentials in secret.h +const char* ssid = WIFI_SSID; +const char* password = WIFI_PASS; -//hostname for mdns (psychic.local) -const char *local_hostname = "psychic"; +// hostname for mdns (psychic.local) +const char* local_hostname = "psychic"; PsychicHttpServer server; PsychicWebSocketHandler websocketHandler; -typedef struct { - int socket; - char *buffer; - size_t len; +typedef struct +{ + int socket; + char* buffer; + size_t len; } WebsocketMessage; QueueHandle_t wsMessages; @@ -50,7 +51,7 @@ bool connectToWifi() Serial.print("[WiFi] Connecting to "); Serial.println(ssid); - //setup our wifi + // setup our wifi WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); @@ -113,7 +114,7 @@ void setup() Serial.begin(115200); delay(10); - //prepare our message queue of 10 messages + // prepare our message queue of 10 messages wsMessages = xQueueCreate(10, sizeof(WebsocketMessage)); if (wsMessages == 0) Serial.printf("Failed to create queue= %p\n", wsMessages); @@ -122,34 +123,33 @@ void setup() // To debug, please enable Core Debug Level to Verbose if (connectToWifi()) { - //set up our esp32 to listen on the local_hostname.local domain - if (!MDNS.begin(local_hostname)) { + // set up our esp32 to listen on the local_hostname.local domain + if (!MDNS.begin(local_hostname)) + { Serial.println("Error starting mDNS"); return; } MDNS.addService("http", "tcp", 80); - if(!LittleFS.begin()) + if (!LittleFS.begin()) { Serial.println("LittleFS Mount Failed. Do Platform -> Build Filesystem Image and Platform -> Upload Filesystem Image from VSCode"); return; } - server.listen(80); + // this is where our /index.html file lives + // curl -i http://psychic.local/ + server.serveStatic("/", LittleFS, "/www/"); - //this is where our /index.html file lives - // curl -i http://psychic.local/ - PsychicStaticFileHandler* handler = server.serveStatic("/", LittleFS, "/www/"); - - //a websocket echo server - // npm install -g wscat - // wscat -c ws://psychic.local/ws - websocketHandler.onOpen([](PsychicWebSocketClient *client) { - Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); - client->sendMessage("Hello!"); - }); - websocketHandler.onFrame([](PsychicWebSocketRequest *request, httpd_ws_frame *frame) - { + // a websocket echo server + // npm install -g wscat + // wscat -c ws://psychic.local/ws + websocketHandler.onOpen([](PsychicWebSocketClient* client) + { + Serial.printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString().c_str()); + client->sendMessage("Hello!"); }); + websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) + { Serial.printf("[socket] #%d sent: %s\n", request->client()->socket(), (char *)frame->payload); //we are allocating memory here, and the worker will free it @@ -181,11 +181,9 @@ void setup() if (!uxQueueSpacesAvailable(wsMessages)) return request->reply("Queue Full"); - return ESP_OK; - }); - websocketHandler.onClose([](PsychicWebSocketClient *client) { - Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); - }); + return ESP_OK; }); + websocketHandler.onClose([](PsychicWebSocketClient* client) + { Serial.printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString().c_str()); }); server.on("/ws", &websocketHandler); } } @@ -195,29 +193,30 @@ char output[60]; void loop() { - //process our websockets outside the callback. + // process our websockets outside the callback. WebsocketMessage message; while (xQueueReceive(wsMessages, &message, 0) == pdTRUE) { - //make sure our client is still good. - PsychicWebSocketClient *client = websocketHandler.getClient(message.socket); - if (client == NULL) { + // make sure our client is still good. + PsychicWebSocketClient* client = websocketHandler.getClient(message.socket); + if (client == NULL) + { Serial.printf("[socket] client #%d bad, bailing\n", message.socket); return; } - //echo it back to the client. - //alternatively, this is where you would deserialize a json message, parse it, and generate a response if needed + // echo it back to the client. + // alternatively, this is where you would deserialize a json message, parse it, and generate a response if needed client->sendMessage(HTTPD_WS_TYPE_TEXT, message.buffer, message.len); - //make sure to release our memory! + // make sure to release our memory! free(message.buffer); } - //send a periodic update to all clients + // send a periodic update to all clients if (millis() - lastUpdate > 2000) { - sprintf(output, "Millis: %d\n", millis()); + sprintf(output, "Millis: %lu\n", millis()); websocketHandler.sendAll(output); lastUpdate = millis(); diff --git a/lib/PsychicHttp/library.json b/lib/PsychicHttp/library.json index e732d31..ba1c627 100644 --- a/lib/PsychicHttp/library.json +++ b/lib/PsychicHttp/library.json @@ -1,15 +1,13 @@ { "name": "PsychicHttp", - "version": "1.2.1", + "version": "2.0.0", "description": "Arduino style wrapper around ESP-IDF HTTP library. HTTP server with SSL + websockets. Works on esp32 and probably esp8266", "keywords": "network,http,https,tcp,ssl,tls,websocket,espasyncwebserver", - "repository": - { + "repository": { "type": "git", "url": "https://github.com/hoeken/PsychicHttp" }, - "authors": - [ + "authors": [ { "name": "Zach Hoeken", "email": "hoeken@gmail.com", @@ -36,8 +34,8 @@ }, { "owner": "plageoj", - "name" : "UrlEncode", - "version" : "^1.0.1" + "name": "UrlEncode", + "version": "^1.0.1" } ], "export": { @@ -50,4 +48,4 @@ "README.md" ] } -} +} \ No newline at end of file diff --git a/lib/PsychicHttp/library.properties b/lib/PsychicHttp/library.properties index 885afb5..fe11bc3 100644 --- a/lib/PsychicHttp/library.properties +++ b/lib/PsychicHttp/library.properties @@ -1,5 +1,5 @@ name=PsychicHttp -version=1.2.1 +version=2.0.0 author=Zach Hoeken maintainer=Zach Hoeken sentence=PsychicHttp is a robust webserver that supports http/https + websockets. diff --git a/lib/PsychicHttp/middleware.md b/lib/PsychicHttp/middleware.md new file mode 100644 index 0000000..dc9a18c --- /dev/null +++ b/lib/PsychicHttp/middleware.md @@ -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 diff --git a/lib/PsychicHttp/partitions-4MB.csv b/lib/PsychicHttp/partitions-4MB.csv new file mode 100644 index 0000000..75efc35 --- /dev/null +++ b/lib/PsychicHttp/partitions-4MB.csv @@ -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 , diff --git a/lib/PsychicHttp/platformio.ini b/lib/PsychicHttp/platformio.ini new file mode 100644 index 0000000..ef564d7 --- /dev/null +++ b/lib/PsychicHttp/platformio.ini @@ -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} diff --git a/lib/PsychicHttp/src/ChunkPrinter.cpp b/lib/PsychicHttp/src/ChunkPrinter.cpp index b3f8713..0275113 100644 --- a/lib/PsychicHttp/src/ChunkPrinter.cpp +++ b/lib/PsychicHttp/src/ChunkPrinter.cpp @@ -1,12 +1,12 @@ #include "ChunkPrinter.h" -ChunkPrinter::ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len) : - _response(response), - _buffer(buffer), - _length(len), - _pos(0) -{} +ChunkPrinter::ChunkPrinter(PsychicResponse* response, uint8_t* buffer, size_t len) : _response(response), + _buffer(buffer), + _length(len), + _pos(0) +{ +} ChunkPrinter::~ChunkPrinter() { @@ -16,34 +16,34 @@ ChunkPrinter::~ChunkPrinter() size_t ChunkPrinter::write(uint8_t c) { esp_err_t err; - - //if we're full, send a chunk + + // if we're full, send a chunk if (_pos == _length) { _pos = 0; err = _response->sendChunk(_buffer, _length); - + if (err != ESP_OK) return 0; - } + } _buffer[_pos] = c; _pos++; return 1; } -size_t ChunkPrinter::write(const uint8_t *buffer, size_t size) +size_t ChunkPrinter::write(const uint8_t* buffer, size_t size) { size_t written = 0; - + while (written < size) { size_t space = _length - _pos; size_t blockSize = std::min(space, size - written); - + memcpy(_buffer + _pos, buffer + written, blockSize); _pos += blockSize; - + if (_pos == _length) { _pos = 0; @@ -51,7 +51,7 @@ size_t ChunkPrinter::write(const uint8_t *buffer, size_t size) if (_response->sendChunk(_buffer, _length) != ESP_OK) return written; } - written += blockSize; //Update if sent correctly. + written += blockSize; // Update if sent correctly. } return written; } @@ -65,18 +65,19 @@ void ChunkPrinter::flush() } } -size_t ChunkPrinter::copyFrom(Stream &stream) +size_t ChunkPrinter::copyFrom(Stream& stream) { size_t count = 0; - while (stream.available()){ - + while (stream.available()) + { + if (_pos == _length) { _response->sendChunk(_buffer, _length); _pos = 0; } - + size_t readBytes = stream.readBytes(_buffer + _pos, _length - _pos); _pos += readBytes; count += readBytes; diff --git a/lib/PsychicHttp/src/ChunkPrinter.h b/lib/PsychicHttp/src/ChunkPrinter.h index f63fac0..3f0c1f5 100644 --- a/lib/PsychicHttp/src/ChunkPrinter.h +++ b/lib/PsychicHttp/src/ChunkPrinter.h @@ -7,19 +7,19 @@ class ChunkPrinter : public Print { private: - PsychicResponse *_response; - uint8_t *_buffer; + PsychicResponse* _response; + uint8_t* _buffer; size_t _length; size_t _pos; public: - ChunkPrinter(PsychicResponse *response, uint8_t *buffer, size_t len); + ChunkPrinter(PsychicResponse* response, uint8_t* buffer, size_t len); ~ChunkPrinter(); - - size_t write(uint8_t c) override; - size_t write(const uint8_t *buffer, size_t size) override; - size_t copyFrom(Stream &stream); + size_t write(uint8_t c) override; + size_t write(const uint8_t* buffer, size_t size) override; + + size_t copyFrom(Stream& stream); void flush() override; }; diff --git a/lib/PsychicHttp/src/MultipartProcessor.cpp b/lib/PsychicHttp/src/MultipartProcessor.cpp new file mode 100644 index 0000000..8560ba5 --- /dev/null +++ b/lib/PsychicHttp/src/MultipartProcessor.cpp @@ -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); + } + } +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/MultipartProcessor.h b/lib/PsychicHttp/src/MultipartProcessor.h new file mode 100644 index 0000000..30db540 --- /dev/null +++ b/lib/PsychicHttp/src/MultipartProcessor.h @@ -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 \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicClient.cpp b/lib/PsychicHttp/src/PsychicClient.cpp index fd7820d..ff6bd49 100644 --- a/lib/PsychicHttp/src/PsychicClient.cpp +++ b/lib/PsychicHttp/src/PsychicClient.cpp @@ -2,21 +2,24 @@ #include "PsychicHttpServer.h" #include -PsychicClient::PsychicClient(httpd_handle_t server, int socket) : - _server(server), - _socket(socket), - _friend(NULL), - isNew(false) -{} - -PsychicClient::~PsychicClient() { +PsychicClient::PsychicClient(httpd_handle_t server, int socket) : _server(server), + _socket(socket), + _friend(NULL), + isNew(false) +{ } -httpd_handle_t PsychicClient::server() { +PsychicClient::~PsychicClient() +{ +} + +httpd_handle_t PsychicClient::server() +{ return _server; } -int PsychicClient::socket() { +int PsychicClient::socket() +{ return _socket; } @@ -24,49 +27,67 @@ int PsychicClient::socket() { esp_err_t PsychicClient::close() { esp_err_t err = httpd_sess_trigger_close(_server, _socket); - //PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list. + // PsychicHttpServer::closeCallback(_server, _socket); // call this immediately so the client is taken off the list. return err; } IPAddress PsychicClient::localIP() { - IPAddress address(0,0,0,0); + IPAddress address(0, 0, 0, 0); char ipstr[INET6_ADDRSTRLEN]; - struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing + struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing socklen_t addr_size = sizeof(addr); - if (getsockname(_socket, (struct sockaddr *)&addr, &addr_size) < 0) { + if (getsockname(_socket, (struct sockaddr*)&addr, &addr_size) < 0) { ESP_LOGE(PH_TAG, "Error getting client IP"); return address; } // Convert to IPv4 string inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr)); - //ESP_LOGD(PH_TAG, "Client Local IP => %s", ipstr); + // ESP_LOGD(PH_TAG, "Client Local IP => %s", ipstr); address.fromString(ipstr); return address; } +uint16_t PsychicClient::localPort() const +{ + struct sockaddr_storage addr; + socklen_t len = sizeof addr; + getsockname(_socket, (struct sockaddr*)&addr, &len); + struct sockaddr_in* s = (struct sockaddr_in*)&addr; + return ntohs(s->sin_port); +} + IPAddress PsychicClient::remoteIP() { - IPAddress address(0,0,0,0); + IPAddress address(0, 0, 0, 0); char ipstr[INET6_ADDRSTRLEN]; - struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing + struct sockaddr_in6 addr; // esp_http_server uses IPv6 addressing socklen_t addr_size = sizeof(addr); - if (getpeername(_socket, (struct sockaddr *)&addr, &addr_size) < 0) { + if (getpeername(_socket, (struct sockaddr*)&addr, &addr_size) < 0) { ESP_LOGE(PH_TAG, "Error getting client IP"); return address; } // Convert to IPv4 string inet_ntop(AF_INET, &addr.sin6_addr.un.u32_addr[3], ipstr, sizeof(ipstr)); - //ESP_LOGD(PH_TAG, "Client Remote IP => %s", ipstr); + // ESP_LOGD(PH_TAG, "Client Remote IP => %s", ipstr); address.fromString(ipstr); return address; -} \ No newline at end of file +} + +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); +} diff --git a/lib/PsychicHttp/src/PsychicClient.h b/lib/PsychicHttp/src/PsychicClient.h index b823df7..5fe3e6b 100644 --- a/lib/PsychicHttp/src/PsychicClient.h +++ b/lib/PsychicHttp/src/PsychicClient.h @@ -4,10 +4,12 @@ #include "PsychicCore.h" /* -* PsychicClient :: Generic wrapper around the ESP-IDF socket -*/ + * PsychicClient :: Generic wrapper around the ESP-IDF socket + */ + +class PsychicClient +{ -class PsychicClient { protected: httpd_handle_t _server; int _socket; @@ -16,9 +18,9 @@ class PsychicClient { PsychicClient(httpd_handle_t server, int socket); ~PsychicClient(); - //no idea if this is the right way to do it or not, but lets see. - //pointer to our derived class (eg. PsychicWebSocketConnection) - void *_friend; + // no idea if this is the right way to do it or not, but lets see. + // pointer to our derived class (eg. PsychicWebSocketConnection) + void* _friend; bool isNew = false; @@ -29,7 +31,9 @@ class PsychicClient { esp_err_t close(); IPAddress localIP(); + uint16_t localPort() const; IPAddress remoteIP(); + uint16_t remotePort() const; }; #endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicCore.h b/lib/PsychicHttp/src/PsychicCore.h index fe88b38..b43bdfb 100644 --- a/lib/PsychicHttp/src/PsychicCore.h +++ b/lib/PsychicHttp/src/PsychicCore.h @@ -3,17 +3,8 @@ #define PH_TAG "psychic" -//version numbers -#define PSYCHIC_HTTP_VERSION_MAJOR 1 -#define PSYCHIC_HTTP_VERSION_MINOR 1 -#define PSYCHIC_HTTP_VERSION_PATCH 0 - -#ifndef MAX_COOKIE_SIZE - #define MAX_COOKIE_SIZE 512 -#endif - #ifndef FILE_CHUNK_SIZE - #define FILE_CHUNK_SIZE 8*1024 + #define FILE_CHUNK_SIZE 8 * 1024 #endif #ifndef STREAM_CHUNK_SIZE @@ -21,87 +12,93 @@ #endif #ifndef MAX_UPLOAD_SIZE - #define MAX_UPLOAD_SIZE (2048*1024) // 2MB + #define MAX_UPLOAD_SIZE (2048 * 1024) // 2MB #endif #ifndef MAX_REQUEST_BODY_SIZE - #define MAX_REQUEST_BODY_SIZE (16*1024) //16K + #define MAX_REQUEST_BODY_SIZE (16 * 1024) // 16K #endif #ifdef ARDUINO #include #endif -#include -#include -#include -#include -#include "esp_random.h" -#include "MD5Builder.h" -#include #include "FS.h" +#include "MD5Builder.h" +#include "esp_random.h" #include +#include +#include +#include +#include +#include -enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH }; +#ifdef PSY_DEVMODE + #include "ArduinoTrace.h" +#endif + +enum HTTPAuthMethod { + BASIC_AUTH, + DIGEST_AUTH +}; String urlDecode(const char* encoded); class PsychicHttpServer; class PsychicRequest; +class PsychicResponse; class PsychicWebSocketRequest; class PsychicClient; -//filter function definition -typedef std::function PsychicRequestFilterFunction; +// filter function definition +typedef std::function PsychicRequestFilterFunction; -//client connect callback -typedef std::function PsychicClientCallback; +// middleware function definition +typedef std::function PsychicMiddlewareNext; +typedef std::function PsychicMiddlewareCallback; -//callback definitions -typedef std::function PsychicHttpRequestCallback; -typedef std::function PsychicJsonRequestCallback; +// client connect callback +typedef std::function PsychicClientCallback; + +// callback definitions +typedef std::function PsychicHttpRequestCallback; +typedef std::function PsychicJsonRequestCallback; +typedef std::function PsychicUploadCallback; struct HTTPHeader { - char * field; - char * value; + String field; + String value; }; -class DefaultHeaders { - std::list _headers; +class DefaultHeaders +{ + std::list _headers; -public: - DefaultHeaders() {} + public: + DefaultHeaders() {} - void addHeader(const String& field, const String& value) - { - addHeader(field.c_str(), value.c_str()); - } + void addHeader(const String& field, const String& value) + { + _headers.push_back({field, value}); + } - void addHeader(const char * field, const char * value) - { - HTTPHeader header; + void addHeader(const char* field, const char* value) + { + _headers.push_back({field, value}); + } - //these are just going to stick around forever. - header.field =(char *)malloc(strlen(field)+1); - header.value = (char *)malloc(strlen(value)+1); + const std::list& getHeaders() { return _headers; } - strlcpy(header.field, field, strlen(field)+1); - strlcpy(header.value, value, strlen(value)+1); + // delete the copy constructor, singleton class + DefaultHeaders(DefaultHeaders const&) = delete; + DefaultHeaders& operator=(DefaultHeaders const&) = delete; - _headers.push_back(header); - } - - const std::list& getHeaders() { return _headers; } - - //delete the copy constructor, singleton class - DefaultHeaders(DefaultHeaders const &) = delete; - DefaultHeaders &operator=(DefaultHeaders const &) = delete; - - //single static class interface - static DefaultHeaders &Instance() { - static DefaultHeaders instance; - return instance; - } + // single static class interface + static DefaultHeaders& Instance() + { + static DefaultHeaders instance; + return instance; + } }; -#endif //PsychicCore_h \ No newline at end of file +#endif // PsychicCore_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicEndpoint.cpp b/lib/PsychicHttp/src/PsychicEndpoint.cpp index e692658..7e9e1c1 100644 --- a/lib/PsychicHttp/src/PsychicEndpoint.cpp +++ b/lib/PsychicHttp/src/PsychicEndpoint.cpp @@ -1,90 +1,140 @@ #include "PsychicEndpoint.h" #include "PsychicHttpServer.h" -PsychicEndpoint::PsychicEndpoint() : - _server(NULL), - _uri(""), - _method(HTTP_GET), - _handler(NULL) +PsychicEndpoint::PsychicEndpoint() : _server(NULL), + _uri(""), + _method(HTTP_GET), + _handler(NULL) { } -PsychicEndpoint::PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri) : - _server(server), - _uri(uri), - _method(method), - _handler(NULL) +PsychicEndpoint::PsychicEndpoint(PsychicHttpServer* server, int method, const char* uri) : _server(server), + _uri(uri), + _method(method), + _handler(NULL) { } -PsychicEndpoint * PsychicEndpoint::setHandler(PsychicHandler *handler) +PsychicEndpoint* PsychicEndpoint::setHandler(PsychicHandler* handler) { - //clean up old / default handler + // clean up old / default handler if (_handler != NULL) delete _handler; - //get our new pointer + // get our new pointer _handler = handler; - //keep a pointer to the server + // keep a pointer to the server _handler->_server = _server; return this; } -PsychicHandler * PsychicEndpoint::handler() +PsychicHandler* PsychicEndpoint::handler() { return _handler; } -String PsychicEndpoint::uri() { +String PsychicEndpoint::uri() +{ return _uri; } -esp_err_t PsychicEndpoint::requestCallback(httpd_req_t *req) +esp_err_t PsychicEndpoint::requestCallback(httpd_req_t* req) { - #ifdef ENABLE_ASYNC - if (is_on_async_worker_thread() == false) { - if (submit_async_req(req, PsychicEndpoint::requestCallback) == ESP_OK) { - return ESP_OK; - } else { - httpd_resp_set_status(req, "503 Busy"); - httpd_resp_sendstr(req, "No workers available. Server busy."); - return ESP_OK; - } +#ifdef ENABLE_ASYNC + if (is_on_async_worker_thread() == false) { + if (submit_async_req(req, PsychicEndpoint::requestCallback) == ESP_OK) { + return ESP_OK; + } else { + httpd_resp_set_status(req, "503 Busy"); + httpd_resp_sendstr(req, "No workers available. Server busy."); + return ESP_OK; } - #endif + } +#endif - PsychicEndpoint *self = (PsychicEndpoint *)req->user_ctx; - PsychicHandler *handler = self->handler(); + PsychicEndpoint* self = (PsychicEndpoint*)req->user_ctx; PsychicRequest request(self->_server, req); - //make sure we have a handler - if (handler != NULL) - { - if (handler->filter(&request) && handler->canHandle(&request)) - { - //check our credentials - if (handler->needsAuthentication(&request)) - return handler->authenticate(&request); + esp_err_t err = self->process(&request); - //pass it to our handler - return handler->handleRequest(&request); - } - //pass it to our generic handlers - else - return PsychicHttpServer::notFoundHandler(req, HTTPD_500_INTERNAL_SERVER_ERROR); - } + if (err == HTTPD_404_NOT_FOUND) + return PsychicHttpServer::requestHandler(req); + + if (err == ESP_ERR_HTTPD_INVALID_REQ) + return request.response()->error(HTTPD_500_INTERNAL_SERVER_ERROR, "No handler registered."); + + return err; +} + +bool PsychicEndpoint::matches(const char* uri) +{ + // we only want to match the path, no GET strings + char* ptr; + size_t position = 0; + + // look for a ? and set our path length to that, + ptr = strchr(uri, '?'); + if (ptr != NULL) + position = (size_t)(int)(ptr - uri); + // or use the whole uri if not found else - return request.reply(500, "text/html", "No handler registered."); + position = strlen(uri); + + // do we have a per-endpoint match function + if (this->getURIMatchFunction() != NULL) { + // ESP_LOGD(PH_TAG, "Match? %s == %s (%d)", _uri.c_str(), uri, position); + return this->getURIMatchFunction()(_uri.c_str(), uri, (size_t)position); + } + // do we have a global match function + if (_server->getURIMatchFunction() != NULL) { + // ESP_LOGD(PH_TAG, "Match? %s == %s (%d)", _uri.c_str(), uri, position); + return _server->getURIMatchFunction()(_uri.c_str(), uri, (size_t)position); + } else { + ESP_LOGE(PH_TAG, "No uri matching function set"); + return false; + } } -PsychicEndpoint* PsychicEndpoint::setFilter(PsychicRequestFilterFunction fn) { - _handler->setFilter(fn); +httpd_uri_match_func_t PsychicEndpoint::getURIMatchFunction() +{ + return _uri_match_fn; +} + +void PsychicEndpoint::setURIMatchFunction(httpd_uri_match_func_t match_fn) +{ + _uri_match_fn = match_fn; +} + +PsychicEndpoint* PsychicEndpoint::addFilter(PsychicRequestFilterFunction fn) +{ + _handler->addFilter(fn); return this; } -PsychicEndpoint* PsychicEndpoint::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) { - _handler->setAuthentication(username, password, method, realm, authFailMsg); +PsychicEndpoint* PsychicEndpoint::addMiddleware(PsychicMiddleware* middleware) +{ + _handler->addMiddleware(middleware); return this; -}; \ No newline at end of file +} + +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; +} diff --git a/lib/PsychicHttp/src/PsychicEndpoint.h b/lib/PsychicHttp/src/PsychicEndpoint.h index 540b294..3ddebd4 100644 --- a/lib/PsychicHttp/src/PsychicEndpoint.h +++ b/lib/PsychicHttp/src/PsychicEndpoint.h @@ -4,6 +4,7 @@ #include "PsychicCore.h" class PsychicHandler; +class PsychicMiddleware; #ifdef ENABLE_ASYNC #include "async_worker.h" @@ -11,27 +12,39 @@ class PsychicHandler; class PsychicEndpoint { - friend PsychicHttpServer; + friend PsychicHttpServer; private: - PsychicHttpServer *_server; + PsychicHttpServer* _server; String _uri; - http_method _method; - PsychicHandler *_handler; + int _method; + PsychicHandler* _handler; + httpd_uri_match_func_t _uri_match_fn = nullptr; // use this change the endpoint matching function. public: PsychicEndpoint(); - PsychicEndpoint(PsychicHttpServer *server, http_method method, const char * uri); + PsychicEndpoint(PsychicHttpServer* server, int method, const char* uri); - PsychicEndpoint *setHandler(PsychicHandler *handler); - PsychicHandler *handler(); + PsychicEndpoint* setHandler(PsychicHandler* handler); + PsychicHandler* handler(); - PsychicEndpoint* setFilter(PsychicRequestFilterFunction fn); - PsychicEndpoint* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = ""); + httpd_uri_match_func_t getURIMatchFunction(); + void setURIMatchFunction(httpd_uri_match_func_t match_fn); + + bool matches(const char* uri); + + // called to process this endpoint with its middleware chain + esp_err_t process(PsychicRequest* request); + + PsychicEndpoint* addFilter(PsychicRequestFilterFunction fn); + + PsychicEndpoint* addMiddleware(PsychicMiddleware* middleware); + PsychicEndpoint* addMiddleware(PsychicMiddlewareCallback fn); + void removeMiddleware(PsychicMiddleware* middleware); String uri(); - static esp_err_t requestCallback(httpd_req_t *req); + static esp_err_t requestCallback(httpd_req_t* req); }; #endif // PsychicEndpoint_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicEventSource.cpp b/lib/PsychicHttp/src/PsychicEventSource.cpp index 25f947d..f6347dc 100644 --- a/lib/PsychicHttp/src/PsychicEventSource.cpp +++ b/lib/PsychicHttp/src/PsychicEventSource.cpp @@ -19,83 +19,88 @@ */ #include "PsychicEventSource.h" +#include /*****************************************/ // PsychicEventSource - Handler /*****************************************/ -PsychicEventSource::PsychicEventSource() : - PsychicHandler(), - _onOpen(NULL), - _onClose(NULL) -{} - -PsychicEventSource::~PsychicEventSource() { +PsychicEventSource::PsychicEventSource() : PsychicHandler(), + _onOpen(NULL), + _onClose(NULL) +{ } -PsychicEventSourceClient * PsychicEventSource::getClient(int socket) +PsychicEventSource::~PsychicEventSource() { - PsychicClient *client = PsychicHandler::getClient(socket); +} + +PsychicEventSourceClient* PsychicEventSource::getClient(int socket) +{ + PsychicClient* client = PsychicHandler::getClient(socket); if (client == NULL) return NULL; - return (PsychicEventSourceClient *)client->_friend; + return (PsychicEventSourceClient*)client->_friend; } -PsychicEventSourceClient * PsychicEventSource::getClient(PsychicClient *client) { +PsychicEventSourceClient* PsychicEventSource::getClient(PsychicClient* client) +{ return getClient(client->socket()); } -esp_err_t PsychicEventSource::handleRequest(PsychicRequest *request) +esp_err_t PsychicEventSource::handleRequest(PsychicRequest* request, PsychicResponse* resp) { - //start our open ended HTTP response - PsychicEventSourceResponse response(request); + // start our open ended HTTP response + PsychicEventSourceResponse response(resp); esp_err_t err = response.send(); - //lookup our client - PsychicClient *client = checkForNewClient(request->client()); - if (client->isNew) - { - //did we get our last id? - if(request->hasHeader("Last-Event-ID")) - { - PsychicEventSourceClient *buddy = getClient(client); + // lookup our client + PsychicClient* client = checkForNewClient(request->client()); + if (client->isNew) { + // did we get our last id? + if (request->hasHeader("Last-Event-ID")) { + PsychicEventSourceClient* buddy = getClient(client); buddy->_lastId = atoi(request->header("Last-Event-ID").c_str()); } - //let our handler know. + // let our handler know. openCallback(client); } return err; } -PsychicEventSource * PsychicEventSource::onOpen(PsychicEventSourceClientCallback fn) { +PsychicEventSource* PsychicEventSource::onOpen(PsychicEventSourceClientCallback fn) +{ _onOpen = fn; return this; } -PsychicEventSource * PsychicEventSource::onClose(PsychicEventSourceClientCallback fn) { +PsychicEventSource* PsychicEventSource::onClose(PsychicEventSourceClientCallback fn) +{ _onClose = fn; return this; } -void PsychicEventSource::addClient(PsychicClient *client) { +void PsychicEventSource::addClient(PsychicClient* client) +{ client->_friend = new PsychicEventSourceClient(client); PsychicHandler::addClient(client); } -void PsychicEventSource::removeClient(PsychicClient *client) { +void PsychicEventSource::removeClient(PsychicClient* client) +{ PsychicHandler::removeClient(client); delete (PsychicEventSourceClient*)client->_friend; client->_friend = NULL; } -void PsychicEventSource::openCallback(PsychicClient *client) { - PsychicEventSourceClient *buddy = getClient(client); - if (buddy == NULL) - { +void PsychicEventSource::openCallback(PsychicClient* client) +{ + PsychicEventSourceClient* buddy = getClient(client); + if (buddy == NULL) { return; } @@ -103,10 +108,10 @@ void PsychicEventSource::openCallback(PsychicClient *client) { _onOpen(buddy); } -void PsychicEventSource::closeCallback(PsychicClient *client) { - PsychicEventSourceClient *buddy = getClient(client); - if (buddy == NULL) - { +void PsychicEventSource::closeCallback(PsychicClient* client) +{ + PsychicEventSourceClient* buddy = getClient(client); + if (buddy == NULL) { return; } @@ -114,11 +119,13 @@ void PsychicEventSource::closeCallback(PsychicClient *client) { _onClose(getClient(buddy)); } -void PsychicEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) +void PsychicEventSource::send(const char* message, const char* event, uint32_t id, uint32_t reconnect) { String ev = generateEventMessage(message, event, id, reconnect); for(PsychicClient *c : _clients) { - ((PsychicEventSourceClient*)c->_friend)->sendEvent(ev.c_str()); + if (c && c->_friend) { + ((PsychicEventSourceClient*)c->_friend)->sendEvent(ev.c_str()); + } } } @@ -126,58 +133,135 @@ void PsychicEventSource::send(const char *message, const char *event, uint32_t i // PsychicEventSourceClient /*****************************************/ -PsychicEventSourceClient::PsychicEventSourceClient(PsychicClient *client) : - PsychicClient(client->server(), client->socket()), - _lastId(0) +PsychicEventSourceClient::PsychicEventSourceClient(PsychicClient* client) : PsychicClient(client->server(), client->socket()), + _lastId(0) { } -PsychicEventSourceClient::~PsychicEventSourceClient(){ +PsychicEventSourceClient::~PsychicEventSourceClient() +{ } -void PsychicEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){ +void PsychicEventSourceClient::send(const char* message, const char* event, uint32_t id, uint32_t reconnect) +{ String ev = generateEventMessage(message, event, id, reconnect); sendEvent(ev.c_str()); } -void PsychicEventSourceClient::sendEvent(const char *event) { - int result; - do { - result = httpd_socket_send(this->server(), this->socket(), event, strlen(event), 0); - } while (result == HTTPD_SOCK_ERR_TIMEOUT); +void PsychicEventSourceClient::sendEvent(const char* event) +{ + _sendEventAsync(this->server(), this->socket(), event, strlen(event)); +} - //if (result < 0) - //error log here +esp_err_t PsychicEventSourceClient::_sendEventAsync(httpd_handle_t handle, int socket, const char* event, size_t len) +{ + // create the transfer object + async_event_transfer_t* transfer = (async_event_transfer_t*)calloc(1, sizeof(async_event_transfer_t)); + if (transfer == NULL) { + return ESP_ERR_NO_MEM; + } + + // populate it + transfer->arg = this; + transfer->callback = _sendEventSentCallback; + transfer->handle = handle; + transfer->socket = socket; + transfer->len = len; + + // allocate for event text + transfer->event = (char*)malloc(len); + if (transfer->event == NULL) { + free(transfer); + return ESP_ERR_NO_MEM; + } + + // copy over the event data + memcpy(transfer->event, event, len); + + // queue it. + esp_err_t err = httpd_queue_work(handle, _sendEventWorkCallback, transfer); + + // cleanup + if (err) { + free(transfer->event); + free(transfer); + return err; + } + + return ESP_OK; +} + +void PsychicEventSourceClient::_sendEventWorkCallback(void* arg) +{ + async_event_transfer_t* trans = (async_event_transfer_t*)arg; + + // omg the error is overloaded with the number of bytes sent! + esp_err_t err = httpd_socket_send(trans->handle, trans->socket, trans->event, trans->len, 0); + if (err == trans->len) + err = ESP_OK; + + if (trans->callback) + trans->callback(err, trans->socket, trans->arg); + + // free our memory + free(trans->event); + free(trans); +} + +void PsychicEventSourceClient::_sendEventSentCallback(esp_err_t err, int socket, void* arg) +{ + // PsychicEventSourceClient* client = (PsychicEventSourceClient*)arg; + + if (err == ESP_OK) + return; + else if (err == ESP_FAIL) + ESP_LOGE(PH_TAG, "EventSource: send - socket error (#%d)", socket); + else if (err == ESP_ERR_INVALID_STATE) + ESP_LOGE(PH_TAG, "EventSource: Handshake was already done beforehand (#%d)", socket); + else if (err == ESP_ERR_INVALID_ARG) + ESP_LOGE(PH_TAG, "EventSource: Argument is invalid (#%d)", socket); + else if (err == HTTPD_SOCK_ERR_TIMEOUT) + ESP_LOGE(PH_TAG, "EventSource: Socket timeout (#%d)", socket); + else if (err == HTTPD_SOCK_ERR_INVALID) + ESP_LOGE(PH_TAG, "EventSource: Invalid socket (#%d)", socket); + else if (err == HTTPD_SOCK_ERR_FAIL) + ESP_LOGE(PH_TAG, "EventSource: Socket fail (#%d)", socket); + else + ESP_LOGE(PH_TAG, "EventSource: %#06x %s (#%d)", (int)err, esp_err_to_name(err), socket); } /*****************************************/ // PsychicEventSourceResponse /*****************************************/ -PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicRequest *request) - : PsychicResponse(request) +PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicResponse* response) : PsychicResponseDelegate(response) { } -esp_err_t PsychicEventSourceResponse::send() { +esp_err_t PsychicEventSourceResponse::send() +{ + _response->addHeader("Content-Type", "text/event-stream"); + _response->addHeader("Cache-Control", "no-cache"); + _response->addHeader("Connection", "keep-alive"); - //build our main header + // build our main header String out = String(); out.concat("HTTP/1.1 200 OK\r\n"); - out.concat("Content-Type: text/event-stream\r\n"); - out.concat("Cache-Control: no-cache\r\n"); - out.concat("Connection: keep-alive\r\n"); - //get our global headers out of the way first - for (HTTPHeader header : DefaultHeaders::Instance().getHeaders()) - out.concat(String(header.field) + ": " + String(header.value) + "\r\n"); + // get our global headers out of the way first + for (auto& header : DefaultHeaders::Instance().getHeaders()) + out.concat(header.field + ": " + header.value + "\r\n"); - //separator + // now do our individual headers + for (auto& header : _response->headers()) + out.concat(header.field + ": " + header.value + "\r\n"); + + // separator out.concat("\r\n"); int result; do { - result = httpd_send(_request->request(), out.c_str(), out.length()); + result = httpd_send(request(), out.c_str(), out.length()); } while (result == HTTPD_SOCK_ERR_TIMEOUT); if (result < 0) @@ -193,28 +277,29 @@ esp_err_t PsychicEventSourceResponse::send() { // Event Message Generator /*****************************************/ -String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect) { +String generateEventMessage(const char* message, const char* event, uint32_t id, uint32_t reconnect) +{ String ev = ""; - if(reconnect){ + if (reconnect) { ev += "retry: "; ev += String(reconnect); ev += "\r\n"; } - if(id){ + if (id) { ev += "id: "; ev += String(id); ev += "\r\n"; } - if(event != NULL){ + if (event != NULL) { ev += "event: "; ev += String(event); ev += "\r\n"; } - if(message != NULL){ + if (message != NULL) { ev += "data: "; ev += String(message); ev += "\r\n"; diff --git a/lib/PsychicHttp/src/PsychicEventSource.h b/lib/PsychicHttp/src/PsychicEventSource.h index ab31980..0f72dc5 100644 --- a/lib/PsychicHttp/src/PsychicEventSource.h +++ b/lib/PsychicHttp/src/PsychicEventSource.h @@ -20,9 +20,9 @@ #ifndef PsychicEventSource_H_ #define PsychicEventSource_H_ +#include "PsychicClient.h" #include "PsychicCore.h" #include "PsychicHandler.h" -#include "PsychicClient.h" #include "PsychicResponse.h" class PsychicEventSource; @@ -30,24 +30,38 @@ class PsychicEventSourceResponse; class PsychicEventSourceClient; class PsychicResponse; -typedef std::function PsychicEventSourceClientCallback; +typedef std::function PsychicEventSourceClientCallback; -class PsychicEventSourceClient : public PsychicClient { - friend PsychicEventSource; +typedef struct { + httpd_handle_t handle; + int socket; + char* event; + size_t len; + transfer_complete_cb callback; + void* arg; +} async_event_transfer_t; + +class PsychicEventSourceClient : public PsychicClient +{ + friend PsychicEventSource; protected: uint32_t _lastId; + esp_err_t _sendEventAsync(httpd_handle_t handle, int socket, const char* event, size_t len); + static void _sendEventWorkCallback(void* arg); + static void _sendEventSentCallback(esp_err_t err, int socket, void* arg); public: - PsychicEventSourceClient(PsychicClient *client); + PsychicEventSourceClient(PsychicClient* client); ~PsychicEventSourceClient(); uint32_t lastId() const { return _lastId; } - void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); - void sendEvent(const char *event); + void send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0); + void sendEvent(const char* event); }; -class PsychicEventSource : public PsychicHandler { +class PsychicEventSource : public PsychicHandler +{ private: PsychicEventSourceClientCallback _onOpen; PsychicEventSourceClientCallback _onClose; @@ -56,27 +70,28 @@ class PsychicEventSource : public PsychicHandler { PsychicEventSource(); ~PsychicEventSource(); - PsychicEventSourceClient * getClient(int socket) override; - PsychicEventSourceClient * getClient(PsychicClient *client) override; - void addClient(PsychicClient *client) override; - void removeClient(PsychicClient *client) override; - void openCallback(PsychicClient *client) override; - void closeCallback(PsychicClient *client) override; + PsychicEventSourceClient* getClient(int socket) override; + PsychicEventSourceClient* getClient(PsychicClient* client) override; + void addClient(PsychicClient* client) override; + void removeClient(PsychicClient* client) override; + void openCallback(PsychicClient* client) override; + void closeCallback(PsychicClient* client) override; - PsychicEventSource *onOpen(PsychicEventSourceClientCallback fn); - PsychicEventSource *onClose(PsychicEventSourceClientCallback fn); + PsychicEventSource* onOpen(PsychicEventSourceClientCallback fn); + PsychicEventSource* onClose(PsychicEventSourceClientCallback fn); - esp_err_t handleRequest(PsychicRequest *request) override final; + esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override final; - void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); + void send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0); }; -class PsychicEventSourceResponse: public PsychicResponse { +class PsychicEventSourceResponse : public PsychicResponseDelegate +{ public: - PsychicEventSourceResponse(PsychicRequest *request); - virtual esp_err_t send() override; + PsychicEventSourceResponse(PsychicResponse* response); + esp_err_t send(); }; -String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect); +String generateEventMessage(const char* message, const char* event, uint32_t id, uint32_t reconnect); #endif /* PsychicEventSource_H_ */ \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicFileResponse.cpp b/lib/PsychicHttp/src/PsychicFileResponse.cpp index 5fc9822..656a173 100644 --- a/lib/PsychicHttp/src/PsychicFileResponse.cpp +++ b/lib/PsychicHttp/src/PsychicFileResponse.cpp @@ -1,97 +1,116 @@ #include "PsychicFileResponse.h" -#include "PsychicResponse.h" #include "PsychicRequest.h" +#include "PsychicResponse.h" - -PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path, const String& contentType, bool download) - : PsychicResponse(request) { +PsychicFileResponse::PsychicFileResponse(PsychicResponse* response, FS& fs, const String& path, const String& contentType, bool download) : PsychicResponseDelegate(response) +{ //_code = 200; String _path(path); - if(!download && !fs.exists(_path) && fs.exists(_path+".gz")){ - _path = _path+".gz"; + if (!download && !fs.exists(_path) && fs.exists(_path + ".gz")) { + _path = _path + ".gz"; addHeader("Content-Encoding", "gzip"); } _content = fs.open(_path, "r"); - _contentLength = _content.size(); + setContentLength(_content.size()); - if(contentType == "") - _setContentType(path); + if (contentType == "") + _setContentTypeFromPath(path); else setContentType(contentType.c_str()); int filenameStart = path.lastIndexOf('/') + 1; - char buf[26+path.length()-filenameStart]; + char buf[26 + path.length() - filenameStart]; char* filename = (char*)path.c_str() + filenameStart; - if(download) { + if (download) { // set filename and force download - snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename); + snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", filename); } else { // set filename and force rendering - snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename); + snprintf(buf, sizeof(buf), "inline; filename=\"%s\"", filename); } addHeader("Content-Disposition", buf); } -PsychicFileResponse::PsychicFileResponse(PsychicRequest *request, File content, const String& path, const String& contentType, bool download) - : PsychicResponse(request) { +PsychicFileResponse::PsychicFileResponse(PsychicResponse* response, File content, const String& path, const String& contentType, bool download) : PsychicResponseDelegate(response) +{ String _path(path); - if(!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")){ + if (!download && String(content.name()).endsWith(".gz") && !path.endsWith(".gz")) { addHeader("Content-Encoding", "gzip"); } _content = content; - _contentLength = _content.size(); + setContentLength(_content.size()); - if(contentType == "") - _setContentType(path); + if (contentType == "") + _setContentTypeFromPath(path); else setContentType(contentType.c_str()); int filenameStart = path.lastIndexOf('/') + 1; - char buf[26+path.length()-filenameStart]; + char buf[26 + path.length() - filenameStart]; char* filename = (char*)path.c_str() + filenameStart; - if(download) { - snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", filename); + if (download) { + snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", filename); } else { - snprintf(buf, sizeof (buf), "inline; filename=\"%s\"", filename); + snprintf(buf, sizeof(buf), "inline; filename=\"%s\"", filename); } addHeader("Content-Disposition", buf); } PsychicFileResponse::~PsychicFileResponse() { - if(_content) + if (_content) _content.close(); } -void PsychicFileResponse::_setContentType(const String& path){ - const char *_contentType; - - if (path.endsWith(".html")) _contentType = "text/html"; - else if (path.endsWith(".htm")) _contentType = "text/html"; - else if (path.endsWith(".css")) _contentType = "text/css"; - else if (path.endsWith(".json")) _contentType = "application/json"; - else if (path.endsWith(".js")) _contentType = "application/javascript"; - else if (path.endsWith(".png")) _contentType = "image/png"; - else if (path.endsWith(".gif")) _contentType = "image/gif"; - else if (path.endsWith(".jpg")) _contentType = "image/jpeg"; - else if (path.endsWith(".ico")) _contentType = "image/x-icon"; - else if (path.endsWith(".svg")) _contentType = "image/svg+xml"; - else if (path.endsWith(".eot")) _contentType = "font/eot"; - else if (path.endsWith(".woff")) _contentType = "font/woff"; - else if (path.endsWith(".woff2")) _contentType = "font/woff2"; - else if (path.endsWith(".ttf")) _contentType = "font/ttf"; - else if (path.endsWith(".xml")) _contentType = "text/xml"; - else if (path.endsWith(".pdf")) _contentType = "application/pdf"; - else if (path.endsWith(".zip")) _contentType = "application/zip"; - else if(path.endsWith(".gz")) _contentType = "application/x-gzip"; - else _contentType = "text/plain"; - +void PsychicFileResponse::_setContentTypeFromPath(const String& path) +{ + const char* _contentType; + + if (path.endsWith(".html")) + _contentType = "text/html"; + else if (path.endsWith(".htm")) + _contentType = "text/html"; + else if (path.endsWith(".css")) + _contentType = "text/css"; + else if (path.endsWith(".json")) + _contentType = "application/json"; + else if (path.endsWith(".js")) + _contentType = "application/javascript"; + else if (path.endsWith(".png")) + _contentType = "image/png"; + else if (path.endsWith(".gif")) + _contentType = "image/gif"; + else if (path.endsWith(".jpg")) + _contentType = "image/jpeg"; + else if (path.endsWith(".ico")) + _contentType = "image/x-icon"; + else if (path.endsWith(".svg")) + _contentType = "image/svg+xml"; + else if (path.endsWith(".eot")) + _contentType = "font/eot"; + else if (path.endsWith(".woff")) + _contentType = "font/woff"; + else if (path.endsWith(".woff2")) + _contentType = "font/woff2"; + else if (path.endsWith(".ttf")) + _contentType = "font/ttf"; + else if (path.endsWith(".xml")) + _contentType = "text/xml"; + else if (path.endsWith(".pdf")) + _contentType = "application/pdf"; + else if (path.endsWith(".zip")) + _contentType = "application/zip"; + else if (path.endsWith(".gz")) + _contentType = "application/x-gzip"; + else + _contentType = "text/plain"; + setContentType(_contentType); } @@ -99,59 +118,52 @@ esp_err_t PsychicFileResponse::send() { esp_err_t err = ESP_OK; - //just send small files directly + // just send small files directly size_t size = getContentLength(); - if (size < FILE_CHUNK_SIZE) - { - uint8_t *buffer = (uint8_t *)malloc(size); - if (buffer == NULL) - { - /* Respond with 500 Internal Server Error */ - httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); + if (size < FILE_CHUNK_SIZE) { + uint8_t* buffer = (uint8_t*)malloc(size); + if (buffer == NULL) { + ESP_LOGE(PH_TAG, "Unable to allocate %" PRIu32 " bytes to send chunk", size); + httpd_resp_send_err(request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); return ESP_FAIL; } - size_t readSize = _content.readBytes((char *)buffer, size); + size_t readSize = _content.readBytes((char*)buffer, size); + + setContent(buffer, readSize); + err = _response->send(); - this->setContent(buffer, readSize); - err = PsychicResponse::send(); - free(buffer); - } - else - { + } else { /* Retrieve the pointer to scratch buffer for temporary storage */ - char *chunk = (char *)malloc(FILE_CHUNK_SIZE); - if (chunk == NULL) - { - /* Respond with 500 Internal Server Error */ - httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); + char* chunk = (char*)malloc(FILE_CHUNK_SIZE); + if (chunk == NULL) { + ESP_LOGE(PH_TAG, "Unable to allocate %" PRIu32 " bytes to send chunk", FILE_CHUNK_SIZE); + httpd_resp_send_err(request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); return ESP_FAIL; } - this->sendHeaders(); + sendHeaders(); size_t chunksize; do { - /* Read file in chunks into the scratch buffer */ - chunksize = _content.readBytes(chunk, FILE_CHUNK_SIZE); - if (chunksize > 0) - { - err = this->sendChunk((uint8_t *)chunk, chunksize); - if (err != ESP_OK) - break; - } + /* Read file in chunks into the scratch buffer */ + chunksize = _content.readBytes(chunk, FILE_CHUNK_SIZE); + if (chunksize > 0) { + err = sendChunk((uint8_t*)chunk, chunksize); + if (err != ESP_OK) + break; + } - /* Keep looping till the whole file is sent */ + /* Keep looping till the whole file is sent */ } while (chunksize != 0); - //keep track of our memory + // keep track of our memory free(chunk); - if (err == ESP_OK) - { + if (err == ESP_OK) { ESP_LOGD(PH_TAG, "File sending complete"); - this->finishChunking(); + finishChunking(); } } diff --git a/lib/PsychicHttp/src/PsychicFileResponse.h b/lib/PsychicHttp/src/PsychicFileResponse.h index 8eb75fc..69b5b30 100644 --- a/lib/PsychicHttp/src/PsychicFileResponse.h +++ b/lib/PsychicHttp/src/PsychicFileResponse.h @@ -6,16 +6,18 @@ class PsychicRequest; -class PsychicFileResponse: public PsychicResponse +class PsychicFileResponse : public PsychicResponseDelegate { - using File = fs::File; - using FS = fs::FS; - private: + using File = fs::File; + using FS = fs::FS; + + protected: File _content; - void _setContentType(const String& path); + void _setContentTypeFromPath(const String& path); + public: - PsychicFileResponse(PsychicRequest *request, FS &fs, const String& path, const String& contentType=String(), bool download=false); - PsychicFileResponse(PsychicRequest *request, File content, const String& path, const String& contentType=String(), bool download=false); + PsychicFileResponse(PsychicResponse* response, FS& fs, const String& path, const String& contentType = String(), bool download = false); + PsychicFileResponse(PsychicResponse* response, File content, const String& path, const String& contentType = String(), bool download = false); ~PsychicFileResponse(); esp_err_t send(); }; diff --git a/lib/PsychicHttp/src/PsychicHandler.cpp b/lib/PsychicHttp/src/PsychicHandler.cpp index 097b30e..d08fc70 100644 --- a/lib/PsychicHttp/src/PsychicHandler.cpp +++ b/lib/PsychicHttp/src/PsychicHandler.cpp @@ -1,111 +1,148 @@ #include "PsychicHandler.h" -PsychicHandler::PsychicHandler() : - _filter(NULL), - _server(NULL), - _username(""), - _password(""), - _method(DIGEST_AUTH), - _realm(""), - _authFailMsg(""), - _subprotocol("") - {} +PsychicHandler::PsychicHandler() +{ +} -PsychicHandler::~PsychicHandler() { +PsychicHandler::~PsychicHandler() +{ + delete _chain; // actual PsychicClient deletion handled by PsychicServer // for (PsychicClient *client : _clients) // delete(client); _clients.clear(); } -PsychicHandler* PsychicHandler::setFilter(PsychicRequestFilterFunction fn) { - _filter = fn; - return this; -} - -bool PsychicHandler::filter(PsychicRequest *request){ - return _filter == NULL || _filter(request); -} - -void PsychicHandler::setSubprotocol(const String& subprotocol) { - this->_subprotocol = subprotocol; -} -const char* PsychicHandler::getSubprotocol() const { - return _subprotocol.c_str(); -} - -PsychicHandler* PsychicHandler::setAuthentication(const char *username, const char *password, HTTPAuthMethod method, const char *realm, const char *authFailMsg) { - _username = String(username); - _password = String(password); - _method = method; - _realm = String(realm); - _authFailMsg = String(authFailMsg); - return this; -}; - -bool PsychicHandler::needsAuthentication(PsychicRequest *request) { - return (_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()); -} - -esp_err_t PsychicHandler::authenticate(PsychicRequest *request) { - return request->requestAuthentication(_method, _realm.c_str(), _authFailMsg.c_str()); -} - -PsychicClient * PsychicHandler::checkForNewClient(PsychicClient *client) +PsychicHandler* PsychicHandler::addFilter(PsychicRequestFilterFunction fn) { - PsychicClient *c = PsychicHandler::getClient(client); - if (c == NULL) - { + _filters.push_back(fn); + return this; +} + +bool PsychicHandler::filter(PsychicRequest* request) +{ + // run through our filter chain. + for (auto& filter : _filters) { + if (!filter(request)) { + ESP_LOGD(PH_TAG, "Request %s refused by filter from handler", request->uri().c_str()); + return false; + } + } + return true; +} + +void PsychicHandler::setSubprotocol(const String& subprotocol) +{ + this->_subprotocol = subprotocol; +} +const char* PsychicHandler::getSubprotocol() const +{ + return _subprotocol.c_str(); +} + +PsychicClient* PsychicHandler::checkForNewClient(PsychicClient* client) +{ + PsychicClient* c = PsychicHandler::getClient(client); + if (c == NULL) { c = client; addClient(c); c->isNew = true; - } - else + } else c->isNew = false; return c; } -void PsychicHandler::checkForClosedClient(PsychicClient *client) +void PsychicHandler::checkForClosedClient(PsychicClient* client) { - if (hasClient(client)) - { + if (hasClient(client)) { closeCallback(client); removeClient(client); } } -void PsychicHandler::addClient(PsychicClient *client) { +void PsychicHandler::addClient(PsychicClient* client) +{ _clients.push_back(client); } -void PsychicHandler::removeClient(PsychicClient *client) { +void PsychicHandler::removeClient(PsychicClient* client) +{ _clients.remove(client); } -PsychicClient * PsychicHandler::getClient(int socket) +PsychicClient* PsychicHandler::getClient(int socket) { - //make sure the server has it too. + // make sure the server has it too. if (!_server->hasClient(socket)) return NULL; - //what about us? - for (PsychicClient *client : _clients) + // what about us? + for (PsychicClient* client : _clients) if (client->socket() == socket) return client; - //nothing found. + // nothing found. return NULL; } -PsychicClient * PsychicHandler::getClient(PsychicClient *client) { +PsychicClient* PsychicHandler::getClient(PsychicClient* client) +{ return PsychicHandler::getClient(client->socket()); } -bool PsychicHandler::hasClient(PsychicClient *socket) { +bool PsychicHandler::hasClient(PsychicClient* socket) +{ return PsychicHandler::getClient(socket) != NULL; } -const std::list& PsychicHandler::getClientList() { +const std::list& PsychicHandler::getClientList() +{ return _clients; +} + +PsychicHandler* PsychicHandler::addMiddleware(PsychicMiddleware* middleware) +{ + if (!_chain) { + _chain = new PsychicMiddlewareChain(); + } + _chain->addMiddleware(middleware); + return this; +} + +PsychicHandler* PsychicHandler::addMiddleware(PsychicMiddlewareCallback fn) +{ + if (!_chain) { + _chain = new PsychicMiddlewareChain(); + } + _chain->addMiddleware(fn); + return this; +} + +void PsychicHandler::removeMiddleware(PsychicMiddleware* middleware) +{ + if (_chain) { + _chain->removeMiddleware(middleware); + } +} + +esp_err_t PsychicHandler::process(PsychicRequest* request) +{ + if (!filter(request)) { + return HTTPD_404_NOT_FOUND; + } + + if (!canHandle(request)) { + ESP_LOGD(PH_TAG, "Request %s refused by handler", request->uri().c_str()); + return HTTPD_404_NOT_FOUND; + } + + if (_chain) { + return _chain->runChain(request, [this, request]() { + return handleRequest(request, request->response()); + }); + + } else { + return handleRequest(request, request->response()); + } } \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHandler.h b/lib/PsychicHttp/src/PsychicHandler.h index bad62bf..bfba87b 100644 --- a/lib/PsychicHttp/src/PsychicHandler.h +++ b/lib/PsychicHttp/src/PsychicHandler.h @@ -6,23 +6,21 @@ class PsychicEndpoint; class PsychicHttpServer; +class PsychicMiddleware; +class PsychicMiddlewareChain; /* -* HANDLER :: Can be attached to any endpoint or as a generic request handler. -*/ + * HANDLER :: Can be attached to any endpoint or as a generic request handler. + */ -class PsychicHandler { - friend PsychicEndpoint; +class PsychicHandler +{ + friend PsychicEndpoint; protected: - PsychicRequestFilterFunction _filter; - PsychicHttpServer *_server; - - String _username; - String _password; - HTTPAuthMethod _method; - String _realm; - String _authFailMsg; + PsychicHttpServer* _server = nullptr; + PsychicMiddlewareChain* _chain = nullptr; + std::list _filters; String _subprotocol; @@ -32,35 +30,39 @@ class PsychicHandler { PsychicHandler(); virtual ~PsychicHandler(); - PsychicHandler* setFilter(PsychicRequestFilterFunction fn); - bool filter(PsychicRequest *request); - - PsychicHandler* setAuthentication(const char *username, const char *password, HTTPAuthMethod method = BASIC_AUTH, const char *realm = "", const char *authFailMsg = ""); - bool needsAuthentication(PsychicRequest *request); - esp_err_t authenticate(PsychicRequest *request); - virtual bool isWebSocket() { return false; }; void setSubprotocol(const String& subprotocol); const char* getSubprotocol() const; - PsychicClient * checkForNewClient(PsychicClient *client); - void checkForClosedClient(PsychicClient *client); + PsychicClient* checkForNewClient(PsychicClient* client); + void checkForClosedClient(PsychicClient* client); - virtual void addClient(PsychicClient *client); - virtual void removeClient(PsychicClient *client); - virtual PsychicClient * getClient(int socket); - virtual PsychicClient * getClient(PsychicClient *client); - virtual void openCallback(PsychicClient *client) {}; - virtual void closeCallback(PsychicClient *client) {}; + virtual void addClient(PsychicClient* client); + virtual void removeClient(PsychicClient* client); + virtual PsychicClient* getClient(int socket); + virtual PsychicClient* getClient(PsychicClient* client); + virtual void openCallback(PsychicClient* client) {}; + virtual void closeCallback(PsychicClient* client) {}; - bool hasClient(PsychicClient *client); + bool hasClient(PsychicClient* client); int count() { return _clients.size(); }; const std::list& getClientList(); - //derived classes must implement these functions - virtual bool canHandle(PsychicRequest *request) { return true; }; - virtual esp_err_t handleRequest(PsychicRequest *request) = 0; + // called to process this handler with its middleware chain and filers + esp_err_t process(PsychicRequest* request); + + //bool filter(PsychicRequest* request); + PsychicHandler* addFilter(PsychicRequestFilterFunction fn); + bool filter(PsychicRequest* request); + + PsychicHandler* addMiddleware(PsychicMiddleware* middleware); + PsychicHandler* addMiddleware(PsychicMiddlewareCallback fn); + void removeMiddleware(PsychicMiddleware *middleware); + + // derived classes must implement these functions + virtual bool canHandle(PsychicRequest* request) { return true; }; + virtual esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) { return HTTPD_404_NOT_FOUND; }; }; #endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttp.h b/lib/PsychicHttp/src/PsychicHttp.h index 3e9da55..acf1bf0 100644 --- a/lib/PsychicHttp/src/PsychicHttp.h +++ b/lib/PsychicHttp/src/PsychicHttp.h @@ -1,24 +1,33 @@ #ifndef PsychicHttp_h #define PsychicHttp_h -//#define ENABLE_ASYNC // This is something added in ESP-IDF 5.1.x where each request can be handled in its own thread +// #define ENABLE_ASYNC // This is something added in ESP-IDF 5.1.x where each request can be handled in its own thread -#include +#include "PsychicEndpoint.h" +#include "PsychicEventSource.h" +#include "PsychicFileResponse.h" +#include "PsychicHandler.h" #include "PsychicHttpServer.h" +#include "PsychicJson.h" +#include "PsychicMiddleware.h" +#include "PsychicMiddlewareChain.h" +#include "PsychicMiddlewares.h" #include "PsychicRequest.h" #include "PsychicResponse.h" -#include "PsychicEndpoint.h" -#include "PsychicHandler.h" #include "PsychicStaticFileHandler.h" -#include "PsychicFileResponse.h" #include "PsychicStreamResponse.h" #include "PsychicUploadHandler.h" +#include "PsychicVersion.h" #include "PsychicWebSocket.h" -#include "PsychicEventSource.h" -#include "PsychicJson.h" +#include #ifdef ENABLE_ASYNC #include "async_worker.h" #endif +// debugging library +#ifdef PSY_USE_ARDUINO_TRACE + #include +#endif + #endif /* PsychicHttp_h */ \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttpServer.cpp b/lib/PsychicHttp/src/PsychicHttpServer.cpp index f6cbb7f..908d660 100644 --- a/lib/PsychicHttp/src/PsychicHttpServer.cpp +++ b/lib/PsychicHttp/src/PsychicHttpServer.cpp @@ -1,155 +1,305 @@ #include "PsychicHttpServer.h" #include "PsychicEndpoint.h" #include "PsychicHandler.h" -#include "PsychicWebHandler.h" -#include "PsychicStaticFileHandler.h" -#include "PsychicWebSocket.h" #include "PsychicJson.h" +#include "PsychicStaticFileHandler.h" +#include "PsychicWebHandler.h" +#include "PsychicWebSocket.h" +#ifndef CONFIG_IDF_TARGET_ESP32H2 #include "WiFi.h" - -PsychicHttpServer::PsychicHttpServer() : - _onOpen(NULL), - _onClose(NULL) +#endif +PsychicHttpServer::PsychicHttpServer(uint16_t port) { maxRequestBodySize = MAX_REQUEST_BODY_SIZE; maxUploadSize = MAX_UPLOAD_SIZE; defaultEndpoint = new PsychicEndpoint(this, HTTP_GET, ""); onNotFound(PsychicHttpServer::defaultNotFoundHandler); - - //for a regular server + + // for a regular server config = HTTPD_DEFAULT_CONFIG(); config.open_fn = PsychicHttpServer::openCallback; config.close_fn = PsychicHttpServer::closeCallback; - config.uri_match_fn = httpd_uri_match_wildcard; config.global_user_ctx = this; - config.global_user_ctx_free_fn = destroy; - config.max_uri_handlers = 20; + config.global_user_ctx_free_fn = PsychicHttpServer::destroy; + config.uri_match_fn = MATCH_WILDCARD; // new internal endpoint matching - do not change this!!! + config.stack_size = 4608; // default stack is just a little bit too small. - #ifdef ENABLE_ASYNC - // It is advisable that httpd_config_t->max_open_sockets > MAX_ASYNC_REQUESTS - // Why? This leaves at least one socket still available to handle - // quick synchronous requests. Otherwise, all the sockets will - // get taken by the long async handlers, and your server will no - // longer be responsive. - config.max_open_sockets = ASYNC_WORKER_COUNT + 1; - config.lru_purge_enable = true; - #endif + // our internal matching function for endpoints + _uri_match_fn = MATCH_WILDCARD; // use this change the endpoint matching function. + +#ifdef ENABLE_ASYNC + // It is advisable that httpd_config_t->max_open_sockets > MAX_ASYNC_REQUESTS + // Why? This leaves at least one socket still available to handle + // quick synchronous requests. Otherwise, all the sockets will + // get taken by the long async handlers, and your server will no + // longer be responsive. + config.max_open_sockets = ASYNC_WORKER_COUNT + 1; + config.lru_purge_enable = true; +#endif + + setPort(port); } PsychicHttpServer::~PsychicHttpServer() { - for (auto *client : _clients) - delete(client); + _esp_idf_endpoints.clear(); + + for (auto* client : _clients) + delete (client); _clients.clear(); - for (auto *endpoint : _endpoints) - delete(endpoint); + for (auto* endpoint : _endpoints) + delete (endpoint); _endpoints.clear(); - for (auto *handler : _handlers) - delete(handler); + for (auto* handler : _handlers) + delete (handler); _handlers.clear(); + for (auto* rewrite : _rewrites) + delete (rewrite); + _rewrites.clear(); + delete defaultEndpoint; + delete _chain; } -void PsychicHttpServer::destroy(void *ctx) +void PsychicHttpServer::destroy(void* ctx) { // do not release any resource for PsychicHttpServer in order to be able to restart it after stopping } -esp_err_t PsychicHttpServer::listen(uint16_t port) +void PsychicHttpServer::setPort(uint16_t port) { - this->_use_ssl = false; this->config.server_port = port; - - return this->_start(); } -esp_err_t PsychicHttpServer::_start() +uint16_t PsychicHttpServer::getPort() { + return this->config.server_port; +} + +esp_err_t PsychicHttpServer::start() +{ + if (_running) + return ESP_OK; + esp_err_t ret; - #ifdef ENABLE_ASYNC - // start workers - start_async_req_workers(); - #endif +#ifdef ENABLE_ASYNC + // start workers + start_async_req_workers(); +#endif - //fire it up. + // one URI handler for each http_method + config.max_uri_handlers = supported_methods.size() + _esp_idf_endpoints.size(); + + // fire it up. ret = _startServer(); - if (ret != ESP_OK) - { + if (ret != ESP_OK) { ESP_LOGE(PH_TAG, "Server start failed (%s)", esp_err_to_name(ret)); return ret; } + // some handlers (aka websockets) need actual endpoints in esp-idf http_server + for (auto& endpoint : _esp_idf_endpoints) { + ESP_LOGD(PH_TAG, "Adding endpoint %s | %s", endpoint.uri, http_method_str((http_method)endpoint.method)); + + // Register endpoint with ESP-IDF server + esp_err_t ret = httpd_register_uri_handler(this->server, &endpoint); + if (ret != ESP_OK) + ESP_LOGE(PH_TAG, "Add endpoint failed (%s)", esp_err_to_name(ret)); + } + + // Register a handler for each http_method method - it will match all requests with that URI/method + for (auto& method : supported_methods) { + ESP_LOGD(PH_TAG, "Adding %s meta endpoint", http_method_str((http_method)method)); + + httpd_uri_t my_uri; + my_uri.uri = "*"; + my_uri.method = method; + my_uri.handler = PsychicHttpServer::requestHandler; + my_uri.is_websocket = false; + my_uri.supported_subprotocol = ""; + + // Register endpoint with ESP-IDF server + esp_err_t ret = httpd_register_uri_handler(this->server, &my_uri); + if (ret != ESP_OK) + ESP_LOGE(PH_TAG, "Add endpoint failed (%s)", esp_err_to_name(ret)); + } + // Register handler ret = httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, PsychicHttpServer::notFoundHandler); if (ret != ESP_OK) - ESP_LOGE(PH_TAG, "Add 404 handler failed (%s)", esp_err_to_name(ret)); + ESP_LOGE(PH_TAG, "Add 404 handler failed (%s)", esp_err_to_name(ret)); + ESP_LOGI(PH_TAG, "Server started on port %" PRIu16, getPort()); + + _running = true; return ret; } -esp_err_t PsychicHttpServer::_startServer() { +esp_err_t PsychicHttpServer::_startServer() +{ return httpd_start(&this->server, &this->config); } -void PsychicHttpServer::stop() +esp_err_t PsychicHttpServer::stop() { - httpd_stop(this->server); + if (!_running) + return ESP_OK; + + // some handlers (aka websockets) need actual endpoints in esp-idf http_server + for (auto& endpoint : _esp_idf_endpoints) { + ESP_LOGD(PH_TAG, "Removing endpoint %s | %s", endpoint.uri, http_method_str((http_method)endpoint.method)); + + // Unregister endpoint with ESP-IDF server + esp_err_t ret = httpd_unregister_uri_handler(this->server, endpoint.uri, endpoint.method); + if (ret != ESP_OK) + ESP_LOGE(PH_TAG, "Removal of endpoint failed (%s)", esp_err_to_name(ret)); + } + + // Unregister a handler for each http_method method - it will match all requests with that URI/method + for (auto& method : supported_methods) { + ESP_LOGD(PH_TAG, "Removing %s meta endpoint", http_method_str((http_method)method)); + + // Unregister endpoint with ESP-IDF server + esp_err_t ret = httpd_unregister_uri_handler(this->server, "*", method); + if (ret != ESP_OK) + ESP_LOGE(PH_TAG, "Removal of endpoint failed (%s)", esp_err_to_name(ret)); + } + + esp_err_t ret = _stopServer(); + if (ret != ESP_OK) { + ESP_LOGE(PH_TAG, "Server stop failed (%s)", esp_err_to_name(ret)); + return ret; + } + + ESP_LOGI(PH_TAG, "Server stopped"); + _running = false; + return ret; } -PsychicHandler& PsychicHttpServer::addHandler(PsychicHandler* handler){ +esp_err_t PsychicHttpServer::_stopServer() +{ + return httpd_stop(this->server); +} + +void PsychicHttpServer::reset() +{ + if (_running) + stop(); + + for (auto* client : _clients) + delete (client); + _clients.clear(); + + for (auto* endpoint : _endpoints) + delete (endpoint); + _endpoints.clear(); + + for (auto* handler : _handlers) + delete (handler); + _handlers.clear(); + + for (auto* rewrite : _rewrites) + delete (rewrite); + _rewrites.clear(); + + _esp_idf_endpoints.clear(); + + onNotFound(PsychicHttpServer::defaultNotFoundHandler); + _onOpen = nullptr; + _onClose = nullptr; +} + +httpd_uri_match_func_t PsychicHttpServer::getURIMatchFunction() +{ + return _uri_match_fn; +} + +void PsychicHttpServer::setURIMatchFunction(httpd_uri_match_func_t match_fn) +{ + _uri_match_fn = match_fn; +} + +PsychicHandler* PsychicHttpServer::addHandler(PsychicHandler* handler) +{ _handlers.push_back(handler); - return *handler; + return handler; } -void PsychicHttpServer::removeHandler(PsychicHandler *handler){ +void PsychicHttpServer::removeHandler(PsychicHandler* handler) +{ _handlers.remove(handler); } -PsychicEndpoint* PsychicHttpServer::on(const char* uri) { +PsychicRewrite* PsychicHttpServer::addRewrite(PsychicRewrite* rewrite) +{ + _rewrites.push_back(rewrite); + return rewrite; +} + +void PsychicHttpServer::removeRewrite(PsychicRewrite* rewrite) +{ + _rewrites.remove(rewrite); +} + +PsychicRewrite* PsychicHttpServer::rewrite(const char* from, const char* to) +{ + return addRewrite(new PsychicRewrite(from, to)); +} + +PsychicEndpoint* PsychicHttpServer::on(const char* uri) +{ return on(uri, HTTP_GET); } -PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method) +PsychicEndpoint* PsychicHttpServer::on(const char* uri, int method) { - PsychicWebHandler *handler = new PsychicWebHandler(); + PsychicWebHandler* handler = new PsychicWebHandler(); return on(uri, method, handler); } -PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHandler *handler) +PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHandler* handler) { return on(uri, HTTP_GET, handler); } -PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicHandler *handler) +PsychicEndpoint* PsychicHttpServer::on(const char* uri, int method, PsychicHandler* handler) { - //make our endpoint - PsychicEndpoint *endpoint = new PsychicEndpoint(this, method, uri); + // make our endpoint + PsychicEndpoint* endpoint = new PsychicEndpoint(this, method, uri); - //set our handler + // set our handler endpoint->setHandler(handler); - // URI handler structure - httpd_uri_t my_uri { - .uri = uri, - .method = method, - .handler = PsychicEndpoint::requestCallback, - .user_ctx = endpoint, - .is_websocket = handler->isWebSocket(), - .supported_subprotocol = handler->getSubprotocol() - }; - - // Register endpoint with ESP-IDF server - esp_err_t ret = httpd_register_uri_handler(this->server, &my_uri); - if (ret != ESP_OK) - ESP_LOGE(PH_TAG, "Add endpoint failed (%s)", esp_err_to_name(ret)); + // websockets need a real endpoint in esp-idf + if (handler->isWebSocket()) { + // URI handler structure + httpd_uri_t my_uri; + my_uri.uri = uri; + my_uri.method = HTTP_GET; + my_uri.handler = PsychicEndpoint::requestCallback; + my_uri.user_ctx = endpoint; + my_uri.is_websocket = handler->isWebSocket(); + my_uri.supported_subprotocol = handler->getSubprotocol(); - //save it for later + // save it to our 'real' handlers for later. + _esp_idf_endpoints.push_back(my_uri); + } + + // if this is a method we haven't added yet, do it. + if (method != HTTP_ANY) { + if (!(std::find(supported_methods.begin(), supported_methods.end(), (http_method)method) != supported_methods.end())) { + ESP_LOGD(PH_TAG, "Adding %s to server.supported_methods", http_method_str((http_method)method)); + supported_methods.push_back((http_method)method); + } + } + + // add it to our meta endpoints _endpoints.push_back(endpoint); return endpoint; @@ -160,10 +310,10 @@ PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicHttpRequestCallba return on(uri, HTTP_GET, fn); } -PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicHttpRequestCallback fn) +PsychicEndpoint* PsychicHttpServer::on(const char* uri, int method, PsychicHttpRequestCallback fn) { - //these basic requests need a basic web handler - PsychicWebHandler *handler = new PsychicWebHandler(); + // these basic requests need a basic web handler + PsychicWebHandler* handler = new PsychicWebHandler(); handler->onRequest(fn); return on(uri, method, handler); @@ -174,59 +324,187 @@ PsychicEndpoint* PsychicHttpServer::on(const char* uri, PsychicJsonRequestCallba return on(uri, HTTP_GET, fn); } -PsychicEndpoint* PsychicHttpServer::on(const char* uri, http_method method, PsychicJsonRequestCallback fn) +PsychicEndpoint* PsychicHttpServer::on(const char* uri, int method, PsychicJsonRequestCallback fn) { - //these basic requests need a basic web handler - PsychicJsonHandler *handler = new PsychicJsonHandler(); + // these basic requests need a basic web handler + PsychicJsonHandler* handler = new PsychicJsonHandler(); handler->onRequest(fn); return on(uri, method, handler); } +bool PsychicHttpServer::removeEndpoint(const char* uri, int method) +{ + // some handlers (aka websockets) need actual endpoints in esp-idf http_server + // don't return from here, because its added to the _endpoints list too. + for (auto& endpoint : _esp_idf_endpoints) { + if (!strcmp(endpoint.uri, uri) && method == endpoint.method) { + ESP_LOGD(PH_TAG, "Unregistering endpoint %s | %s", endpoint.uri, http_method_str((http_method)endpoint.method)); + + // Register endpoint with ESP-IDF server + esp_err_t ret = httpd_register_uri_handler(this->server, &endpoint); + if (ret != ESP_OK) + ESP_LOGE(PH_TAG, "Add endpoint failed (%s)", esp_err_to_name(ret)); + } + } + + // loop through our endpoints and see if anyone matches + for (auto* endpoint : _endpoints) { + if (endpoint->uri().equals(uri) && method == endpoint->_method) + return removeEndpoint(endpoint); + } + + return false; +} + +bool PsychicHttpServer::removeEndpoint(PsychicEndpoint* endpoint) +{ + _endpoints.remove(endpoint); + return true; +} + +PsychicHttpServer* PsychicHttpServer::addFilter(PsychicRequestFilterFunction fn) +{ + _filters.push_back(fn); + + return this; +} + +bool PsychicHttpServer::_filter(PsychicRequest* request) +{ + // run through our filter chain. + for (auto& filter : _filters) { + if (!filter(request)) + return false; + } + + return true; +} + +PsychicHttpServer* PsychicHttpServer::addMiddleware(PsychicMiddleware* middleware) +{ + if (!_chain) { + _chain = new PsychicMiddlewareChain(); + } + _chain->addMiddleware(middleware); + return this; +} + +PsychicHttpServer* PsychicHttpServer::addMiddleware(PsychicMiddlewareCallback fn) +{ + if (!_chain) { + _chain = new PsychicMiddlewareChain(); + } + _chain->addMiddleware(fn); + return this; +} + +void PsychicHttpServer::removeMiddleware(PsychicMiddleware* middleware) +{ + if (_chain) { + _chain->removeMiddleware(middleware); + } +} + void PsychicHttpServer::onNotFound(PsychicHttpRequestCallback fn) { - PsychicWebHandler *handler = new PsychicWebHandler(); + PsychicWebHandler* handler = new PsychicWebHandler(); handler->onRequest(fn == nullptr ? PsychicHttpServer::defaultNotFoundHandler : fn); this->defaultEndpoint->setHandler(handler); } -esp_err_t PsychicHttpServer::notFoundHandler(httpd_req_t *req, httpd_err_code_t err) +bool PsychicHttpServer::_rewriteRequest(PsychicRequest* request) { - PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(req->handle); - PsychicRequest request(server, req); - - //loop through our global handlers and see if anyone wants it - for(auto *handler: server->_handlers) - { - //are we capable of handling this? - if (handler->filter(&request) && handler->canHandle(&request)) - { - //check our credentials - if (handler->needsAuthentication(&request)) - return handler->authenticate(&request); - else - return handler->handleRequest(&request); + for (auto* r : _rewrites) { + if (r->match(request)) { + request->_setUri(r->toUrl().c_str()); + return true; } } - //nothing found, give it to our defaultEndpoint - PsychicHandler *handler = server->defaultEndpoint->handler(); - if (handler->filter(&request) && handler->canHandle(&request)) - return handler->handleRequest(&request); - - //not sure how we got this far. - return ESP_ERR_HTTPD_INVALID_REQ; + return false; } -esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest *request) +esp_err_t PsychicHttpServer::requestHandler(httpd_req_t* req) { - request->reply(404, "text/html", "That URI does not exist."); + PsychicHttpServer* server = (PsychicHttpServer*)httpd_get_global_user_ctx(req->handle); + PsychicRequest request(server, req); - return ESP_OK; + // process any URL rewrites + server->_rewriteRequest(&request); + + // run it through our global server filter list + if (!server->_filter(&request)) { + ESP_LOGD(PH_TAG, "Request %s refused by global filter", request.uri().c_str()); + return request.response()->send(400); + } + + // then runs the request through the filter chain + esp_err_t ret; + if (server->_chain) { + ret = server->_chain->runChain(&request, [server, &request]() { + return server->_process(&request); + }); + } else { + ret = server->_process(&request); + } + ESP_LOGD(PH_TAG, "Request %s processed by global middleware: %s", request.uri().c_str(), esp_err_to_name(ret)); + + if (ret == HTTPD_404_NOT_FOUND) { + return PsychicHttpServer::notFoundHandler(req, HTTPD_404_NOT_FOUND); + } + + return ret; } -void PsychicHttpServer::onOpen(PsychicClientCallback handler) { +esp_err_t PsychicHttpServer::_process(PsychicRequest* request) +{ + // loop through our endpoints and see if anyone wants it. + for (auto* endpoint : _endpoints) { + if (endpoint->matches(request->uri().c_str())) { + if (endpoint->_method == request->method() || endpoint->_method == HTTP_ANY) { + request->setEndpoint(endpoint); + return endpoint->process(request); + } + } + } + + // loop through our global handlers and see if anyone wants it + for (auto* handler : _handlers) { + esp_err_t ret = handler->process(request); + if (ret != HTTPD_404_NOT_FOUND) + return ret; + } + + return HTTPD_404_NOT_FOUND; +} + +esp_err_t PsychicHttpServer::notFoundHandler(httpd_req_t* req, httpd_err_code_t err) +{ + PsychicHttpServer* server = (PsychicHttpServer*)httpd_get_global_user_ctx(req->handle); + PsychicRequest request(server, req); + + // pull up our default handler / endpoint + PsychicHandler* handler = server->defaultEndpoint->handler(); + if (!handler) + return request.response()->send(404); + + esp_err_t ret = handler->process(&request); + if (ret != HTTPD_404_NOT_FOUND) + return ret; + + // not sure how we got this far. + return request.response()->send(404); +} + +esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest* request, PsychicResponse* response) +{ + return response->send(404, "text/html", "That URI does not exist."); +} + +void PsychicHttpServer::onOpen(PsychicClientCallback handler) +{ this->_onOpen = handler; } @@ -234,25 +512,25 @@ esp_err_t PsychicHttpServer::openCallback(httpd_handle_t hd, int sockfd) { ESP_LOGD(PH_TAG, "New client connected %d", sockfd); - //get our global server reference - PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(hd); + // get our global server reference + PsychicHttpServer* server = (PsychicHttpServer*)httpd_get_global_user_ctx(hd); - //lookup our client - PsychicClient *client = server->getClient(sockfd); - if (client == NULL) - { + // lookup our client + PsychicClient* client = server->getClient(sockfd); + if (client == NULL) { client = new PsychicClient(hd, sockfd); server->addClient(client); } - //user callback + // user callback if (server->_onOpen != NULL) server->_onOpen(client); return ESP_OK; } -void PsychicHttpServer::onClose(PsychicClientCallback handler) { +void PsychicHttpServer::onClose(PsychicClientCallback handler) +{ this->_onClose = handler; } @@ -260,30 +538,27 @@ void PsychicHttpServer::closeCallback(httpd_handle_t hd, int sockfd) { ESP_LOGD(PH_TAG, "Client disconnected %d", sockfd); - PsychicHttpServer *server = (PsychicHttpServer*)httpd_get_global_user_ctx(hd); + PsychicHttpServer* server = (PsychicHttpServer*)httpd_get_global_user_ctx(hd); - //lookup our client - PsychicClient *client = server->getClient(sockfd); - if (client != NULL) - { - //give our handlers a chance to handle a disconnect first - for (PsychicEndpoint * endpoint : server->_endpoints) - { - PsychicHandler *handler = endpoint->handler(); + // lookup our client + PsychicClient* client = server->getClient(sockfd); + if (client != NULL) { + // give our handlers a chance to handle a disconnect first + for (PsychicEndpoint* endpoint : server->_endpoints) { + PsychicHandler* handler = endpoint->handler(); handler->checkForClosedClient(client); } - //do we have a callback attached? + // do we have a callback attached? if (server->_onClose != NULL) server->_onClose(client); - //remove it from our list + // remove it from our list server->removeClient(client); - } - else + } else ESP_LOGE(PH_TAG, "No client record %d", sockfd); - //finally close it out. + // finally close it out. close(sockfd); } @@ -295,48 +570,56 @@ PsychicStaticFileHandler* PsychicHttpServer::serveStatic(const char* uri, fs::FS return handler; } -void PsychicHttpServer::addClient(PsychicClient *client) { +void PsychicHttpServer::addClient(PsychicClient* client) +{ _clients.push_back(client); } -void PsychicHttpServer::removeClient(PsychicClient *client) { +void PsychicHttpServer::removeClient(PsychicClient* client) +{ _clients.remove(client); delete client; } -PsychicClient * PsychicHttpServer::getClient(int socket) { - for (PsychicClient * client : _clients) +PsychicClient* PsychicHttpServer::getClient(int socket) +{ + for (PsychicClient* client : _clients) if (client->socket() == socket) return client; return NULL; } -PsychicClient * PsychicHttpServer::getClient(httpd_req_t *req) { +PsychicClient* PsychicHttpServer::getClient(httpd_req_t* req) +{ return getClient(httpd_req_to_sockfd(req)); } -bool PsychicHttpServer::hasClient(int socket) { +bool PsychicHttpServer::hasClient(int socket) +{ return getClient(socket) != NULL; } -const std::list& PsychicHttpServer::getClientList() { +const std::list& PsychicHttpServer::getClientList() +{ return _clients; } -bool ON_STA_FILTER(PsychicRequest *request) { - #ifndef CONFIG_IDF_TARGET_ESP32H2 - return WiFi.localIP() == request->client()->localIP(); - #else +bool ON_STA_FILTER(PsychicRequest* request) +{ + #if defined(CONFIG_IDF_TARGET_ESP32H2) return false; + #else + return WiFi.localIP() == request->client()->localIP(); #endif } -bool ON_AP_FILTER(PsychicRequest *request) { - #ifndef CONFIG_IDF_TARGET_ESP32H2 - return WiFi.softAPIP() == request->client()->localIP(); - #else +bool ON_AP_FILTER(PsychicRequest* request) +{ + #if defined(CONFIG_IDF_TARGET_ESP32H2) return false; + #else + return WiFi.softAPIP() == request->client()->localIP(); #endif } @@ -350,25 +633,46 @@ String urlDecode(const char* encoded) size_t i, j = 0; for (i = 0; i < length; ++i) { - if (encoded[i] == '%' && isxdigit(encoded[i + 1]) && isxdigit(encoded[i + 2])) { - // Valid percent-encoded sequence - int hex; - sscanf(encoded + i + 1, "%2x", &hex); - decoded[j++] = (char)hex; - i += 2; // Skip the two hexadecimal characters - } else if (encoded[i] == '+') { - // Convert '+' to space - decoded[j++] = ' '; - } else { - // Copy other characters as they are - decoded[j++] = encoded[i]; - } + if (encoded[i] == '%' && isxdigit(encoded[i + 1]) && isxdigit(encoded[i + 2])) { + // Valid percent-encoded sequence + int hex; + sscanf(encoded + i + 1, "%2x", &hex); + decoded[j++] = (char)hex; + i += 2; // Skip the two hexadecimal characters + } else if (encoded[i] == '+') { + // Convert '+' to space + decoded[j++] = ' '; + } else { + // Copy other characters as they are + decoded[j++] = encoded[i]; + } } - decoded[j] = '\0'; // Null-terminate the decoded string + decoded[j] = '\0'; // Null-terminate the decoded string String output(decoded); free(decoded); return output; -} \ No newline at end of file +} + +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 \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttpServer.h b/lib/PsychicHttp/src/PsychicHttpServer.h index 40e5442..9fef275 100644 --- a/lib/PsychicHttp/src/PsychicHttpServer.h +++ b/lib/PsychicHttp/src/PsychicHttpServer.h @@ -1,9 +1,20 @@ #ifndef PsychicHttpServer_h #define PsychicHttpServer_h -#include "PsychicCore.h" #include "PsychicClient.h" +#include "PsychicCore.h" #include "PsychicHandler.h" +#include "PsychicMiddleware.h" +#include "PsychicMiddlewareChain.h" +#include "PsychicRewrite.h" + +#ifdef PSY_ENABLE_REGEX + #include +#endif + +#ifndef HTTP_ANY + #define HTTP_ANY INT_MAX +#endif class PsychicEndpoint; class PsychicHandler; @@ -12,70 +23,123 @@ class PsychicStaticFileHandler; class PsychicHttpServer { protected: - bool _use_ssl = false; + std::list _esp_idf_endpoints; std::list _endpoints; std::list _handlers; std::list _clients; + std::list _rewrites; + std::list _filters; - PsychicClientCallback _onOpen; - PsychicClientCallback _onClose; + PsychicClientCallback _onOpen = nullptr; + PsychicClientCallback _onClose = nullptr; + PsychicMiddlewareChain* _chain = nullptr; esp_err_t _start(); virtual esp_err_t _startServer(); + virtual esp_err_t _stopServer(); + bool _running = false; + httpd_uri_match_func_t _uri_match_fn = nullptr; + + bool _rewriteRequest(PsychicRequest* request); + esp_err_t _process(PsychicRequest* request); + bool _filter(PsychicRequest* request); public: - PsychicHttpServer(); + PsychicHttpServer(uint16_t port = 80); virtual ~PsychicHttpServer(); - //esp-idf specific stuff + // what methods to support + std::list supported_methods = { + HTTP_GET, + HTTP_POST, + HTTP_DELETE, + HTTP_HEAD, + HTTP_PUT, + HTTP_OPTIONS + }; + + // esp-idf specific stuff httpd_handle_t server; httpd_config_t config; - //some limits on what we will accept + // some limits on what we will accept unsigned long maxUploadSize; unsigned long maxRequestBodySize; - PsychicEndpoint *defaultEndpoint; + PsychicEndpoint* defaultEndpoint; - static void destroy(void *ctx); + static void destroy(void* ctx); - esp_err_t listen(uint16_t port); + virtual void setPort(uint16_t port); + virtual uint16_t getPort(); - virtual void stop(); + bool isRunning() { return _running; } + esp_err_t begin() { return start(); } + esp_err_t end() { return stop(); } + esp_err_t start(); + esp_err_t stop(); + void reset(); - PsychicHandler& addHandler(PsychicHandler* handler); + httpd_uri_match_func_t getURIMatchFunction(); + void setURIMatchFunction(httpd_uri_match_func_t match_fn); + + PsychicRewrite* addRewrite(PsychicRewrite* rewrite); + void removeRewrite(PsychicRewrite* rewrite); + PsychicRewrite* rewrite(const char* from, const char* to); + + PsychicHandler* addHandler(PsychicHandler* handler); void removeHandler(PsychicHandler* handler); - void addClient(PsychicClient *client); - void removeClient(PsychicClient *client); + void addClient(PsychicClient* client); + void removeClient(PsychicClient* client); PsychicClient* getClient(int socket); - PsychicClient* getClient(httpd_req_t *req); + PsychicClient* getClient(httpd_req_t* req); bool hasClient(int socket); int count() { return _clients.size(); }; const std::list& getClientList(); PsychicEndpoint* on(const char* uri); - PsychicEndpoint* on(const char* uri, http_method method); - PsychicEndpoint* on(const char* uri, PsychicHandler *handler); - PsychicEndpoint* on(const char* uri, http_method method, PsychicHandler *handler); + PsychicEndpoint* on(const char* uri, int method); + PsychicEndpoint* on(const char* uri, PsychicHandler* handler); + PsychicEndpoint* on(const char* uri, int method, PsychicHandler* handler); PsychicEndpoint* on(const char* uri, PsychicHttpRequestCallback onRequest); - PsychicEndpoint* on(const char* uri, http_method method, PsychicHttpRequestCallback onRequest); + PsychicEndpoint* on(const char* uri, int method, PsychicHttpRequestCallback onRequest); PsychicEndpoint* on(const char* uri, PsychicJsonRequestCallback onRequest); - PsychicEndpoint* on(const char* uri, http_method method, PsychicJsonRequestCallback onRequest); + PsychicEndpoint* on(const char* uri, int method, PsychicJsonRequestCallback onRequest); - static esp_err_t notFoundHandler(httpd_req_t *req, httpd_err_code_t err); - static esp_err_t defaultNotFoundHandler(PsychicRequest *request); - void onNotFound(PsychicHttpRequestCallback fn); + bool removeEndpoint(const char* uri, int method); + bool removeEndpoint(PsychicEndpoint* endpoint); - void onOpen(PsychicClientCallback handler); - void onClose(PsychicClientCallback handler); + PsychicHttpServer* addFilter(PsychicRequestFilterFunction fn); + + PsychicHttpServer* addMiddleware(PsychicMiddleware* middleware); + PsychicHttpServer* addMiddleware(PsychicMiddlewareCallback fn); + void removeMiddleware(PsychicMiddleware *middleware); + + static esp_err_t requestHandler(httpd_req_t* req); + static esp_err_t notFoundHandler(httpd_req_t* req, httpd_err_code_t err); + static esp_err_t defaultNotFoundHandler(PsychicRequest* request, PsychicResponse* response); static esp_err_t openCallback(httpd_handle_t hd, int sockfd); static void closeCallback(httpd_handle_t hd, int sockfd); + void onNotFound(PsychicHttpRequestCallback fn); + void onOpen(PsychicClientCallback handler); + void onClose(PsychicClientCallback handler); + PsychicStaticFileHandler* serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL); }; -bool ON_STA_FILTER(PsychicRequest *request); -bool ON_AP_FILTER(PsychicRequest *request); +bool ON_STA_FILTER(PsychicRequest* request); +bool ON_AP_FILTER(PsychicRequest* request); + +// URI matching functions +bool psychic_uri_match_simple(const char* uri1, const char* uri2, size_t len2); +#define MATCH_SIMPLE psychic_uri_match_simple +#define MATCH_WILDCARD httpd_uri_match_wildcard + +#ifdef PSY_ENABLE_REGEX +bool psychic_uri_match_regex(const char* uri1, const char* uri2, size_t len2); + #define MATCH_REGEX psychic_uri_match_regex +#endif #endif // PsychicHttpServer_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttpsServer.cpp b/lib/PsychicHttp/src/PsychicHttpsServer.cpp index 5df225e..faaeb01 100644 --- a/lib/PsychicHttp/src/PsychicHttpsServer.cpp +++ b/lib/PsychicHttp/src/PsychicHttpsServer.cpp @@ -2,9 +2,9 @@ #ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE -PsychicHttpsServer::PsychicHttpsServer() : PsychicHttpServer() +PsychicHttpsServer::PsychicHttpsServer(uint16_t port) : PsychicHttpServer(port) { - //for a SSL server + // for a SSL server ssl_config = HTTPD_SSL_CONFIG_DEFAULT(); ssl_config.httpd.open_fn = PsychicHttpServer::openCallback; ssl_config.httpd.close_fn = PsychicHttpServer::closeCallback; @@ -18,44 +18,53 @@ PsychicHttpsServer::PsychicHttpsServer() : PsychicHttpServer() // if we set it higher than 2 and use all the connections, we get lots of memory errors. // not to mention there is no heap left over for the program itself. ssl_config.httpd.max_open_sockets = 2; + + setPort(port); } PsychicHttpsServer::~PsychicHttpsServer() {} -esp_err_t PsychicHttpsServer::listen(uint16_t port, const char *cert, const char *private_key) +void PsychicHttpsServer::setPort(uint16_t port) { - this->_use_ssl = true; - this->ssl_config.port_secure = port; +} -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2) - this->ssl_config.servercert = (uint8_t *)cert; - this->ssl_config.servercert_len = strlen(cert)+1; -#else - this->ssl_config.cacert_pem = (uint8_t *)cert; - this->ssl_config.cacert_len = strlen(cert)+1; -#endif +uint16_t PsychicHttpsServer::getPort() +{ + return this->ssl_config.port_secure; +} - this->ssl_config.prvtkey_pem = (uint8_t *)private_key; - this->ssl_config.prvtkey_len = strlen(private_key)+1; +void PsychicHttpsServer::setCertificate(const uint8_t* cert, size_t cert_size, const uint8_t* private_key, size_t private_key_size) +{ + if (cert) { + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2) + this->ssl_config.servercert = cert; + this->ssl_config.servercert_len = cert_size; + #else + this->ssl_config.cacert_pem = cert; + this->ssl_config.cacert_len = cert_size; + #endif + } - return this->_start(); + if (private_key) { + this->ssl_config.prvtkey_pem = private_key; + this->ssl_config.prvtkey_len = private_key_size; + } } esp_err_t PsychicHttpsServer::_startServer() { - if (this->_use_ssl) - return httpd_ssl_start(&this->server, &this->ssl_config); - else - return httpd_start(&this->server, &this->config); + return httpd_ssl_start(&this->server, &this->ssl_config); } -void PsychicHttpsServer::stop() +esp_err_t PsychicHttpsServer::_stopServer() { - if (this->_use_ssl) - httpd_ssl_stop(this->server); - else - httpd_stop(this->server); + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 2) + return httpd_ssl_stop(this->server); + #else + httpd_ssl_stop(this->server); + return ESP_OK; + #endif } #endif // CONFIG_ESP_HTTPS_SERVER_ENABLE \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicHttpsServer.h b/lib/PsychicHttp/src/PsychicHttpsServer.h index c60b25b..bdc2b46 100644 --- a/lib/PsychicHttp/src/PsychicHttpsServer.h +++ b/lib/PsychicHttp/src/PsychicHttpsServer.h @@ -1,38 +1,44 @@ #ifndef PsychicHttpsServer_h -#define PsychicHttpsServer_h + #define PsychicHttpsServer_h -#include + #include -#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE + #ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE -#include "PsychicCore.h" -#include "PsychicHttpServer.h" -#include -#if !CONFIG_HTTPD_WS_SUPPORT - #error PsychicHttpsServer cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration -#endif + #include "PsychicCore.h" + #include "PsychicHttpServer.h" + #include + #if !CONFIG_HTTPD_WS_SUPPORT + #error PsychicHttpsServer cannot be used unless HTTPD_WS_SUPPORT is enabled in esp-http-server component configuration + #endif -#define PSY_ENABLE_SSL //you can use this define in your code to enable/disable these features + #ifndef PSY_ENABLE_SSL + #define PSY_ENABLE_SSL // you can use this define in your code to enable/disable these features + #endif class PsychicHttpsServer : public PsychicHttpServer { protected: - bool _use_ssl = false; + virtual esp_err_t _startServer() override final; + virtual esp_err_t _stopServer() override final; public: - PsychicHttpsServer(); + PsychicHttpsServer(uint16_t port = 443); ~PsychicHttpsServer(); httpd_ssl_config_t ssl_config; - using PsychicHttpServer::listen; //keep the regular version - esp_err_t listen(uint16_t port, const char *cert, const char *private_key); - - virtual esp_err_t _startServer() override final; - virtual void stop() override final; + // using PsychicHttpServer::listen; // keep the regular version + virtual void setPort(uint16_t port) override final; + virtual uint16_t getPort() override final; + // Pointer to certificate data in PEM format + void setCertificate(const char* cert, const char* private_key) { setCertificate((const uint8_t*)cert, strlen(cert) + 1, (const uint8_t*)private_key, private_key ? strlen(private_key) + 1 : 0); } + // Pointer to certificate data in PEM or DER format. PEM-format must have a terminating NULL-character. DER-format requires the length to be passed in certSize and keySize. + void setCertificate(const uint8_t* cert, size_t cert_size, const uint8_t* private_key, size_t private_key_size); }; + + #endif // PsychicHttpsServer_h + #else #warning ESP-IDF https server support not enabled. -#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE - -#endif // PsychicHttpsServer_h \ No newline at end of file +#endif // CONFIG_ESP_HTTPS_SERVER_ENABLE \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicJson.cpp b/lib/PsychicHttp/src/PsychicJson.cpp index ca0f0a5..3d69233 100644 --- a/lib/PsychicHttp/src/PsychicJson.cpp +++ b/lib/PsychicHttp/src/PsychicJson.cpp @@ -1,28 +1,30 @@ #include "PsychicJson.h" #ifdef ARDUINOJSON_6_COMPATIBILITY - PsychicJsonResponse::PsychicJsonResponse(PsychicRequest *request, bool isArray, size_t maxJsonBufferSize) : - PsychicResponse(request), - _jsonBuffer(maxJsonBufferSize) - { - setContentType(JSON_MIMETYPE); - if (isArray) - _root = _jsonBuffer.createNestedArray(); - else - _root = _jsonBuffer.createNestedObject(); - } +PsychicJsonResponse::PsychicJsonResponse(PsychicResponse* response, bool isArray, size_t maxJsonBufferSize) : __response(response), + _jsonBuffer(maxJsonBufferSize) +{ + response->setContentType(JSON_MIMETYPE); + if (isArray) + _root = _jsonBuffer.createNestedArray(); + else + _root = _jsonBuffer.createNestedObject(); +} #else - PsychicJsonResponse::PsychicJsonResponse(PsychicRequest *request, bool isArray) : PsychicResponse(request) - { - setContentType(JSON_MIMETYPE); - if (isArray) - _root = _jsonBuffer.add(); - else - _root = _jsonBuffer.add(); - } +PsychicJsonResponse::PsychicJsonResponse(PsychicResponse* response, bool isArray) : PsychicResponseDelegate(response) +{ + setContentType(JSON_MIMETYPE); + if (isArray) + _root = _jsonBuffer.add(); + else + _root = _jsonBuffer.add(); +} #endif -JsonVariant &PsychicJsonResponse::getRoot() { return _root; } +JsonVariant& PsychicJsonResponse::getRoot() +{ + return _root; +} size_t PsychicJsonResponse::getLength() { @@ -34,100 +36,93 @@ esp_err_t PsychicJsonResponse::send() esp_err_t err = ESP_OK; size_t length = getLength(); size_t buffer_size; - char *buffer; + char* buffer; - //how big of a buffer do we want? + // how big of a buffer do we want? if (length < JSON_BUFFER_SIZE) - buffer_size = length+1; + buffer_size = length + 1; else buffer_size = JSON_BUFFER_SIZE; - buffer = (char *)malloc(buffer_size); + buffer = (char*)malloc(buffer_size); if (buffer == NULL) { - httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); - return ESP_FAIL; + return error(HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); } - //send it in one shot or no? - if (length < JSON_BUFFER_SIZE) - { + // send it in one shot or no? + if (length < JSON_BUFFER_SIZE) { serializeJson(_root, buffer, buffer_size); - this->setContent((uint8_t *)buffer, length); - this->setContentType(JSON_MIMETYPE); + setContent((uint8_t*)buffer, length); + setContentType(JSON_MIMETYPE); - err = PsychicResponse::send(); - } - else - { - //helper class that acts as a stream to print chunked responses - ChunkPrinter dest(this, (uint8_t *)buffer, buffer_size); + err = send(); + } else { + // helper class that acts as a stream to print chunked responses + ChunkPrinter dest(_response, (uint8_t*)buffer, buffer_size); - //keep our headers - this->sendHeaders(); + // keep our headers + sendHeaders(); serializeJson(_root, dest); - //send the last bits + // send the last bits dest.flush(); - //done with our chunked response too - err = this->finishChunking(); + // done with our chunked response too + err = finishChunking(); } - //let the buffer go + // let the buffer go free(buffer); return err; } #ifdef ARDUINOJSON_6_COMPATIBILITY - PsychicJsonHandler::PsychicJsonHandler(size_t maxJsonBufferSize) : - _onRequest(NULL), - _maxJsonBufferSize(maxJsonBufferSize) - {}; +PsychicJsonHandler::PsychicJsonHandler(size_t maxJsonBufferSize) : _onRequest(NULL), + _maxJsonBufferSize(maxJsonBufferSize) {}; - PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize) : - _onRequest(onRequest), - _maxJsonBufferSize(maxJsonBufferSize) - {} +PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize) : _onRequest(onRequest), + _maxJsonBufferSize(maxJsonBufferSize) +{ +} #else - PsychicJsonHandler::PsychicJsonHandler() : - _onRequest(NULL) - {}; +PsychicJsonHandler::PsychicJsonHandler() : _onRequest(NULL) {}; - PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest) : - _onRequest(onRequest) - {} +PsychicJsonHandler::PsychicJsonHandler(PsychicJsonRequestCallback onRequest) : _onRequest(onRequest) +{ +} #endif -void PsychicJsonHandler::onRequest(PsychicJsonRequestCallback fn) { _onRequest = fn; } - -esp_err_t PsychicJsonHandler::handleRequest(PsychicRequest *request) +void PsychicJsonHandler::onRequest(PsychicJsonRequestCallback fn) { - //process basic stuff - PsychicWebHandler::handleRequest(request); + _onRequest = fn; +} - if (_onRequest) - { - #ifdef ARDUINOJSON_6_COMPATIBILITY - DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize); - DeserializationError error = deserializeJson(jsonBuffer, request->body()); - if (error) - return request->reply(400); +esp_err_t PsychicJsonHandler::handleRequest(PsychicRequest* request, PsychicResponse* response) +{ + // process basic stuff + PsychicWebHandler::handleRequest(request, response); - JsonVariant json = jsonBuffer.as(); - #else - JsonDocument jsonBuffer; - DeserializationError error = deserializeJson(jsonBuffer, request->body()); - if (error) - return request->reply(400); + if (_onRequest) { +#ifdef ARDUINOJSON_6_COMPATIBILITY + DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize); + DeserializationError error = deserializeJson(jsonBuffer, request->body()); + if (error) + return response->send(400); - JsonVariant json = jsonBuffer.as(); - #endif + JsonVariant json = jsonBuffer.as(); +#else + JsonDocument jsonBuffer; + DeserializationError error = deserializeJson(jsonBuffer, request->body()); + if (error) + return response->send(400); - return _onRequest(request, json); - } - else - return request->reply(500); + JsonVariant json = jsonBuffer.as(); +#endif + + return _onRequest(request, response, json); + } else + return response->send(500); } \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicJson.h b/lib/PsychicHttp/src/PsychicJson.h index 95ca894..76f67a1 100644 --- a/lib/PsychicHttp/src/PsychicJson.h +++ b/lib/PsychicHttp/src/PsychicJson.h @@ -8,9 +8,9 @@ #ifndef PSYCHIC_JSON_H_ #define PSYCHIC_JSON_H_ +#include "ChunkPrinter.h" #include "PsychicRequest.h" #include "PsychicWebHandler.h" -#include "ChunkPrinter.h" #include #if ARDUINOJSON_VERSION_MAJOR == 6 @@ -20,70 +20,71 @@ #endif #endif - #ifndef JSON_BUFFER_SIZE - #define JSON_BUFFER_SIZE 4*1024 + #define JSON_BUFFER_SIZE 4 * 1024 #endif -constexpr const char *JSON_MIMETYPE = "application/json"; +constexpr const char* JSON_MIMETYPE = "application/json"; /* * Json Response * */ -class PsychicJsonResponse : public PsychicResponse +class PsychicJsonResponse : public PsychicResponseDelegate { protected: - #ifdef ARDUINOJSON_5_COMPATIBILITY - DynamicJsonBuffer _jsonBuffer; - #elif ARDUINOJSON_VERSION_MAJOR == 6 - DynamicJsonDocument _jsonBuffer; - #else - JsonDocument _jsonBuffer; - #endif +#ifdef ARDUINOJSON_5_COMPATIBILITY + DynamicJsonBuffer _jsonBuffer; +#elif ARDUINOJSON_VERSION_MAJOR == 6 + DynamicJsonDocument _jsonBuffer; +#else + JsonDocument _jsonBuffer; +#endif JsonVariant _root; size_t _contentLength; public: - #ifdef ARDUINOJSON_5_COMPATIBILITY - PsychicJsonResponse(PsychicRequest *request, bool isArray = false); - #elif ARDUINOJSON_VERSION_MAJOR == 6 - PsychicJsonResponse(PsychicRequest *request, bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); - #else - PsychicJsonResponse(PsychicRequest *request, bool isArray = false); - #endif +#ifdef ARDUINOJSON_5_COMPATIBILITY + PsychicJsonResponse(PsychicResponse* response, bool isArray = false); +#elif ARDUINOJSON_VERSION_MAJOR == 6 + PsychicJsonResponse(PsychicResponse* response, bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); +#else + PsychicJsonResponse(PsychicResponse* response, bool isArray = false); +#endif - ~PsychicJsonResponse() {} + ~PsychicJsonResponse() + { + } - JsonVariant &getRoot(); + JsonVariant& getRoot(); size_t getLength(); - - virtual esp_err_t send() override; + + esp_err_t send(); }; class PsychicJsonHandler : public PsychicWebHandler { protected: PsychicJsonRequestCallback _onRequest; - #if ARDUINOJSON_VERSION_MAJOR == 6 - const size_t _maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE; - #endif +#if ARDUINOJSON_VERSION_MAJOR == 6 + const size_t _maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE; +#endif public: - #ifdef ARDUINOJSON_5_COMPATIBILITY - PsychicJsonHandler(); - PsychicJsonHandler(PsychicJsonRequestCallback onRequest); - #elif ARDUINOJSON_VERSION_MAJOR == 6 - PsychicJsonHandler(size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); - PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); - #else - PsychicJsonHandler(); - PsychicJsonHandler(PsychicJsonRequestCallback onRequest); - #endif +#ifdef ARDUINOJSON_5_COMPATIBILITY + PsychicJsonHandler(); + PsychicJsonHandler(PsychicJsonRequestCallback onRequest); +#elif ARDUINOJSON_VERSION_MAJOR == 6 + PsychicJsonHandler(size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); + PsychicJsonHandler(PsychicJsonRequestCallback onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); +#else + PsychicJsonHandler(); + PsychicJsonHandler(PsychicJsonRequestCallback onRequest); +#endif void onRequest(PsychicJsonRequestCallback fn); - virtual esp_err_t handleRequest(PsychicRequest *request) override; + virtual esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override; }; #endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicMiddleware.cpp b/lib/PsychicHttp/src/PsychicMiddleware.cpp new file mode 100644 index 0000000..34f48dc --- /dev/null +++ b/lib/PsychicHttp/src/PsychicMiddleware.cpp @@ -0,0 +1,6 @@ +#include "PsychicMiddleware.h" + +esp_err_t PsychicMiddlewareFunction::run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareNext next) +{ + return _fn(request, request->response(), next); +} diff --git a/lib/PsychicHttp/src/PsychicMiddleware.h b/lib/PsychicHttp/src/PsychicMiddleware.h new file mode 100644 index 0000000..97580f8 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicMiddleware.h @@ -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 diff --git a/lib/PsychicHttp/src/PsychicMiddlewareChain.cpp b/lib/PsychicHttp/src/PsychicMiddlewareChain.cpp new file mode 100644 index 0000000..3e72b71 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicMiddlewareChain.cpp @@ -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::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(); +} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicMiddlewareChain.h b/lib/PsychicHttp/src/PsychicMiddlewareChain.h new file mode 100644 index 0000000..c6ccaaa --- /dev/null +++ b/lib/PsychicHttp/src/PsychicMiddlewareChain.h @@ -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 _middleware; +}; + +#endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicMiddlewares.cpp b/lib/PsychicHttp/src/PsychicMiddlewares.cpp new file mode 100644 index 0000000..dd5bb12 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicMiddlewares.cpp @@ -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::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(); +} diff --git a/lib/PsychicHttp/src/PsychicMiddlewares.h b/lib/PsychicHttp/src/PsychicMiddlewares.h new file mode 100644 index 0000000..cb0473d --- /dev/null +++ b/lib/PsychicHttp/src/PsychicMiddlewares.h @@ -0,0 +1,78 @@ +#ifndef PsychicMiddlewares_h +#define PsychicMiddlewares_h + +#include "PsychicMiddleware.h" + +#include +#include + +// 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 diff --git a/lib/PsychicHttp/src/PsychicRequest.cpp b/lib/PsychicHttp/src/PsychicRequest.cpp index 2005d91..e4272d0 100644 --- a/lib/PsychicHttp/src/PsychicRequest.cpp +++ b/lib/PsychicHttp/src/PsychicRequest.cpp @@ -1,91 +1,122 @@ #include "PsychicRequest.h" -#include "http_status.h" +#include "MultipartProcessor.h" #include "PsychicHttpServer.h" +#include "http_status.h" - -PsychicRequest::PsychicRequest(PsychicHttpServer *server, httpd_req_t *req) : - _server(server), - _req(req), - _method(HTTP_GET), - _query(""), - _body(""), - _tempObject(NULL) +PsychicRequest::PsychicRequest(PsychicHttpServer* server, httpd_req_t* req) : _server(server), + _req(req), + _endpoint(nullptr), + _method(HTTP_GET), + _uri(""), + _query(""), + _body(""), + _tempObject(nullptr) { - //load up our client. + // load up our client. this->_client = server->getClient(req); - //handle our session data + // handle our session data if (req->sess_ctx != NULL) - this->_session = (SessionData *)req->sess_ctx; - else - { + this->_session = (SessionData*)req->sess_ctx; + else { this->_session = new SessionData(); req->sess_ctx = this->_session; } - //callback for freeing the session later + // callback for freeing the session later req->free_ctx = this->freeSession; - //load up some data - this->_uri = String(this->_req->uri); + // load and parse our uri. + this->_setUri(this->_req->uri); + + _response = new PsychicResponse(this); } PsychicRequest::~PsychicRequest() { - //temorary user object + // temorary user object if (_tempObject != NULL) free(_tempObject); - //our web parameters - for (auto *param : _params) - delete(param); + // our web parameters + for (auto* param : _params) + delete (param); _params.clear(); + + delete _response; } -void PsychicRequest::freeSession(void *ctx) +void PsychicRequest::freeSession(void* ctx) { - if (ctx != NULL) - { - SessionData *session = (SessionData*)ctx; + if (ctx != NULL) { + SessionData* session = (SessionData*)ctx; delete session; } } -PsychicHttpServer * PsychicRequest::server() { +PsychicHttpServer* PsychicRequest::server() +{ return _server; } -httpd_req_t * PsychicRequest::request() { +httpd_req_t* PsychicRequest::request() +{ return _req; } -PsychicClient * PsychicRequest::client() { +PsychicClient* PsychicRequest::client() +{ return _client; } +PsychicEndpoint* PsychicRequest::endpoint() +{ + return _endpoint; +} + +void PsychicRequest::setEndpoint(PsychicEndpoint* endpoint) +{ + _endpoint = endpoint; +} + +#ifdef PSY_ENABLE_REGEX +bool PsychicRequest::getRegexMatches(std::smatch& matches, bool use_full_uri) +{ + if (_endpoint != nullptr) { + std::regex pattern(_endpoint->uri().c_str()); + std::string s(this->path().c_str()); + if (use_full_uri) + s = this->uri().c_str(); + + return std::regex_search(s, matches, pattern); + } + + return false; +} +#endif + const String PsychicRequest::getFilename() { - //parse the content-disposition header - if (this->hasHeader("Content-Disposition")) - { + // parse the content-disposition header + if (this->hasHeader("Content-Disposition")) { ContentDisposition cd = this->getContentDisposition(); if (cd.filename != "") return cd.filename; } - //fall back to passed in query string - PsychicWebParameter *param = getParam("_filename"); + // fall back to passed in query string + PsychicWebParameter* param = getParam("_filename"); if (param != NULL) return param->name(); - //fall back to parsing it from url (useful for wildcard uploads) + // fall back to parsing it from url (useful for wildcard uploads) String uri = this->uri(); int filenameStart = uri.lastIndexOf('/') + 1; String filename = uri.substring(filenameStart); if (filename != "") return filename; - //finally, unknown. + // finally, unknown. ESP_LOGE(PH_TAG, "Did not get a valid filename from the upload."); return "unknown.txt"; } @@ -103,21 +134,19 @@ const ContentDisposition PsychicRequest::getContentDisposition() cd.disposition = ATTACHMENT; else if (header.indexOf("inline") == 0) cd.disposition = INLINE; - else + else cd.disposition = NONE; start = header.indexOf("filename="); - if (start) - { - end = header.indexOf('"', start+10); - cd.filename = header.substring(start+10, end-1); + if (start) { + end = header.indexOf('"', start + 10); + cd.filename = header.substring(start + 10, end - 1); } start = header.indexOf("name="); - if (start) - { - end = header.indexOf('"', start+6); - cd.name = header.substring(start+6, end-1); + if (start) { + end = header.indexOf('"', start + 6); + cd.name = header.substring(start + 6, end - 1); } return cd; @@ -125,16 +154,23 @@ const ContentDisposition PsychicRequest::getContentDisposition() esp_err_t PsychicRequest::loadBody() { - esp_err_t err = ESP_OK; + if (_bodyParsed != ESP_ERR_NOT_FINISHED) + return _bodyParsed; + + // quick size check. + if (contentLength() > server()->maxRequestBodySize) { + ESP_LOGE(PH_TAG, "Body size larger than maxRequestBodySize"); + return _bodyParsed = ESP_ERR_INVALID_SIZE; + } this->_body = String(); size_t remaining = this->_req->content_len; size_t actuallyReceived = 0; - char *buf = (char *)malloc(remaining + 1); + char* buf = (char*)malloc(remaining + 1); if (buf == NULL) { ESP_LOGE(PH_TAG, "Failed to allocate memory for body"); - return ESP_FAIL; + return _bodyParsed = ESP_FAIL; } while (remaining > 0) { @@ -142,10 +178,9 @@ esp_err_t PsychicRequest::loadBody() if (received == HTTPD_SOCK_ERR_TIMEOUT) { continue; - } - else if (received == HTTPD_SOCK_ERR_FAIL) { + } else if (received == HTTPD_SOCK_ERR_FAIL) { ESP_LOGE(PH_TAG, "Failed to receive data."); - err = ESP_FAIL; + _bodyParsed = ESP_FAIL; break; } @@ -156,30 +191,38 @@ esp_err_t PsychicRequest::loadBody() buf[actuallyReceived] = '\0'; this->_body = String(buf); free(buf); - return err; + + _bodyParsed = ESP_OK; + + return _bodyParsed; } -http_method PsychicRequest::method() { +http_method PsychicRequest::method() +{ return (http_method)this->_req->method; } -const String PsychicRequest::methodStr() { +const String PsychicRequest::methodStr() +{ return String(http_method_str((http_method)this->_req->method)); } -const String PsychicRequest::path() { +const String PsychicRequest::path() +{ int index = _uri.indexOf("?"); - if(index == -1) + if (index == -1) return _uri; else - return _uri.substring(0, index); + return _uri.substring(0, index); } -const String& PsychicRequest::uri() { +const String& PsychicRequest::uri() +{ return this->_uri; } -const String& PsychicRequest::query() { +const String& PsychicRequest::query() +{ return this->_query; } @@ -188,35 +231,36 @@ const String& PsychicRequest::query() { // { // } -const String PsychicRequest::header(const char *name) +const String PsychicRequest::header(const char* name) { size_t header_len = httpd_req_get_hdr_value_len(this->_req, name); - //if we've got one, allocated it and load it - if (header_len) - { - char header[header_len+1]; + // if we've got one, allocated it and load it + if (header_len) { + char header[header_len + 1]; httpd_req_get_hdr_value_str(this->_req, name, header, sizeof(header)); return String(header); - } - else + } else return ""; } -bool PsychicRequest::hasHeader(const char *name) +bool PsychicRequest::hasHeader(const char* name) { return httpd_req_get_hdr_value_len(this->_req, name) > 0; } -const String PsychicRequest::host() { +const String PsychicRequest::host() +{ return this->header("Host"); } -const String PsychicRequest::contentType() { +const String PsychicRequest::contentType() +{ return header("Content-Type"); } -size_t PsychicRequest::contentLength() { +size_t PsychicRequest::contentLength() +{ return this->_req->content_len; } @@ -232,90 +276,99 @@ bool PsychicRequest::isMultipart() return (this->contentType().indexOf("multipart/form-data") >= 0); } -esp_err_t PsychicRequest::redirect(const char *url) +bool PsychicRequest::hasCookie(const char* key, size_t* size) { - PsychicResponse response(this); - response.setCode(301); - response.addHeader("Location", url); + char buffer; - return response.send(); + // this keeps our size for the user. + if (size != nullptr) { + *size = 1; + return getCookie(key, &buffer, size) != ESP_ERR_NOT_FOUND; + } + // this just checks that it exists. + else { + size_t mysize = 1; + return getCookie(key, &buffer, &mysize) != ESP_ERR_NOT_FOUND; + } } -bool PsychicRequest::hasCookie(const char *key) +esp_err_t PsychicRequest::getCookie(const char* key, char* buffer, size_t* size) { - char cookie[MAX_COOKIE_SIZE]; - size_t cookieSize = MAX_COOKIE_SIZE; - esp_err_t err = httpd_req_get_cookie_val(this->_req, key, cookie, &cookieSize); - - //did we get anything? - if (err == ESP_OK) - return true; - else if (err == ESP_ERR_HTTPD_RESULT_TRUNC) - ESP_LOGE(PH_TAG, "cookie too large (%d bytes).\n", cookieSize); - - return false; + return httpd_req_get_cookie_val(this->_req, key, buffer, size); } -const String PsychicRequest::getCookie(const char *key) +String PsychicRequest::getCookie(const char* key) { - char cookie[MAX_COOKIE_SIZE]; - size_t cookieSize = MAX_COOKIE_SIZE; - esp_err_t err = httpd_req_get_cookie_val(this->_req, key, cookie, &cookieSize); + String cookie = ""; - //did we get anything? + // how big is our cookie? + size_t size; + if (!hasCookie("counter", &size)) + return cookie; + + // allocate cookie buffer... keep it on the stack + char buf[size]; + + // load it up. + esp_err_t err = getCookie(key, buf, &size); if (err == ESP_OK) - return String(cookie); - else - return ""; + cookie.concat(buf); + + return cookie; +} + +void PsychicRequest::replaceResponse(PsychicResponse* response) +{ + delete _response; + _response = response; +} + +void PsychicRequest::addResponseHeader(const char* key, const char* value) +{ + _response->addHeader(key, value); +} + +std::list& PsychicRequest::getResponseHeaders() +{ + return _response->headers(); } void PsychicRequest::loadParams() { - //did we get a query string? - size_t query_len = httpd_req_get_url_query_len(_req); - if (query_len) - { - char query[query_len+1]; - httpd_req_get_url_query_str(_req, query, sizeof(query)); - _query.concat(query); + if (_paramsParsed != ESP_ERR_NOT_FINISHED) + return; - //parse them. + // convenience shortcut to allow calling loadParams() + if (_bodyParsed == ESP_ERR_NOT_FINISHED) + loadBody(); + + // various form data as parameters + if (this->method() == HTTP_POST) { + if (this->contentType().startsWith("application/x-www-form-urlencoded")) + _addParams(_body, true); + + if (this->isMultipart()) { + MultipartProcessor mpp(this); + _paramsParsed = mpp.process(_body.c_str()); + return; + } + } + + _paramsParsed = ESP_OK; +} + +void PsychicRequest::_setUri(const char* uri) +{ + // save it + _uri = String(uri); + + // look for our query separator + int index = _uri.indexOf('?', 0); + if (index) { + // parse them. + _query = _uri.substring(index + 1); _addParams(_query, false); } - - //did we get form data as body? - if (this->method() == HTTP_POST && this->contentType().startsWith("application/x-www-form-urlencoded")) - { - _addParams(_body, true); - } -} - -void PsychicRequest::_addParams(const String& params, bool post){ - size_t start = 0; - while (start < params.length()){ - int end = params.indexOf('&', start); - if (end < 0) end = params.length(); - int equal = params.indexOf('=', start); - if (equal < 0 || equal > end) equal = end; - String name = params.substring(start, equal); - String value = equal + 1 < end ? params.substring(equal + 1, end) : String(); - addParam(name, value, true, post); - start = end + 1; - } -} - -PsychicWebParameter * PsychicRequest::addParam(const String &name, const String &value, bool decode, bool post) -{ - if (decode) - return addParam(new PsychicWebParameter(urlDecode(name.c_str()), urlDecode(value.c_str()), post)); - else - return addParam(new PsychicWebParameter(name, value, post)); -} - -PsychicWebParameter * PsychicRequest::addParam(PsychicWebParameter *param) { - // ESP_LOGD(PH_TAG, "Adding param: '%s' = '%s'", param->name().c_str(), param->value().c_str()); - _params.push_back(param); - return param; } int PsychicRequest::params() @@ -323,14 +376,51 @@ int PsychicRequest::params() return _params.size(); } -bool PsychicRequest::hasParam(const char *key) +void PsychicRequest::_addParams(const String& params, bool post) +{ + size_t start = 0; + while (start < params.length()) { + int end = params.indexOf('&', start); + if (end < 0) + end = params.length(); + int equal = params.indexOf('=', start); + if (equal < 0 || equal > end) + equal = end; + String name = params.substring(start, equal); + String value = equal + 1 < end ? params.substring(equal + 1, end) : String(); + addParam(name, value, true, post); + start = end + 1; + } +} + +PsychicWebParameter* PsychicRequest::addParam(const String& name, const String& value, bool decode, bool post) +{ + if (decode) + return addParam(new PsychicWebParameter(urlDecode(name.c_str()), urlDecode(value.c_str()), post)); + else + return addParam(new PsychicWebParameter(name, value, post)); +} + +PsychicWebParameter* PsychicRequest::addParam(PsychicWebParameter* param) +{ + // ESP_LOGD(PH_TAG, "Adding param: '%s' = '%s'", param->name().c_str(), param->value().c_str()); + _params.push_back(param); + return param; +} + +bool PsychicRequest::hasParam(const char* key) { return getParam(key) != NULL; } -PsychicWebParameter * PsychicRequest::getParam(const char *key) +bool PsychicRequest::hasParam(const char* key, bool isPost, bool isFile) { - for (auto *param : _params) + return getParam(key, isPost, isFile) != NULL; +} + +PsychicWebParameter* PsychicRequest::getParam(const char* key) +{ + for (auto* param : _params) if (param->name().equals(key)) return param; @@ -349,6 +439,14 @@ PsychicWebParameter * PsychicRequest::getParam(int index) return NULL; } +PsychicWebParameter* PsychicRequest::getParam(const char* key, bool isPost, bool isFile) +{ + for (auto* param : _params) + if (param->name().equals(key) && isPost == param->isPost() && isFile == param->isFile()) + return param; + return NULL; +} + bool PsychicRequest::hasSessionKey(const String& key) { return this->_session->find(key) != this->_session->end(); @@ -368,7 +466,8 @@ void PsychicRequest::setSessionKey(const String& key, const String& value) this->_session->insert(std::pair(key, value)); } -static const String md5str(const String &in){ +static const String md5str(const String& in) +{ MD5Builder md5 = MD5Builder(); md5.begin(); md5.add(in); @@ -376,28 +475,27 @@ static const String md5str(const String &in){ return md5.toString(); } -bool PsychicRequest::authenticate(const char * username, const char * password) +bool PsychicRequest::authenticate(const char* username, const char* password) { - if(hasHeader("Authorization")) - { + if (hasHeader("Authorization")) { String authReq = header("Authorization"); - if(authReq.startsWith("Basic")){ + if (authReq.startsWith("Basic")) { authReq = authReq.substring(6); authReq.trim(); - char toencodeLen = strlen(username)+strlen(password)+1; - char *toencode = new char[toencodeLen + 1]; - if(toencode == NULL){ + char toencodeLen = strlen(username) + strlen(password) + 1; + char* toencode = new char[toencodeLen + 1]; + if (toencode == NULL) { authReq = ""; return false; } - char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; - if(encoded == NULL){ + char* encoded = new char[base64_encode_expected_len(toencodeLen) + 1]; + if (encoded == NULL) { authReq = ""; delete[] toencode; return false; } sprintf(toencode, "%s:%s", username, password); - if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) { + if (base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) { authReq = ""; delete[] toencode; delete[] encoded; @@ -405,64 +503,62 @@ bool PsychicRequest::authenticate(const char * username, const char * password) } delete[] toencode; delete[] encoded; - } - else if(authReq.startsWith(F("Digest"))) - { + } else if (authReq.startsWith(F("Digest"))) { authReq = authReq.substring(7); - String _username = _extractParam(authReq,F("username=\""),'\"'); - if(!_username.length() || _username != String(username)) { + String _username = _extractParam(authReq, F("username=\""), '\"'); + if (!_username.length() || _username != String(username)) { authReq = ""; return false; } // extracting required parameters for RFC 2069 simpler Digest - String _realm = _extractParam(authReq, F("realm=\""),'\"'); - String _nonce = _extractParam(authReq, F("nonce=\""),'\"'); - String _uri = _extractParam(authReq, F("uri=\""),'\"'); - String _resp = _extractParam(authReq, F("response=\""),'\"'); - String _opaque = _extractParam(authReq, F("opaque=\""),'\"'); + String _realm = _extractParam(authReq, F("realm=\""), '\"'); + String _nonce = _extractParam(authReq, F("nonce=\""), '\"'); + String _url = _extractParam(authReq, F("uri=\""), '\"'); + String _resp = _extractParam(authReq, F("response=\""), '\"'); + String _opaque = _extractParam(authReq, F("opaque=\""), '\"'); - if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_resp.length()) || (!_opaque.length())) { + if ((!_realm.length()) || (!_nonce.length()) || (!_url.length()) || (!_resp.length()) || (!_opaque.length())) { authReq = ""; return false; } - if((_opaque != this->getSessionKey("opaque")) || (_nonce != this->getSessionKey("nonce")) || (_realm != this->getSessionKey("realm"))) - { + if ((_opaque != this->getSessionKey("opaque")) || (_nonce != this->getSessionKey("nonce")) || (_realm != this->getSessionKey("realm"))) { authReq = ""; return false; } // parameters for the RFC 2617 newer Digest - String _nc,_cnonce; - if(authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) { + String _nc, _cnonce; + if (authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) { _nc = _extractParam(authReq, F("nc="), ','); - _cnonce = _extractParam(authReq, F("cnonce=\""),'\"'); + _cnonce = _extractParam(authReq, F("cnonce=\""), '\"'); } - + String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password)); - //ESP_LOGD(PH_TAG, "Hash of user:realm:pass=%s", _H1.c_str()); - + // ESP_LOGD(PH_TAG, "Hash of user:realm:pass=%s", _H1.c_str()); + String _H2 = ""; - if(_method == HTTP_GET){ - _H2 = md5str(String(F("GET:")) + _uri); - }else if(_method == HTTP_POST){ - _H2 = md5str(String(F("POST:")) + _uri); - }else if(_method == HTTP_PUT){ - _H2 = md5str(String(F("PUT:")) + _uri); - }else if(_method == HTTP_DELETE){ - _H2 = md5str(String(F("DELETE:")) + _uri); - }else{ - _H2 = md5str(String(F("GET:")) + _uri); - } - //ESP_LOGD(PH_TAG, "Hash of GET:uri=%s", _H2.c_str()); - - String _responsecheck = ""; - if(authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) { - _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2); + http_method method = this->method(); + if (method == HTTP_GET) { + _H2 = md5str(String(F("GET:")) + _url); + } else if (method == HTTP_POST) { + _H2 = md5str(String(F("POST:")) + _url); + } else if (method == HTTP_PUT) { + _H2 = md5str(String(F("PUT:")) + _url); + } else if (method == HTTP_DELETE) { + _H2 = md5str(String(F("DELETE:")) + _url); } else { - _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2); + _H2 = md5str(String(F("GET:")) + _url); } - - //ESP_LOGD(PH_TAG, "The Proper response=%s", _responsecheck.c_str()); - if(_resp == _responsecheck){ + // ESP_LOGD(PH_TAG, "Hash of GET:uri=%s", _H2.c_str()); + + String _responsecheck = ""; + if (authReq.indexOf("qop=auth") != -1 || authReq.indexOf("qop=\"auth\"") != -1) { + _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2); + } else { + _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2); + } + + // ESP_LOGD(PH_TAG, "The Proper response=%s", _responsecheck.c_str()); + if (_resp == _responsecheck) { authReq = ""; return true; } @@ -477,23 +573,23 @@ const String PsychicRequest::_extractParam(const String& authReq, const String& int _begin = authReq.indexOf(param); if (_begin == -1) return ""; - return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length())); + return authReq.substring(_begin + param.length(), authReq.indexOf(delimit, _begin + param.length())); } const String PsychicRequest::_getRandomHexString() { - char buffer[33]; // buffer to hold 32 Hex Digit + /0 + char buffer[33]; // buffer to hold 32 Hex Digit + /0 int i; - for(i = 0; i < 4; i++) { - sprintf (buffer + (i*8), "%08lx", (unsigned long int)esp_random()); + for (i = 0; i < 4; i++) { + sprintf(buffer + (i * 8), "%08lx", (unsigned long int)esp_random()); } return String(buffer); } esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, const char* realm, const char* authFailMsg) { - //what is thy realm, sire? - if(!strcmp(realm, "")) + // what is thy realm, sire? + if (!strcmp(realm, "")) this->setSessionKey("realm", "Login Required"); else this->setSessionKey("realm", realm); @@ -501,17 +597,14 @@ esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, const char* PsychicResponse response(this); String authStr; - //what kind of auth? - if(mode == BASIC_AUTH) - { + // what kind of auth? + if (mode == BASIC_AUTH) { authStr = "Basic realm=\"" + this->getSessionKey("realm") + "\""; response.addHeader("WWW-Authenticate", authStr.c_str()); - } - else - { - //only make new ones if we havent sent them yet + } else { + // only make new ones if we havent sent them yet if (this->getSessionKey("nonce").isEmpty()) - this->setSessionKey("nonce", _getRandomHexString()); + this->setSessionKey("nonce", _getRandomHexString()); if (this->getSessionKey("opaque").isEmpty()) this->setSessionKey("opaque", _getRandomHexString()); @@ -521,39 +614,6 @@ esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, const char* response.setCode(401); response.setContentType("text/html"); - response.setContent(authStr.c_str()); + response.setContent(authFailMsg); return response.send(); } - -esp_err_t PsychicRequest::reply(int code) -{ - PsychicResponse response(this); - - response.setCode(code); - response.setContentType("text/plain"); - response.setContent(http_status_reason(code)); - - return response.send(); -} - -esp_err_t PsychicRequest::reply(const char *content) -{ - PsychicResponse response(this); - - response.setCode(200); - response.setContentType("text/html"); - response.setContent(content); - - return response.send(); -} - -esp_err_t PsychicRequest::reply(int code, const char *contentType, const char *content) -{ - PsychicResponse response(this); - - response.setCode(code); - response.setContentType(contentType); - response.setContent(content); - - return response.send(); -} \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicRequest.h b/lib/PsychicHttp/src/PsychicRequest.h index 5b54d6e..59411ac 100644 --- a/lib/PsychicHttp/src/PsychicRequest.h +++ b/lib/PsychicHttp/src/PsychicRequest.h @@ -1,38 +1,55 @@ #ifndef PsychicRequest_h #define PsychicRequest_h -#include "PsychicCore.h" -#include "PsychicHttpServer.h" #include "PsychicClient.h" +#include "PsychicCore.h" +#include "PsychicEndpoint.h" +#include "PsychicHttpServer.h" #include "PsychicWebParameter.h" -#include "PsychicResponse.h" + +#ifdef PSY_ENABLE_REGEX + #include +#endif typedef std::map SessionData; -enum Disposition { NONE, INLINE, ATTACHMENT, FORM_DATA}; - -struct ContentDisposition { - Disposition disposition; - String filename; - String name; +enum Disposition { + NONE, + INLINE, + ATTACHMENT, + FORM_DATA }; -class PsychicRequest { - friend PsychicHttpServer; +struct ContentDisposition { + Disposition disposition; + String filename; + String name; +}; + +class PsychicRequest +{ + friend PsychicHttpServer; + friend PsychicResponse; protected: - PsychicHttpServer *_server; - httpd_req_t *_req; - SessionData *_session; - PsychicClient *_client; + PsychicHttpServer* _server; + httpd_req_t* _req; + SessionData* _session; + PsychicClient* _client; + PsychicEndpoint* _endpoint; http_method _method; String _uri; String _query; String _body; + esp_err_t _bodyParsed = ESP_ERR_NOT_FINISHED; + esp_err_t _paramsParsed = ESP_ERR_NOT_FINISHED; std::list _params; + PsychicResponse* _response; + + void _setUri(const char* uri); void _addParams(const String& params, bool post); void _parseGETParams(); void _parsePOSTParams(); @@ -41,28 +58,59 @@ class PsychicRequest { const String _getRandomHexString(); public: - PsychicRequest(PsychicHttpServer *server, httpd_req_t *req); + PsychicRequest(PsychicHttpServer* server, httpd_req_t* req); virtual ~PsychicRequest(); - void *_tempObject; + void* _tempObject; - PsychicHttpServer * server(); - httpd_req_t * request(); - virtual PsychicClient * client(); + PsychicHttpServer* server(); + httpd_req_t* request(); + virtual PsychicClient* client(); + + PsychicEndpoint* endpoint(); + void setEndpoint(PsychicEndpoint* endpoint); + +#ifdef PSY_ENABLE_REGEX + bool getRegexMatches(std::smatch& matches, bool use_full_uri = false); +#endif bool isMultipart(); esp_err_t loadBody(); - const String header(const char *name); - bool hasHeader(const char *name); + const String header(const char* name); + bool hasHeader(const char* name); - static void freeSession(void *ctx); + static void freeSession(void* ctx); bool hasSessionKey(const String& key); const String getSessionKey(const String& key); void setSessionKey(const String& key, const String& value); - bool hasCookie(const char * key); - const String getCookie(const char * key); + bool hasCookie(const char* key, size_t* size = nullptr); + + PsychicResponse* response() { return _response; } + void replaceResponse(PsychicResponse* response); + void addResponseHeader(const char* key, const char* value); + std::list& getResponseHeaders(); + + /** + * @brief Get the value string of a cookie value from the "Cookie" request headers by cookie name. + * + * @param[in] key The cookie name to be searched in the request + * @param[out] buffer Pointer to the buffer into which the value of cookie will be copied if the cookie is found + * @param[inout] size Pointer to size of the user buffer "val". This variable will contain cookie length if + * ESP_OK is returned and required buffer length in case ESP_ERR_HTTPD_RESULT_TRUNC is returned. + * + * @return + * - ESP_OK : Key is found in the cookie string and copied to buffer. The value is null-terminated. + * - ESP_ERR_NOT_FOUND : Key not found + * - ESP_ERR_INVALID_ARG : Null arguments + * - ESP_ERR_HTTPD_RESULT_TRUNC : Value string truncated + * - ESP_ERR_NO_MEM : Memory allocation failure + */ + esp_err_t getCookie(const char* key, char* buffer, size_t* size); + + // convenience / lazy function for getting cookies. + String getCookie(const char* key); http_method method(); // returns the HTTP method used as enum value (eg. HTTP_GET) const String methodStr(); // returns the HTTP method used as a string (eg. "GET") @@ -74,27 +122,25 @@ class PsychicRequest { size_t contentLength(); // returns the Content-Length header value const String& body(); // returns the body of the request const ContentDisposition getContentDisposition(); + const char* version() { return "HTTP/1.1"; } - const String& queryString() { return query(); } //compatability function. same as query() - const String& url() { return uri(); } //compatability function. same as uri() + const String& queryString() { return query(); } // compatability function. same as query() + const String& url() { return uri(); } // compatability function. same as uri() void loadParams(); - PsychicWebParameter * addParam(PsychicWebParameter *param); - PsychicWebParameter * addParam(const String &name, const String &value, bool decode = true, bool post = false); + PsychicWebParameter* addParam(PsychicWebParameter* param); + PsychicWebParameter* addParam(const String& name, const String& value, bool decode = true, bool post = false); int params(); - bool hasParam(const char *key); - PsychicWebParameter * getParam(const char *name); - PsychicWebParameter * getParam(int index); - + bool hasParam(const char* key); + bool hasParam(const char* key, bool isPost, bool isFile = false); + PsychicWebParameter* getParam(const char* name); + PsychicWebParameter* getParam(int index); + PsychicWebParameter* getParam(const char* name, bool isPost, bool isFile = false); + const String getFilename(); - bool authenticate(const char * username, const char * password); + bool authenticate(const char* username, const char* password); esp_err_t requestAuthentication(HTTPAuthMethod mode, const char* realm, const char* authFailMsg); - - esp_err_t redirect(const char *url); - esp_err_t reply(int code); - esp_err_t reply(const char *content); - esp_err_t reply(int code, const char *contentType, const char *content); }; #endif // PsychicRequest_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicResponse.cpp b/lib/PsychicHttp/src/PsychicResponse.cpp index 5046441..8379592 100644 --- a/lib/PsychicHttp/src/PsychicResponse.cpp +++ b/lib/PsychicHttp/src/PsychicResponse.cpp @@ -2,52 +2,49 @@ #include "PsychicRequest.h" #include -PsychicResponse::PsychicResponse(PsychicRequest *request) : - _request(request), - _code(200), - _status(""), - _contentLength(0), - _body("") +PsychicResponse::PsychicResponse(PsychicRequest* request) : _request(request), + _code(200), + _status(""), + _contentType(emptyString), + _contentLength(0), + _body("") { + // get our global headers out of the way + for (auto& header : DefaultHeaders::Instance().getHeaders()) + addHeader(header.field.c_str(), header.value.c_str()); } PsychicResponse::~PsychicResponse() { - //clean up our header variables. we have to do this on desctruct since httpd_resp_send doesn't store copies - for (HTTPHeader header : _headers) - { - free(header.field); - free(header.value); - } _headers.clear(); } -void PsychicResponse::addHeader(const char *field, const char *value) +void PsychicResponse::addHeader(const char* field, const char* value) { - //these get freed after send by the destructor - HTTPHeader header; - header.field =(char *)malloc(strlen(field)+1); - header.value = (char *)malloc(strlen(value)+1); + // erase any existing ones. + for (auto itr = _headers.begin(); itr != _headers.end();) { + if (itr->field.equalsIgnoreCase(field)) + itr = _headers.erase(itr); + else + itr++; + } - strlcpy(header.field, field, strlen(field)+1); - strlcpy(header.value, value, strlen(value)+1); - - _headers.push_back(header); + // now add it. + _headers.push_back({field, value}); } -void PsychicResponse::setCookie(const char *name, const char *value, unsigned long secondsFromNow, const char *extras) +void PsychicResponse::setCookie(const char* name, const char* value, unsigned long secondsFromNow, const char* extras) { time_t now = time(nullptr); String output; output = urlEncode(name) + "=" + urlEncode(value); - //if current time isn't modern, default to using max age + // if current time isn't modern, default to using max age if (now < 1700000000) - output += "; Max-Age=" + String(secondsFromNow); - //otherwise, set an expiration date - else - { + output += "; Max-Age=" + String(secondsFromNow); + // otherwise, set an expiration date + else { time_t expirationTimestamp = now + secondsFromNow; // Convert the expiration timestamp to a formatted string for the "expires" attribute @@ -57,11 +54,11 @@ void PsychicResponse::setCookie(const char *name, const char *value, unsigned lo output += "; Expires=" + String(expires); } - //did we get any extras? + // did we get any extras? if (strlen(extras)) output += "; " + String(extras); - //okay, add it in. + // okay, add it in. addHeader("Set-Cookie", output.c_str()); } @@ -70,24 +67,24 @@ void PsychicResponse::setCode(int code) _code = code; } -void PsychicResponse::setContentType(const char *contentType) +void PsychicResponse::setContentType(const char* contentType) { - httpd_resp_set_type(_request->request(), contentType); + _contentType = contentType; } -void PsychicResponse::setContent(const char *content) +void PsychicResponse::setContent(const char* content) { _body = content; setContentLength(strlen(content)); } -void PsychicResponse::setContent(const uint8_t *content, size_t len) +void PsychicResponse::setContent(const uint8_t* content, size_t len) { - _body = (char *)content; + _body = (char*)content; setContentLength(len); } -const char * PsychicResponse::getContent() +const char* PsychicResponse::getContent() { return _body; } @@ -99,17 +96,20 @@ size_t PsychicResponse::getContentLength() esp_err_t PsychicResponse::send() { - //esp-idf makes you set the whole status. + // esp-idf makes you set the whole status. sprintf(_status, "%u %s", _code, http_status_reason(_code)); httpd_resp_set_status(_request->request(), _status); - //our headers too + // set the content type + httpd_resp_set_type(_request->request(), _contentType.c_str()); + + // our headers too this->sendHeaders(); - //now send it off + // now send it off esp_err_t err = httpd_resp_send(_request->request(), getContent(), getContentLength()); - //did something happen? + // did something happen? if (err != ESP_OK) ESP_LOGE(PH_TAG, "Send response failed (%s)", esp_err_to_name(err)); @@ -118,38 +118,21 @@ esp_err_t PsychicResponse::send() void PsychicResponse::sendHeaders() { - //get our global headers out of the way first - for (HTTPHeader header : DefaultHeaders::Instance().getHeaders()) - httpd_resp_set_hdr(_request->request(), header.field, header.value); - - //now do our individual headers - for (HTTPHeader header : _headers) - httpd_resp_set_hdr(this->_request->request(), header.field, header.value); - - // DO NOT RELEASE HEADERS HERE... released in the PsychicResponse destructor after they have been sent. - // httpd_resp_set_hdr just passes on the pointer, but its needed after this call. - // clean up our header variables after send - // for (HTTPHeader header : _headers) - // { - // free(header.field); - // free(header.value); - // } - // _headers.clear(); + // now do our individual headers + for (auto& header : _headers) + httpd_resp_set_hdr(this->_request->request(), header.field.c_str(), header.value.c_str()); } -esp_err_t PsychicResponse::sendChunk(uint8_t *chunk, size_t chunksize) +esp_err_t PsychicResponse::sendChunk(uint8_t* chunk, size_t chunksize) { /* Send the buffer contents as HTTP response chunk */ - esp_err_t err = httpd_resp_send_chunk(this->_request->request(), (char *)chunk, chunksize); - if (err != ESP_OK) - { + ESP_LOGD(PH_TAG, "Sending chunk: %d", chunksize); + esp_err_t err = httpd_resp_send_chunk(request(), (char*)chunk, chunksize); + if (err != ESP_OK) { ESP_LOGE(PH_TAG, "File sending failed (%s)", esp_err_to_name(err)); /* Abort sending file */ httpd_resp_sendstr_chunk(this->_request->request(), NULL); - - /* Respond with 500 Internal Server Error */ - httpd_resp_send_err(this->_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file"); } return err; @@ -159,4 +142,63 @@ esp_err_t PsychicResponse::finishChunking() { /* Respond with an empty chunk to signal HTTP response completion */ return httpd_resp_send_chunk(this->_request->request(), NULL, 0); -} \ No newline at end of file +} + +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; +} diff --git a/lib/PsychicHttp/src/PsychicResponse.h b/lib/PsychicHttp/src/PsychicResponse.h index a36de65..0b8728f 100644 --- a/lib/PsychicHttp/src/PsychicResponse.h +++ b/lib/PsychicHttp/src/PsychicResponse.h @@ -9,38 +9,101 @@ class PsychicRequest; class PsychicResponse { protected: - PsychicRequest *_request; + PsychicRequest* _request; int _code; char _status[60]; std::list _headers; + String _contentType; int64_t _contentLength; - const char * _body; + const char* _body; public: - PsychicResponse(PsychicRequest *request); + PsychicResponse(PsychicRequest* request); virtual ~PsychicResponse(); - void setCode(int code); + const char* version() { return "HTTP/1.1"; } + + void setCode(int code); + int getCode() { return _code; } + + void setContentType(const char* contentType); + String& getContentType() { return _contentType; } - void setContentType(const char *contentType); void setContentLength(int64_t contentLength) { _contentLength = contentLength; } int64_t getContentLength(int64_t contentLength) { return _contentLength; } - void addHeader(const char *field, const char *value); + void addHeader(const char* field, const char* value); + std::list& headers() { return _headers; } - void setCookie(const char *key, const char *value, unsigned long max_age = 60*60*24*30, const char *extras = ""); + void setCookie(const char* key, const char* value, unsigned long max_age = 60 * 60 * 24 * 30, const char* extras = ""); - void setContent(const char *content); - void setContent(const uint8_t *content, size_t len); + void setContent(const char* content); + void setContent(const uint8_t* content, size_t len); - const char * getContent(); + const char* getContent(); size_t getContentLength(); virtual esp_err_t send(); void sendHeaders(); - esp_err_t sendChunk(uint8_t *chunk, size_t chunksize); + esp_err_t sendChunk(uint8_t* chunk, size_t chunksize); esp_err_t finishChunking(); + + esp_err_t redirect(const char* url); + esp_err_t send(int code); + esp_err_t send(const char* content); + esp_err_t send(const char* contentType, const char* content); + esp_err_t send(int code, const char* contentType, const char* content); + esp_err_t send(int code, const char* contentType, const uint8_t* content, size_t len); + esp_err_t error(httpd_err_code_t code, const char* message); + + httpd_req_t* request(); +}; + +class PsychicResponseDelegate +{ + protected: + PsychicResponse* _response; + + public: + PsychicResponseDelegate(PsychicResponse* response) : _response(response) {} + virtual ~PsychicResponseDelegate() {} + + const char* version() { return _response->version(); } + + void setCode(int code) { _response->setCode(code); } + + void setContentType(const char* contentType) { _response->setContentType(contentType); } + String& getContentType() { return _response->getContentType(); } + + void setContentLength(int64_t contentLength) { _response->setContentLength(contentLength); } + int64_t getContentLength(int64_t contentLength) { return _response->getContentLength(); } + + void addHeader(const char* field, const char* value) { _response->addHeader(field, value); } + + void setCookie(const char* key, const char* value, unsigned long max_age = 60 * 60 * 24 * 30, const char* extras = "") { _response->setCookie(key, value, max_age, extras); } + + void setContent(const char* content) { _response->setContent(content); } + void setContent(const uint8_t* content, size_t len) { _response->setContent(content, len); } + + const char* getContent() { return _response->getContent(); } + size_t getContentLength() { return _response->getContentLength(); } + + esp_err_t send() { return _response->send(); } + void sendHeaders() { _response->sendHeaders(); } + + esp_err_t sendChunk(uint8_t* chunk, size_t chunksize) { return _response->sendChunk(chunk, chunksize); } + esp_err_t finishChunking() { return _response->finishChunking(); } + + esp_err_t redirect(const char* url) { return _response->redirect(url); } + esp_err_t send(int code) { return _response->send(code); } + esp_err_t send(const char* content) { return _response->send(content); } + esp_err_t send(const char* contentType, const char* content) { return _response->send(contentType, content); } + esp_err_t send(int code, const char* contentType, const char* content) { return _response->send(code, contentType, content); } + esp_err_t send(int code, const char* contentType, const uint8_t* content, size_t len) { return _response->send(code, contentType, content, len); } + esp_err_t error(httpd_err_code_t code, const char* message) { return _response->error(code, message); } + + httpd_req_t* request() { return _response->request(); } }; #endif // PsychicResponse_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicRewrite.cpp b/lib/PsychicHttp/src/PsychicRewrite.cpp new file mode 100644 index 0000000..71b5ec2 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicRewrite.cpp @@ -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(); + } \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicRewrite.h b/lib/PsychicHttp/src/PsychicRewrite.h new file mode 100644 index 0000000..c972986 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicRewrite.h @@ -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 \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicStaticFileHander.cpp b/lib/PsychicHttp/src/PsychicStaticFileHander.cpp index 54fafc4..62ed118 100644 --- a/lib/PsychicHttp/src/PsychicStaticFileHander.cpp +++ b/lib/PsychicHttp/src/PsychicStaticFileHander.cpp @@ -5,70 +5,88 @@ /*************************************/ PsychicStaticFileHandler::PsychicStaticFileHandler(const char* uri, FS& fs, const char* path, const char* cache_control) - : _fs(fs), _uri(uri), _path(path), _default_file("index.html"), _cache_control(cache_control), _last_modified("") + : _fs(fs), _uri(uri), _path(path), _default_file("index.html"), _cache_control(cache_control), _last_modified("") { // Ensure leading '/' - if (_uri.length() == 0 || _uri[0] != '/') _uri = "/" + _uri; - if (_path.length() == 0 || _path[0] != '/') _path = "/" + _path; + if (_uri.length() == 0 || _uri[0] != '/') + _uri = "/" + _uri; + if (_path.length() == 0 || _path[0] != '/') + _path = "/" + _path; // If path ends with '/' we assume a hint that this is a directory to improve performance. // However - if it does not end with '/' we, can't assume a file, path can still be a directory. - _isDir = _path[_path.length()-1] == '/'; + _isDir = _path[_path.length() - 1] == '/'; // Remove the trailing '/' so we can handle default file // Notice that root will be "" not "/" - if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1); - if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1); + if (_uri[_uri.length() - 1] == '/') + _uri = _uri.substring(0, _uri.length() - 1); + if (_path[_path.length() - 1] == '/') + _path = _path.substring(0, _path.length() - 1); // Reset stats _gzipFirst = false; _gzipStats = 0xF8; } -PsychicStaticFileHandler& PsychicStaticFileHandler::setIsDir(bool isDir){ - _isDir = isDir; - return *this; -} - -PsychicStaticFileHandler& PsychicStaticFileHandler::setDefaultFile(const char* filename){ - _default_file = String(filename); - return *this; -} - -PsychicStaticFileHandler& PsychicStaticFileHandler::setCacheControl(const char* cache_control){ - _cache_control = String(cache_control); - return *this; -} - -PsychicStaticFileHandler& PsychicStaticFileHandler::setLastModified(const char* last_modified){ - _last_modified = String(last_modified); - return *this; -} - -PsychicStaticFileHandler& PsychicStaticFileHandler::setLastModified(struct tm* last_modified){ - char result[30]; - strftime (result,30,"%a, %d %b %Y %H:%M:%S %Z", last_modified); - return setLastModified((const char *)result); -} - -bool PsychicStaticFileHandler::canHandle(PsychicRequest *request) +PsychicStaticFileHandler* PsychicStaticFileHandler::setIsDir(bool isDir) { - if(request->method() != HTTP_GET || !request->uri().startsWith(_uri) ) + _isDir = isDir; + return this; +} + +PsychicStaticFileHandler* PsychicStaticFileHandler::setDefaultFile(const char* filename) +{ + _default_file = filename; + return this; +} + +PsychicStaticFileHandler* PsychicStaticFileHandler::setCacheControl(const char* cache_control) +{ + _cache_control = cache_control; + return this; +} + +PsychicStaticFileHandler* PsychicStaticFileHandler::setLastModified(const char* last_modified) +{ + _last_modified = String(last_modified); + return this; +} + +PsychicStaticFileHandler* PsychicStaticFileHandler::setLastModified(struct tm* last_modified) +{ + char result[30]; + strftime(result, 30, "%a, %d %b %Y %H:%M:%S %Z", last_modified); + return setLastModified((const char*)result); +} + +bool PsychicStaticFileHandler::canHandle(PsychicRequest* request) +{ + if (request->method() != HTTP_GET) { + ESP_LOGD(PH_TAG, "Request %s refused by PsychicStaticFileHandler: %s", request->uri().c_str(), request->methodStr().c_str()); 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; + } + ESP_LOGD(PH_TAG, "Request %s refused by PsychicStaticFileHandler: file not found", request->uri().c_str()); return false; } -bool PsychicStaticFileHandler::_getFile(PsychicRequest *request) +bool PsychicStaticFileHandler::_getFile(PsychicRequest* request) { // Remove the found uri String path = request->uri().substring(_uri.length()); // We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/' - bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/'); + bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length() - 1] == '/'); path = _path + path; @@ -81,7 +99,7 @@ bool PsychicStaticFileHandler::_getFile(PsychicRequest *request) return false; // Try to add default file, ensure there is a trailing '/' ot the path. - if (path.length() == 0 || path[path.length()-1] != '/') + if (path.length() == 0 || path[path.length() - 1] != '/') path += "/"; path += _default_file; @@ -100,14 +118,14 @@ bool PsychicStaticFileHandler::_fileExists(const String& path) if (_gzipFirst) { _file = _fs.open(gzip, "r"); gzipFound = FILE_IS_REAL(_file); - if (!gzipFound){ + if (!gzipFound) { _file = _fs.open(path, "r"); fileFound = FILE_IS_REAL(_file); } } else { _file = _fs.open(path, "r"); fileFound = FILE_IS_REAL(_file); - if (!fileFound){ + if (!fileFound) { _file = _fs.open(gzip, "r"); gzipFound = FILE_IS_REAL(_file); } @@ -115,17 +133,21 @@ bool PsychicStaticFileHandler::_fileExists(const String& path) bool found = fileFound || gzipFound; - if (found) - { + if (found) { _filename = path; // Calculate gzip statistic _gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); - if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip - else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip - else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first + if (_gzipStats == 0x00) + _gzipFirst = false; // All files are not gzip + else if (_gzipStats == 0xFF) + _gzipFirst = true; // All files are gzip + else + _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first } + ESP_LOGD(PH_TAG, "PsychicStaticFileHandler _fileExists(%s): %d", path.c_str(), found); + return found; } @@ -133,36 +155,32 @@ uint8_t PsychicStaticFileHandler::_countBits(const uint8_t value) const { uint8_t w = value; uint8_t n; - for (n=0; w!=0; n++) w&=w-1; + for (n = 0; w != 0; n++) + w &= w - 1; return n; } -esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest *request) +esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest* request, PsychicResponse* res) { - if (_file == true) - { - //is it not modified? + if (_file == true) { + // is it not modified? 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(); - request->reply(304); // Not modified + res->send(304); // Not modified } - //does our Etag match? - else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) - { + // does our Etag match? + else if (_cache_control.length() && request->hasHeader("If-None-Match") && request->header("If-None-Match").equals(etag)) { _file.close(); - PsychicResponse response(request); - response.addHeader("Cache-Control", _cache_control.c_str()); - response.addHeader("ETag", etag.c_str()); - response.setCode(304); - response.send(); + res->addHeader("Cache-Control", _cache_control.c_str()); + res->addHeader("ETag", etag.c_str()); + res->setCode(304); + res->send(); } - //nope, send them the full file. - else - { - PsychicFileResponse response(request, _fs, _filename); + // nope, send them the full file. + else { + PsychicFileResponse response(res, _fs, _filename); if (_last_modified.length()) response.addHeader("Last-Modified", _last_modified.c_str()); @@ -174,7 +192,7 @@ esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest *request) return response.send(); } } else { - return request->reply(404); + return res->send(404); } return ESP_OK; diff --git a/lib/PsychicHttp/src/PsychicStaticFileHandler.h b/lib/PsychicHttp/src/PsychicStaticFileHandler.h index f757111..1cbe5f7 100644 --- a/lib/PsychicHttp/src/PsychicStaticFileHandler.h +++ b/lib/PsychicHttp/src/PsychicStaticFileHandler.h @@ -2,18 +2,21 @@ #define PsychicStaticFileHandler_h #include "PsychicCore.h" -#include "PsychicWebHandler.h" +#include "PsychicFileResponse.h" #include "PsychicRequest.h" #include "PsychicResponse.h" -#include "PsychicFileResponse.h" +#include "PsychicWebHandler.h" + +class PsychicStaticFileHandler : public PsychicWebHandler +{ + using File = fs::File; + using FS = fs::FS; -class PsychicStaticFileHandler : public PsychicWebHandler { - using File = fs::File; - using FS = fs::FS; private: - bool _getFile(PsychicRequest *request); + bool _getFile(PsychicRequest* request); bool _fileExists(const String& path); uint8_t _countBits(const uint8_t value) const; + protected: FS _fs; File _file; @@ -26,16 +29,17 @@ class PsychicStaticFileHandler : public PsychicWebHandler { bool _isDir; bool _gzipFirst; uint8_t _gzipStats; + public: PsychicStaticFileHandler(const char* uri, FS& fs, const char* path, const char* cache_control); - bool canHandle(PsychicRequest *request) override; - esp_err_t handleRequest(PsychicRequest *request) override; - PsychicStaticFileHandler& setIsDir(bool isDir); - PsychicStaticFileHandler& setDefaultFile(const char* filename); - PsychicStaticFileHandler& setCacheControl(const char* cache_control); - PsychicStaticFileHandler& setLastModified(const char* last_modified); - PsychicStaticFileHandler& setLastModified(struct tm* last_modified); - //PsychicStaticFileHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} + bool canHandle(PsychicRequest* request) override; + esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override; + PsychicStaticFileHandler* setIsDir(bool isDir); + PsychicStaticFileHandler* setDefaultFile(const char* filename); + PsychicStaticFileHandler* setCacheControl(const char* cache_control); + PsychicStaticFileHandler* setLastModified(const char* last_modified); + PsychicStaticFileHandler* setLastModified(struct tm* last_modified); + // PsychicStaticFileHandler* setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} }; #endif /* PsychicHttp_h */ \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicStreamResponse.cpp b/lib/PsychicHttp/src/PsychicStreamResponse.cpp index 5d85649..dfdca2c 100644 --- a/lib/PsychicHttp/src/PsychicStreamResponse.cpp +++ b/lib/PsychicHttp/src/PsychicStreamResponse.cpp @@ -1,63 +1,62 @@ #include "PsychicStreamResponse.h" -#include "PsychicResponse.h" #include "PsychicRequest.h" +#include "PsychicResponse.h" -PsychicStreamResponse::PsychicStreamResponse(PsychicRequest *request, const String& contentType) - : PsychicResponse(request), _buffer(NULL) { +PsychicStreamResponse::PsychicStreamResponse(PsychicResponse* response, const String& contentType) + : PsychicResponseDelegate(response), _buffer(NULL) +{ //setContentType(contentType.c_str()); //addHeader("Content-Disposition", "inline"); } - -PsychicStreamResponse::PsychicStreamResponse(PsychicRequest *request, const String& contentType, const String& name) - : PsychicResponse(request), _buffer(NULL) { +PsychicStreamResponse::PsychicStreamResponse(PsychicResponse* response, const String& contentType, const String& name) + : PsychicResponseDelegate(response), _buffer(NULL) +{ setContentType(contentType.c_str()); - char buf[26+name.length()]; - snprintf(buf, sizeof (buf), "attachment; filename=\"%s\"", name.c_str()); + char buf[26 + name.length()]; + snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", name.c_str()); addHeader("Content-Disposition", buf); } - PsychicStreamResponse::~PsychicStreamResponse() { endSend(); } - esp_err_t PsychicStreamResponse::beginSend() { - if(_buffer) + if (_buffer) return ESP_OK; - //Buffer to hold ChunkPrinter and stream buffer. Using placement new will keep us at a single allocation. + // Buffer to hold ChunkPrinter and stream buffer. Using placement new will keep us at a single allocation. _buffer = (uint8_t*)malloc(STREAM_CHUNK_SIZE + sizeof(ChunkPrinter)); - - if(!_buffer) + + if (!_buffer) { /* Respond with 500 Internal Server Error */ - httpd_resp_send_err(_request->request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); + ESP_LOGE(PH_TAG, "Unable to allocate %" PRIu32 " bytes to send chunk", STREAM_CHUNK_SIZE + sizeof(ChunkPrinter)); + httpd_resp_send_err(request(), HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); return ESP_FAIL; } - _printer = new (_buffer) ChunkPrinter(this, _buffer + sizeof(ChunkPrinter), STREAM_CHUNK_SIZE); + _printer = new (_buffer) ChunkPrinter(_response, _buffer + sizeof(ChunkPrinter), STREAM_CHUNK_SIZE); sendHeaders(); return ESP_OK; } - esp_err_t PsychicStreamResponse::endSend() { esp_err_t err = ESP_OK; - - if(!_buffer) + + if (!_buffer) err = ESP_FAIL; else { - _printer->~ChunkPrinter(); //flushed on destruct + _printer->~ChunkPrinter(); // flushed on destruct err = finishChunking(); free(_buffer); _buffer = NULL; @@ -65,29 +64,25 @@ esp_err_t PsychicStreamResponse::endSend() return err; } - void PsychicStreamResponse::flush() { - if(_buffer) + if (_buffer) _printer->flush(); } - size_t PsychicStreamResponse::write(uint8_t data) { return _buffer ? _printer->write(data) : 0; } - -size_t PsychicStreamResponse::write(const uint8_t *buffer, size_t size) +size_t PsychicStreamResponse::write(const uint8_t* buffer, size_t size) { return _buffer ? _printer->write(buffer, size) : 0; } - -size_t PsychicStreamResponse::copyFrom(Stream &stream) +size_t PsychicStreamResponse::copyFrom(Stream& stream) { - if(_buffer) + if (_buffer) return _printer->copyFrom(stream); return 0; diff --git a/lib/PsychicHttp/src/PsychicStreamResponse.h b/lib/PsychicHttp/src/PsychicStreamResponse.h index 888ec9c..5209f2d 100644 --- a/lib/PsychicHttp/src/PsychicStreamResponse.h +++ b/lib/PsychicHttp/src/PsychicStreamResponse.h @@ -1,34 +1,34 @@ #ifndef PsychicStreamResponse_h #define PsychicStreamResponse_h +#include "ChunkPrinter.h" #include "PsychicCore.h" #include "PsychicResponse.h" -#include "ChunkPrinter.h" class PsychicRequest; -class PsychicStreamResponse : public PsychicResponse, public Print +class PsychicStreamResponse : public PsychicResponseDelegate, public Print { private: - ChunkPrinter *_printer; - uint8_t *_buffer; + ChunkPrinter* _printer; + uint8_t* _buffer; + public: - - PsychicStreamResponse(PsychicRequest *request, const String& contentType); - PsychicStreamResponse(PsychicRequest *request, const String& contentType, const String& name); //Download - + PsychicStreamResponse(PsychicResponse* response, const String& contentType); + PsychicStreamResponse(PsychicResponse* response, const String& contentType, const String& name); // Download + ~PsychicStreamResponse(); - + esp_err_t beginSend(); esp_err_t endSend(); void flush() override; size_t write(uint8_t data) override; - size_t write(const uint8_t *buffer, size_t size) override; - - size_t copyFrom(Stream &stream); - + size_t write(const uint8_t* buffer, size_t size) override; + + size_t copyFrom(Stream& stream); + using Print::write; }; diff --git a/lib/PsychicHttp/src/PsychicUploadHandler.cpp b/lib/PsychicHttp/src/PsychicUploadHandler.cpp index d8a55e3..f0313a9 100644 --- a/lib/PsychicHttp/src/PsychicUploadHandler.cpp +++ b/lib/PsychicHttp/src/PsychicUploadHandler.cpp @@ -1,34 +1,19 @@ #include "PsychicUploadHandler.h" -PsychicUploadHandler::PsychicUploadHandler() : - PsychicWebHandler() - , _temp() - , _parsedLength(0) - , _multiParseState(EXPECT_BOUNDARY) - , _boundaryPosition(0) - , _itemStartIndex(0) - , _itemSize(0) - , _itemName() - , _itemFilename() - , _itemType() - , _itemValue() - , _itemBuffer(0) - , _itemBufferIndex(0) - , _itemIsFile(false) - {} +PsychicUploadHandler::PsychicUploadHandler() : PsychicWebHandler(), _uploadCallback(nullptr) +{ +} PsychicUploadHandler::~PsychicUploadHandler() {} -bool PsychicUploadHandler::canHandle(PsychicRequest *request) { +bool PsychicUploadHandler::canHandle(PsychicRequest* request) +{ return true; } -esp_err_t PsychicUploadHandler::handleRequest(PsychicRequest *request) +esp_err_t PsychicUploadHandler::handleRequest(PsychicRequest* request, PsychicResponse* response) { esp_err_t err = ESP_OK; - //save it for later (multipart) - _request = request; - _parsedLength = 0; /* File cannot be larger than a limit */ if (request->contentLength() > request->server()->maxUploadSize) { @@ -37,63 +22,59 @@ esp_err_t PsychicUploadHandler::handleRequest(PsychicRequest *request) /* Respond with 400 Bad Request */ char error[50]; sprintf(error, "File size must be less than %lu bytes!", request->server()->maxUploadSize); - httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error); - - /* Return failure to close underlying connection else the incoming file content will keep the socket busy */ - return ESP_FAIL; + return httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error); } - //we might want to access some of these params - request->loadParams(); + // TODO: support for the 100 header. not sure if we can do it. + // if (request->header("Expect").equals("100-continue")) + // { + // char response[] = "100 Continue"; + // httpd_socket_send(self->server, httpd_req_to_sockfd(req), response, strlen(response), 0); + // } - //TODO: support for the 100 header. not sure if we can do it. - // if (request->header("Expect").equals("100-continue")) - // { - // char response[] = "100 Continue"; - // httpd_socket_send(self->server, httpd_req_to_sockfd(req), response, strlen(response), 0); - // } - - //2 types of upload requests + // 2 types of upload requests if (request->isMultipart()) err = _multipartUploadHandler(request); else err = _basicUploadHandler(request); - //we can also call onRequest for some final processing and response + // we can also call onRequest for some final processing and response if (err == ESP_OK) { if (_requestCallback != NULL) - err = _requestCallback(request); + err = _requestCallback(request, response); else - err = request->reply("Upload Successful."); + err = response->send("Upload Successful."); } + else if (err == ESP_ERR_HTTPD_INVALID_REQ) + response->send(400, "text/html", "No multipart boundary found."); else - request->reply(500, "text/html", "Error processing upload."); + response->send(500, "text/html", "Error processing upload."); return err; } -esp_err_t PsychicUploadHandler::_basicUploadHandler(PsychicRequest *request) +esp_err_t PsychicUploadHandler::_basicUploadHandler(PsychicRequest* request) { esp_err_t err = ESP_OK; String filename = request->getFilename(); /* Retrieve the pointer to scratch buffer for temporary storage */ - char *buf = (char *)malloc(FILE_CHUNK_SIZE); + char* buf = (char*)malloc(FILE_CHUNK_SIZE); int received; - unsigned long index = 0; + 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 +#ifdef ENABLE_ASYNC + httpd_sess_update_lru_counter(request->server()->server, request->client()->socket()); +#endif - //ESP_LOGD(PH_TAG, "Remaining size : %d", remaining); + // ESP_LOGD(PH_TAG, "Remaining size : %d", remaining); /* Receive the file part by part into a buffer */ if ((received = httpd_req_recv(request->request(), buf, min(remaining, FILE_CHUNK_SIZE))) <= 0) @@ -101,7 +82,7 @@ esp_err_t PsychicUploadHandler::_basicUploadHandler(PsychicRequest *request) /* Retry if timeout occurred */ if (received == HTTPD_SOCK_ERR_TIMEOUT) continue; - //bail if we got an error + // bail if we got an error else if (received == HTTPD_SOCK_ERR_FAIL) { ESP_LOGE(PH_TAG, "Socket error"); @@ -110,10 +91,10 @@ esp_err_t PsychicUploadHandler::_basicUploadHandler(PsychicRequest *request) } } - //call our upload callback here. + // call our upload callback here. if (_uploadCallback != NULL) { - err = _uploadCallback(request, filename, index, (uint8_t *)buf, received, (remaining - received == 0)); + err = _uploadCallback(request, filename, index, (uint8_t*)buf, received, (remaining - received == 0)); if (err != ESP_OK) break; } @@ -129,267 +110,20 @@ esp_err_t PsychicUploadHandler::_basicUploadHandler(PsychicRequest *request) index += received; } - //dont forget to free our buffer + // dont forget to free our buffer free(buf); return err; } -esp_err_t PsychicUploadHandler::_multipartUploadHandler(PsychicRequest *request) +esp_err_t PsychicUploadHandler::_multipartUploadHandler(PsychicRequest* request) { - esp_err_t err = ESP_OK; - - String value = request->header("Content-Type"); - if (value.startsWith("multipart/")){ - _boundary = value.substring(value.indexOf('=')+1); - _boundary.replace("\"",""); - } else { - ESP_LOGE(PH_TAG, "No multipart boundary found."); - return request->reply(400, "text/html", "No multipart boundary found."); - } - - char *buf = (char *)malloc(FILE_CHUNK_SIZE); - int received; - unsigned long index = 0; - - /* Content length of the request gives the size of the file being uploaded */ - int remaining = request->contentLength(); - - while (remaining > 0) - { - #ifdef ENABLE_ASYNC - httpd_sess_update_lru_counter(request->server()->server, request->client()->socket()); - #endif - - //ESP_LOGD(PH_TAG, "Remaining size : %d", remaining); - - /* Receive the file part by part into a buffer */ - if ((received = httpd_req_recv(request->request(), buf, min(remaining, FILE_CHUNK_SIZE))) <= 0) - { - /* Retry if timeout occurred */ - if (received == HTTPD_SOCK_ERR_TIMEOUT) - continue; - //bail if we got an error - else if (received == HTTPD_SOCK_ERR_FAIL) - { - ESP_LOGE(PH_TAG, "Socket error"); - err = ESP_FAIL; - break; - } - } - - //parse it 1 byte at a time. - for (int i=0; i 12 && _temp.substring(0, 12).equalsIgnoreCase("Content-Type")){ - _itemType = _temp.substring(14); - _itemIsFile = true; - } else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase("Content-Disposition")){ - _temp = _temp.substring(_temp.indexOf(';') + 2); - while(_temp.indexOf(';') > 0){ - String name = _temp.substring(0, _temp.indexOf('=')); - String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1); - if(name == "name"){ - _itemName = nameVal; - } else if(name == "filename"){ - _itemFilename = nameVal; - _itemIsFile = true; - } - _temp = _temp.substring(_temp.indexOf(';') + 2); - } - String name = _temp.substring(0, _temp.indexOf('=')); - String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1); - if(name == "name"){ - _itemName = nameVal; - } else if(name == "filename"){ - _itemFilename = nameVal; - _itemIsFile = true; - } - } - _temp = String(); - } else { - _multiParseState = WAIT_FOR_RETURN1; - //value starts from here - _itemSize = 0; - _itemStartIndex = _parsedLength; - _itemValue = String(); - if(_itemIsFile){ - if(_itemBuffer) - free(_itemBuffer); - _itemBuffer = (uint8_t*)malloc(FILE_CHUNK_SIZE); - if(_itemBuffer == NULL){ - ESP_LOGE(PH_TAG, "Multipart: Failed to allocate buffer"); - _multiParseState = PARSE_ERROR; - return; - } - _itemBufferIndex = 0; - } - } - } - } else if(_multiParseState == EXPECT_FEED1){ - if(data != '\n'){ - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); _parseMultipartPostByte(data, last); - } else { - _multiParseState = EXPECT_DASH1; - } - } else if(_multiParseState == EXPECT_DASH1){ - if(data != '-'){ - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); itemWriteByte('\n'); _parseMultipartPostByte(data, last); - } else { - _multiParseState = EXPECT_DASH2; - } - } else if(_multiParseState == EXPECT_DASH2){ - if(data != '-'){ - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); _parseMultipartPostByte(data, last); - } else { - _multiParseState = BOUNDARY_OR_DATA; - _boundaryPosition = 0; - } - } else if(_multiParseState == BOUNDARY_OR_DATA){ - if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){ - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); - uint8_t i; - for(i=0; i<_boundaryPosition; i++) - itemWriteByte(_boundary.c_str()[i]); - _parseMultipartPostByte(data, last); - } else if(_boundaryPosition == _boundary.length() - 1){ - _multiParseState = DASH3_OR_RETURN2; - if(!_itemIsFile){ - _request->addParam(_itemName, _itemValue); - //_addParam(new AsyncWebParameter(_itemName, _itemValue, true)); - } else { - if(_itemSize){ - if(_uploadCallback) - _uploadCallback(_request, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true); - _itemBufferIndex = 0; - _request->addParam(new PsychicWebParameter(_itemName, _itemFilename, true, true, _itemSize)); - } - free(_itemBuffer); - _itemBuffer = NULL; - } - - } else { - _boundaryPosition++; - } - } else if(_multiParseState == DASH3_OR_RETURN2){ - if(data == '-' && (_request->contentLength() - _parsedLength - 4) != 0){ - ESP_LOGE(PH_TAG, "ERROR: The parser got to the end of the POST but is expecting more bytes!"); - _multiParseState = PARSE_ERROR; - return; - } - if(data == '\r'){ - _multiParseState = EXPECT_FEED2; - } else if(data == '-' && _request->contentLength() == (_parsedLength + 4)){ - _multiParseState = PARSING_FINISHED; - } else { - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); - uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); - _parseMultipartPostByte(data, last); - } - } else if(_multiParseState == EXPECT_FEED2){ - if(data == '\n'){ - _multiParseState = PARSE_HEADERS; - _itemIsFile = false; - } else { - _multiParseState = WAIT_FOR_RETURN1; - itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); - uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); - itemWriteByte('\r'); _parseMultipartPostByte(data, last); - } - } -} diff --git a/lib/PsychicHttp/src/PsychicUploadHandler.h b/lib/PsychicHttp/src/PsychicUploadHandler.h index 59e62b6..ef19a19 100644 --- a/lib/PsychicHttp/src/PsychicUploadHandler.h +++ b/lib/PsychicHttp/src/PsychicUploadHandler.h @@ -1,68 +1,32 @@ #ifndef PsychicUploadHandler_h #define PsychicUploadHandler_h +#include "MultipartProcessor.h" #include "PsychicCore.h" #include "PsychicHttpServer.h" #include "PsychicRequest.h" #include "PsychicWebHandler.h" -#include "PsychicWebParameter.h" - -//callback definitions -typedef std::function PsychicUploadCallback; /* -* HANDLER :: Can be attached to any endpoint or as a generic request handler. -*/ + * HANDLER :: Can be attached to any endpoint or as a generic request handler. + */ -class PsychicUploadHandler : public PsychicWebHandler { +class PsychicUploadHandler : public PsychicWebHandler +{ protected: + esp_err_t _basicUploadHandler(PsychicRequest* request); + esp_err_t _multipartUploadHandler(PsychicRequest* request); + PsychicUploadCallback _uploadCallback; - PsychicRequest *_request; - - String _temp; - size_t _parsedLength; - uint8_t _multiParseState; - String _boundary; - uint8_t _boundaryPosition; - size_t _itemStartIndex; - size_t _itemSize; - String _itemName; - String _itemFilename; - String _itemType; - String _itemValue; - uint8_t *_itemBuffer; - size_t _itemBufferIndex; - bool _itemIsFile; - - esp_err_t _basicUploadHandler(PsychicRequest *request); - esp_err_t _multipartUploadHandler(PsychicRequest *request); - - void _handleUploadByte(uint8_t data, bool last); - void _parseMultipartPostByte(uint8_t data, bool last); - public: PsychicUploadHandler(); ~PsychicUploadHandler(); - bool canHandle(PsychicRequest *request) override; - esp_err_t handleRequest(PsychicRequest *request) override; + bool canHandle(PsychicRequest* request) override; + esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override; - PsychicUploadHandler * onUpload(PsychicUploadCallback fn); -}; - -enum { - EXPECT_BOUNDARY, - PARSE_HEADERS, - WAIT_FOR_RETURN1, - EXPECT_FEED1, - EXPECT_DASH1, - EXPECT_DASH2, - BOUNDARY_OR_DATA, - DASH3_OR_RETURN2, - EXPECT_FEED2, - PARSING_FINISHED, - PARSE_ERROR + PsychicUploadHandler* onUpload(PsychicUploadCallback fn); }; #endif // PsychicUploadHandler_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicVersion.h b/lib/PsychicHttp/src/PsychicVersion.h new file mode 100644 index 0000000..cb95452 --- /dev/null +++ b/lib/PsychicHttp/src/PsychicVersion.h @@ -0,0 +1,47 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +/** Major version number (X.x.x) */ +#define PSYCHIC_VERSION_MAJOR 2 +/** Minor version number (x.X.x) */ +#define PSYCHIC_VERSION_MINOR 0 +/** Patch version number (x.x.X) */ +#define PSYCHIC_VERSION_PATCH 0 + +/** + * Macro to convert PsychicHttp version number into an integer + * + * To be used in comparisons, such as PSYCHIC_VERSION >= PSYCHIC_VERSION_VAL(2, 0, 0) + */ +#define PSYCHIC_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch)) + +/** + * Current PsychicHttp version, as an integer + * + * To be used in comparisons, such as PSYCHIC_VERSION >= PSYCHIC_VERSION_VAL(2, 0, 0) + */ +#define PSYCHIC_VERSION PSYCHIC_VERSION_VAL(PSYCHIC_VERSION_MAJOR, PSYCHIC_VERSION_MINOR, PSYCHIC_VERSION_PATCH) + +/** + * Current PsychicHttp version, as string + */ +#ifndef PSYCHIC_df2xstr +#define PSYCHIC_df2xstr(s) #s +#endif +#ifndef PSYCHIC_df2str +#define PSYCHIC_df2str(s) PSYCHIC_df2xstr(s) +#endif +#define PSYCHIC_VERSION_STR PSYCHIC_df2str(PSYCHIC_VERSION_MAJOR) "." PSYCHIC_df2str(PSYCHIC_VERSION_MINOR) "." PSYCHIC_df2str(PSYCHIC_VERSION_PATCH) diff --git a/lib/PsychicHttp/src/PsychicWebHandler.cpp b/lib/PsychicHttp/src/PsychicWebHandler.cpp index 1e4997e..9bdcaca 100644 --- a/lib/PsychicHttp/src/PsychicWebHandler.cpp +++ b/lib/PsychicHttp/src/PsychicWebHandler.cpp @@ -1,21 +1,22 @@ #include "PsychicWebHandler.h" -PsychicWebHandler::PsychicWebHandler() : - PsychicHandler(), - _requestCallback(NULL), - _onOpen(NULL), - _onClose(NULL) - {} +PsychicWebHandler::PsychicWebHandler() : PsychicHandler(), + _requestCallback(NULL), + _onOpen(NULL), + _onClose(NULL) +{ +} PsychicWebHandler::~PsychicWebHandler() {} -bool PsychicWebHandler::canHandle(PsychicRequest *request) { +bool PsychicWebHandler::canHandle(PsychicRequest* request) +{ return true; } -esp_err_t PsychicWebHandler::handleRequest(PsychicRequest *request) +esp_err_t PsychicWebHandler::handleRequest(PsychicRequest* request, PsychicResponse* response) { - //lookup our client - PsychicClient *client = checkForNewClient(request->client()); + // lookup our client + PsychicClient* client = checkForNewClient(request->client()); if (client->isNew) openCallback(client); @@ -27,48 +28,53 @@ esp_err_t PsychicWebHandler::handleRequest(PsychicRequest *request) /* Respond with 400 Bad Request */ char error[60]; sprintf(error, "Request body must be less than %lu bytes!", request->server()->maxRequestBodySize); - httpd_resp_send_err(request->request(), HTTPD_400_BAD_REQUEST, error); + response->send(400, "text/html", error); /* Return failure to close underlying connection else the incoming file content will keep the socket busy */ return ESP_FAIL; } - //get our body loaded up. + // get our body loaded up. esp_err_t err = request->loadBody(); if (err != ESP_OK) - return err; + return response->send(400, "text/html", "Error loading request body."); - //load our params in. + // load our params in. request->loadParams(); - //okay, pass on to our callback. + // okay, pass on to our callback. if (this->_requestCallback != NULL) - err = this->_requestCallback(request); + err = this->_requestCallback(request, response); return err; } -PsychicWebHandler * PsychicWebHandler::onRequest(PsychicHttpRequestCallback fn) { +PsychicWebHandler* PsychicWebHandler::onRequest(PsychicHttpRequestCallback fn) +{ _requestCallback = fn; return this; } -void PsychicWebHandler::openCallback(PsychicClient *client) { +void PsychicWebHandler::openCallback(PsychicClient* client) +{ if (_onOpen != NULL) _onOpen(client); } -void PsychicWebHandler::closeCallback(PsychicClient *client) { +void PsychicWebHandler::closeCallback(PsychicClient* client) +{ if (_onClose != NULL) _onClose(getClient(client)); } -PsychicWebHandler * PsychicWebHandler::onOpen(PsychicClientCallback fn) { +PsychicWebHandler* PsychicWebHandler::onOpen(PsychicClientCallback fn) +{ _onOpen = fn; return this; } -PsychicWebHandler * PsychicWebHandler::onClose(PsychicClientCallback fn) { +PsychicWebHandler* PsychicWebHandler::onClose(PsychicClientCallback fn) +{ _onClose = fn; return this; } \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebHandler.h b/lib/PsychicHttp/src/PsychicWebHandler.h index 07a6780..db640f7 100644 --- a/lib/PsychicHttp/src/PsychicWebHandler.h +++ b/lib/PsychicHttp/src/PsychicWebHandler.h @@ -7,10 +7,11 @@ #include "PsychicHandler.h" /* -* 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 PsychicWebHandler : public PsychicHandler { +class PsychicWebHandler : public PsychicHandler +{ protected: PsychicHttpRequestCallback _requestCallback; PsychicClientCallback _onOpen; @@ -20,15 +21,15 @@ class PsychicWebHandler : public PsychicHandler { PsychicWebHandler(); ~PsychicWebHandler(); - virtual bool canHandle(PsychicRequest *request) override; - virtual esp_err_t handleRequest(PsychicRequest *request) override; - PsychicWebHandler * onRequest(PsychicHttpRequestCallback fn); + virtual bool canHandle(PsychicRequest* request) override; + virtual esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override; + PsychicWebHandler* onRequest(PsychicHttpRequestCallback fn); - virtual void openCallback(PsychicClient *client); - virtual void closeCallback(PsychicClient *client); + virtual void openCallback(PsychicClient* client); + virtual void closeCallback(PsychicClient* client); - PsychicWebHandler *onOpen(PsychicClientCallback fn); - PsychicWebHandler *onClose(PsychicClientCallback fn); + PsychicWebHandler* onOpen(PsychicClientCallback fn); + PsychicWebHandler* onClose(PsychicClientCallback fn); }; #endif \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebParameter.h b/lib/PsychicHttp/src/PsychicWebParameter.h index e09136b..4d3a33c 100644 --- a/lib/PsychicHttp/src/PsychicWebParameter.h +++ b/lib/PsychicHttp/src/PsychicWebParameter.h @@ -5,7 +5,8 @@ * PARAMETER :: Chainable object to hold GET/POST and FILE parameters * */ -class PsychicWebParameter { +class PsychicWebParameter +{ private: String _name; String _value; @@ -14,7 +15,7 @@ class PsychicWebParameter { bool _isFile; public: - PsychicWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){} + PsychicWebParameter(const String& name, const String& value, bool form = false, bool file = false, size_t size = 0) : _name(name), _value(value), _size(size), _isForm(form), _isFile(file) {} const String& name() const { return _name; } const String& value() const { return _value; } size_t size() const { return _size; } @@ -22,4 +23,4 @@ class PsychicWebParameter { bool isFile() const { return _isFile; } }; -#endif //PsychicWebParameter_h \ No newline at end of file +#endif // PsychicWebParameter_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/PsychicWebSocket.cpp b/lib/PsychicHttp/src/PsychicWebSocket.cpp index 7a3c6b1..a553f9c 100644 --- a/lib/PsychicHttp/src/PsychicWebSocket.cpp +++ b/lib/PsychicHttp/src/PsychicWebSocket.cpp @@ -4,9 +4,8 @@ /* PsychicWebSocketRequest */ /*************************************/ -PsychicWebSocketRequest::PsychicWebSocketRequest(PsychicRequest *req) : - PsychicRequest(req->server(), req->request()), - _client(req->client()) +PsychicWebSocketRequest::PsychicWebSocketRequest(PsychicRequest* req) : PsychicRequest(req->server(), req->request()), + _client(req->client()) { } @@ -14,16 +13,17 @@ PsychicWebSocketRequest::~PsychicWebSocketRequest() { } -PsychicWebSocketClient * PsychicWebSocketRequest::client() { +PsychicWebSocketClient* PsychicWebSocketRequest::client() +{ return &_client; } -esp_err_t PsychicWebSocketRequest::reply(httpd_ws_frame_t * ws_pkt) +esp_err_t PsychicWebSocketRequest::reply(httpd_ws_frame_t* ws_pkt) { return httpd_ws_send_frame(this->_req, ws_pkt); -} +} -esp_err_t PsychicWebSocketRequest::reply(httpd_ws_type_t op, const void *data, size_t len) +esp_err_t PsychicWebSocketRequest::reply(httpd_ws_type_t op, const void* data, size_t len) { httpd_ws_frame_t ws_pkt; memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); @@ -35,7 +35,7 @@ esp_err_t PsychicWebSocketRequest::reply(httpd_ws_type_t op, const void *data, s return this->reply(&ws_pkt); } -esp_err_t PsychicWebSocketRequest::reply(const char *buf) +esp_err_t PsychicWebSocketRequest::reply(const char* buf) { return this->reply(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); } @@ -44,80 +44,123 @@ esp_err_t PsychicWebSocketRequest::reply(const char *buf) /* PsychicWebSocketClient */ /*************************************/ -PsychicWebSocketClient::PsychicWebSocketClient(PsychicClient *client) - : PsychicClient(client->server(), client->socket()) +PsychicWebSocketClient::PsychicWebSocketClient(PsychicClient* client) + : PsychicClient(client->server(), client->socket()) { } -PsychicWebSocketClient::~PsychicWebSocketClient() { +PsychicWebSocketClient::~PsychicWebSocketClient() +{ } -esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_frame_t * ws_pkt) +void PsychicWebSocketClient::_sendMessageCallback(esp_err_t err, int socket, void* arg) { - return httpd_ws_send_frame_async(this->server(), this->socket(), ws_pkt); -} + // free our frame. + httpd_ws_frame_t* ws_pkt = (httpd_ws_frame_t*)arg; + free(ws_pkt->payload); + free(ws_pkt); -esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_type_t op, const void *data, size_t len) -{ - httpd_ws_frame_t ws_pkt; - memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); - - ws_pkt.payload = (uint8_t*)data; - ws_pkt.len = len; - ws_pkt.type = op; - - return this->sendMessage(&ws_pkt); + if (err == ESP_OK) + return; + else if (err == ESP_FAIL) + ESP_LOGE(PH_TAG, "Websocket: send - socket error (#%d)", socket); + else if (err == ESP_ERR_INVALID_STATE) + ESP_LOGE(PH_TAG, "Websocket: Handshake was already done beforehand (#%d)", socket); + else if (err == ESP_ERR_INVALID_ARG) + ESP_LOGE(PH_TAG, "Websocket: Argument is invalid (null or non-WebSocket) (#%d)", socket); + else + ESP_LOGE(PH_TAG, "Websocket: Send message unknown error. (#%d)", socket); } -esp_err_t PsychicWebSocketClient::sendMessage(const char *buf) +esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_frame_t* ws_pkt) +{ + return sendMessage(ws_pkt->type, ws_pkt->payload, ws_pkt->len); +} + +esp_err_t PsychicWebSocketClient::sendMessage(httpd_ws_type_t op, const void* data, size_t len) +{ + // init our frame. + httpd_ws_frame_t* ws_pkt = (httpd_ws_frame_t*)malloc(sizeof(httpd_ws_frame_t)); + if (ws_pkt == NULL) { + ESP_LOGE(PH_TAG, "Websocket: out of memory"); + return ESP_ERR_NO_MEM; + } + memset(ws_pkt, 0, sizeof(httpd_ws_frame_t)); // zero the datastructure out + + // allocate for event text + ws_pkt->payload = (uint8_t*)malloc(len); + if (ws_pkt->payload == NULL) { + ESP_LOGE(PH_TAG, "Websocket: out of memory"); + free(ws_pkt); // free our other memory + return ESP_ERR_NO_MEM; + } + memcpy(ws_pkt->payload, data, len); + + ws_pkt->len = len; + ws_pkt->type = op; + + esp_err_t err = httpd_ws_send_data_async(server(), socket(), ws_pkt, PsychicWebSocketClient::_sendMessageCallback, ws_pkt); + + // take care of memory + if (err != ESP_OK) { + free(ws_pkt->payload); + free(ws_pkt); + } + + return err; +} + +esp_err_t PsychicWebSocketClient::sendMessage(const char* buf) { return this->sendMessage(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); } -PsychicWebSocketHandler::PsychicWebSocketHandler() : - PsychicHandler(), - _onOpen(NULL), - _onFrame(NULL), - _onClose(NULL) - { - } - -PsychicWebSocketHandler::~PsychicWebSocketHandler() { +PsychicWebSocketHandler::PsychicWebSocketHandler() : PsychicHandler(), + _onOpen(NULL), + _onFrame(NULL), + _onClose(NULL) +{ } -PsychicWebSocketClient * PsychicWebSocketHandler::getClient(int socket) +PsychicWebSocketHandler::~PsychicWebSocketHandler() { - PsychicClient *client = PsychicHandler::getClient(socket); +} + +PsychicWebSocketClient* PsychicWebSocketHandler::getClient(int socket) +{ + PsychicClient* client = PsychicHandler::getClient(socket); if (client == NULL) return NULL; - if (client->_friend == NULL) - { + if (client->_friend == NULL) { return NULL; } - return (PsychicWebSocketClient *)client->_friend; + return (PsychicWebSocketClient*)client->_friend; } -PsychicWebSocketClient * PsychicWebSocketHandler::getClient(PsychicClient *client) { +PsychicWebSocketClient* PsychicWebSocketHandler::getClient(PsychicClient* client) +{ return getClient(client->socket()); } -void PsychicWebSocketHandler::addClient(PsychicClient *client) { +void PsychicWebSocketHandler::addClient(PsychicClient* client) +{ client->_friend = new PsychicWebSocketClient(client); PsychicHandler::addClient(client); } -void PsychicWebSocketHandler::removeClient(PsychicClient *client) { +void PsychicWebSocketHandler::removeClient(PsychicClient* client) +{ PsychicHandler::removeClient(client); delete (PsychicWebSocketClient*)client->_friend; client->_friend = NULL; } -void PsychicWebSocketHandler::openCallback(PsychicClient *client) { - PsychicWebSocketClient *buddy = getClient(client); - if (buddy == NULL) - { +void PsychicWebSocketHandler::openCallback(PsychicClient* client) +{ + PsychicWebSocketClient* buddy = getClient(client); + if (buddy == NULL) { return; } @@ -125,10 +168,10 @@ void PsychicWebSocketHandler::openCallback(PsychicClient *client) { _onOpen(getClient(buddy)); } -void PsychicWebSocketHandler::closeCallback(PsychicClient *client) { - PsychicWebSocketClient *buddy = getClient(client); - if (buddy == NULL) - { +void PsychicWebSocketHandler::closeCallback(PsychicClient* client) +{ + PsychicWebSocketClient* buddy = getClient(client); + if (buddy == NULL) { return; } @@ -138,28 +181,27 @@ void PsychicWebSocketHandler::closeCallback(PsychicClient *client) { bool PsychicWebSocketHandler::isWebSocket() { return true; } -esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest *request) +esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest* request, PsychicResponse* response) { - //lookup our client - PsychicClient *client = checkForNewClient(request->client()); + // lookup our client + PsychicClient* client = checkForNewClient(request->client()); // beginning of the ws URI handler and our onConnect hook - if (request->method() == HTTP_GET) - { + if (request->method() == HTTP_GET) { if (client->isNew) openCallback(client); return ESP_OK; } - //prep our request + // prep our request PsychicWebSocketRequest wsRequest(request); - //init our memory for storing the packet + // init our memory for storing the packet httpd_ws_frame_t ws_pkt; memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); ws_pkt.type = HTTPD_WS_TYPE_TEXT; - uint8_t *buf = NULL; + uint8_t* buf = NULL; /* Set max_len = 0 to get the frame len */ esp_err_t ret = httpd_ws_recv_frame(wsRequest.request(), &ws_pkt, 0); @@ -168,11 +210,11 @@ esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest *request) return ret; } - //okay, now try to load the packet - //ESP_LOGD(PH_TAG, "frame len is %d", ws_pkt.len); + // okay, now try to load the packet + // ESP_LOGD(PH_TAG, "frame len is %d", ws_pkt.len); if (ws_pkt.len) { /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */ - buf = (uint8_t*) calloc(1, ws_pkt.len + 1); + buf = (uint8_t*)calloc(1, ws_pkt.len + 1); if (buf == NULL) { ESP_LOGE(PH_TAG, "Failed to calloc memory for buf"); return ESP_ERR_NO_MEM; @@ -185,53 +227,53 @@ esp_err_t PsychicWebSocketHandler::handleRequest(PsychicRequest *request) free(buf); return ret; } - //ESP_LOGD(PH_TAG, "Got packet with message: %s", ws_pkt.payload); + // ESP_LOGD(PH_TAG, "Got packet with message: %s", ws_pkt.payload); } // Text messages are our payload. - if (ws_pkt.type == HTTPD_WS_TYPE_TEXT || ws_pkt.type == HTTPD_WS_TYPE_BINARY) - { + if (ws_pkt.type == HTTPD_WS_TYPE_TEXT || ws_pkt.type == HTTPD_WS_TYPE_BINARY) { if (this->_onFrame != NULL) ret = this->_onFrame(&wsRequest, &ws_pkt); } - //logging housekeeping + // logging housekeeping if (ret != ESP_OK) ESP_LOGE(PH_TAG, "httpd_ws_send_frame failed with %s", esp_err_to_name(ret)); - // ESP_LOGD(PH_TAG, "ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d", - // request->server(), - // httpd_req_to_sockfd(request->request()), - // httpd_ws_get_fd_info(request->server()->server, httpd_req_to_sockfd(request->request()))); + // ESP_LOGD(PH_TAG, "ws_handler: httpd_handle_t=%p, sockfd=%d, client_info:%d", + // request->server(), + // httpd_req_to_sockfd(request->request()), + // httpd_ws_get_fd_info(request->server()->server, httpd_req_to_sockfd(request->request()))); - //dont forget to release our buffer memory + // dont forget to release our buffer memory free(buf); return ret; } -PsychicWebSocketHandler * PsychicWebSocketHandler::onOpen(PsychicWebSocketClientCallback fn) { +PsychicWebSocketHandler* PsychicWebSocketHandler::onOpen(PsychicWebSocketClientCallback fn) +{ _onOpen = fn; return this; } -PsychicWebSocketHandler * PsychicWebSocketHandler::onFrame(PsychicWebSocketFrameCallback fn) { +PsychicWebSocketHandler* PsychicWebSocketHandler::onFrame(PsychicWebSocketFrameCallback fn) +{ _onFrame = fn; return this; } -PsychicWebSocketHandler * PsychicWebSocketHandler::onClose(PsychicWebSocketClientCallback fn) { +PsychicWebSocketHandler* PsychicWebSocketHandler::onClose(PsychicWebSocketClientCallback fn) +{ _onClose = fn; return this; } -void PsychicWebSocketHandler::sendAll(httpd_ws_frame_t * ws_pkt) +void PsychicWebSocketHandler::sendAll(httpd_ws_frame_t* ws_pkt) { - for (PsychicClient *client : _clients) - { - //ESP_LOGD(PH_TAG, "Active client (fd=%d) -> sending async message", client->socket()); + for (PsychicClient* client : _clients) { + // ESP_LOGD(PH_TAG, "Active client (fd=%d) -> sending async message", client->socket()); - if (client->_friend == NULL) - { + if (client->_friend == NULL) { return; } @@ -240,7 +282,7 @@ void PsychicWebSocketHandler::sendAll(httpd_ws_frame_t * ws_pkt) } } -void PsychicWebSocketHandler::sendAll(httpd_ws_type_t op, const void *data, size_t len) +void PsychicWebSocketHandler::sendAll(httpd_ws_type_t op, const void* data, size_t len) { httpd_ws_frame_t ws_pkt; memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); @@ -252,7 +294,7 @@ void PsychicWebSocketHandler::sendAll(httpd_ws_type_t op, const void *data, size this->sendAll(&ws_pkt); } -void PsychicWebSocketHandler::sendAll(const char *buf) +void PsychicWebSocketHandler::sendAll(const char* buf) { this->sendAll(HTTPD_WS_TYPE_TEXT, buf, strlen(buf)); } diff --git a/lib/PsychicHttp/src/PsychicWebSocket.h b/lib/PsychicHttp/src/PsychicWebSocket.h index 01917de..a3541f4 100644 --- a/lib/PsychicHttp/src/PsychicWebSocket.h +++ b/lib/PsychicHttp/src/PsychicWebSocket.h @@ -7,19 +7,22 @@ class PsychicWebSocketRequest; class PsychicWebSocketClient; -//callback function definitions -typedef std::function PsychicWebSocketClientCallback; -typedef std::function PsychicWebSocketFrameCallback; +// callback function definitions +typedef std::function PsychicWebSocketClientCallback; +typedef std::function PsychicWebSocketFrameCallback; class PsychicWebSocketClient : public PsychicClient { + protected: + static void _sendMessageCallback(esp_err_t err, int socket, void* arg); + public: - PsychicWebSocketClient(PsychicClient *client); + PsychicWebSocketClient(PsychicClient* client); ~PsychicWebSocketClient(); - - esp_err_t sendMessage(httpd_ws_frame_t * ws_pkt); - esp_err_t sendMessage(httpd_ws_type_t op, const void *data, size_t len); - esp_err_t sendMessage(const char *buf); + + esp_err_t sendMessage(httpd_ws_frame_t* ws_pkt); + esp_err_t sendMessage(httpd_ws_type_t op, const void* data, size_t len); + esp_err_t sendMessage(const char* buf); }; class PsychicWebSocketRequest : public PsychicRequest @@ -28,17 +31,18 @@ class PsychicWebSocketRequest : public PsychicRequest PsychicWebSocketClient _client; public: - PsychicWebSocketRequest(PsychicRequest *req); + PsychicWebSocketRequest(PsychicRequest* req); virtual ~PsychicWebSocketRequest(); - PsychicWebSocketClient * client() override; + PsychicWebSocketClient* client() override; - esp_err_t reply(httpd_ws_frame_t * ws_pkt); - esp_err_t reply(httpd_ws_type_t op, const void *data, size_t len); - esp_err_t reply(const char *buf); + esp_err_t reply(httpd_ws_frame_t* ws_pkt); + esp_err_t reply(httpd_ws_type_t op, const void* data, size_t len); + esp_err_t reply(const char* buf); }; -class PsychicWebSocketHandler : public PsychicHandler { +class PsychicWebSocketHandler : public PsychicHandler +{ protected: PsychicWebSocketClientCallback _onOpen; PsychicWebSocketFrameCallback _onFrame; @@ -48,23 +52,23 @@ class PsychicWebSocketHandler : public PsychicHandler { PsychicWebSocketHandler(); ~PsychicWebSocketHandler(); - PsychicWebSocketClient * getClient(int socket) override; - PsychicWebSocketClient * getClient(PsychicClient *client) override; - void addClient(PsychicClient *client) override; - void removeClient(PsychicClient *client) override; - void openCallback(PsychicClient *client) override; - void closeCallback(PsychicClient *client) override; + PsychicWebSocketClient* getClient(int socket) override; + PsychicWebSocketClient* getClient(PsychicClient* client) override; + void addClient(PsychicClient* client) override; + void removeClient(PsychicClient* client) override; + void openCallback(PsychicClient* client) override; + void closeCallback(PsychicClient* client) override; bool isWebSocket() override final; - esp_err_t handleRequest(PsychicRequest *request) override; + esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override; - PsychicWebSocketHandler *onOpen(PsychicWebSocketClientCallback fn); - PsychicWebSocketHandler *onFrame(PsychicWebSocketFrameCallback fn); - PsychicWebSocketHandler *onClose(PsychicWebSocketClientCallback fn); + PsychicWebSocketHandler* onOpen(PsychicWebSocketClientCallback fn); + PsychicWebSocketHandler* onFrame(PsychicWebSocketFrameCallback fn); + PsychicWebSocketHandler* onClose(PsychicWebSocketClientCallback fn); - void sendAll(httpd_ws_frame_t * ws_pkt); - void sendAll(httpd_ws_type_t op, const void *data, size_t len); - void sendAll(const char *buf); + void sendAll(httpd_ws_frame_t* ws_pkt); + void sendAll(httpd_ws_type_t op, const void* data, size_t len); + void sendAll(const char* buf); }; #endif // PsychicWebSocket_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/TemplatePrinter.cpp b/lib/PsychicHttp/src/TemplatePrinter.cpp index 5c05904..a2b9eed 100644 --- a/lib/PsychicHttp/src/TemplatePrinter.cpp +++ b/lib/PsychicHttp/src/TemplatePrinter.cpp @@ -1,90 +1,112 @@ - /************************************************************ - - TemplatePrinter Class - - A basic templating engine for a stream of text. - This wraps the Arduino Print interface and writes to any - Print interface. - - Written by Christopher Andrews (https://github.com/Chris--A) - - ************************************************************/ - +/************************************************************ + +TemplatePrinter Class + +A basic templating engine for a stream of text. +This wraps the Arduino Print interface and writes to any +Print interface. + +Written by Christopher Andrews (https://github.com/Chris--A) + +************************************************************/ + #include "TemplatePrinter.h" -void TemplatePrinter::resetParam(bool flush){ - if(flush && _inParam){ +void TemplatePrinter::resetParam(bool flush) +{ + if (flush && _inParam) + { _stream.write(_delimiter); - - if(_paramPos) + + if (_paramPos) _stream.print(_paramBuffer); } - + memset(_paramBuffer, 0, sizeof(_paramBuffer)); _paramPos = 0; _inParam = false; } - -void TemplatePrinter::flush(){ +void TemplatePrinter::flush() +{ resetParam(true); _stream.flush(); } -size_t TemplatePrinter::write(uint8_t data){ - - if(data == _delimiter){ - +size_t TemplatePrinter::write(uint8_t data) +{ + + if (data == _delimiter) + { + // End of parameter, send to callback - if(_inParam){ - + if (_inParam) + { + // On false, return the parameter place holder as is: not a parameter - // Bug fix: ignore parameters that are zero length. - if(!_paramPos || !_cb(_stream, _paramBuffer)){ + // Bug fix: ignore parameters that are zero length. + if (!_paramPos || !_cb(_stream, _paramBuffer)) + { resetParam(true); _stream.write(data); - }else{ + } + else + { resetParam(false); } - - // Start collecting parameter - }else{ + + // Start collecting parameter + } + else + { _inParam = true; } - }else{ - + } + else + { + // Are we collecting - if(_inParam){ - + if (_inParam) + { + // Is param still valid - if(isalnum(data) || data == '_'){ - + if (isalnum(data) || data == '_') + { + // Total param len must be 63, 1 for null. - if(_paramPos < sizeof(_paramBuffer) - 1){ + if (_paramPos < sizeof(_paramBuffer) - 1) + { _paramBuffer[_paramPos++] = data; - - // Not a valid param - }else{ + + // Not a valid param + } + else + { resetParam(true); } - }else{ + } + else + { resetParam(true); _stream.write(data); } - // Just output - }else{ + // Just output + } + else + { _stream.write(data); } } return 1; } - -size_t TemplatePrinter::copyFrom(Stream &stream){ + +size_t TemplatePrinter::copyFrom(Stream& stream) +{ size_t count = 0; - - while(stream.available()) + + while (stream.available()) count += this->write(stream.read()); - + return count; } diff --git a/lib/PsychicHttp/src/TemplatePrinter.h b/lib/PsychicHttp/src/TemplatePrinter.h index 6adf14a..f171d1d 100644 --- a/lib/PsychicHttp/src/TemplatePrinter.h +++ b/lib/PsychicHttp/src/TemplatePrinter.h @@ -1,51 +1,53 @@ #ifndef TemplatePrinter_h - #define TemplatePrinter_h +#define TemplatePrinter_h - #include "PsychicCore.h" - #include - - /************************************************************ - - TemplatePrinter Class - - A basic templating engine for a stream of text. - This wraps the Arduino Print interface and writes to any - Print interface. - - Written by Christopher Andrews (https://github.com/Chris--A) - - ************************************************************/ - - class TemplatePrinter; +#include "PsychicCore.h" +#include - typedef std::function TemplateCallback; - typedef std::function TemplateSourceCallback; +/************************************************************ - class TemplatePrinter : public Print{ - private: - bool _inParam; - char _paramBuffer[64]; - uint8_t _paramPos; - Print &_stream; - TemplateCallback _cb; - char _delimiter; - - void resetParam(bool flush); - - public: - using Print::write; +TemplatePrinter Class - static void start(Print &stream, TemplateCallback cb, TemplateSourceCallback entry){ - TemplatePrinter printer(stream, cb); - entry(printer); - } +A basic templating engine for a stream of text. +This wraps the Arduino Print interface and writes to any +Print interface. - TemplatePrinter(Print &stream, TemplateCallback cb, const char delimeter = '%') : _stream(stream), _cb(cb), _delimiter(delimeter) { resetParam(false); } - ~TemplatePrinter(){ flush(); } +Written by Christopher Andrews (https://github.com/Chris--A) - void flush() override; - size_t write(uint8_t data) override; - size_t copyFrom(Stream &stream); - }; +************************************************************/ + +class TemplatePrinter; + +typedef std::function TemplateCallback; +typedef std::function TemplateSourceCallback; + +class TemplatePrinter : public Print +{ + private: + bool _inParam; + char _paramBuffer[64]; + uint8_t _paramPos; + Print& _stream; + TemplateCallback _cb; + char _delimiter; + + void resetParam(bool flush); + + public: + using Print::write; + + static void start(Print& stream, TemplateCallback cb, TemplateSourceCallback entry) + { + TemplatePrinter printer(stream, cb); + entry(printer); + } + + TemplatePrinter(Print& stream, TemplateCallback cb, const char delimeter = '%') : _stream(stream), _cb(cb), _delimiter(delimeter) { resetParam(false); } + ~TemplatePrinter() { flush(); } + + void flush() override; + size_t write(uint8_t data) override; + size_t copyFrom(Stream& stream); +}; #endif diff --git a/lib/PsychicHttp/src/async_worker.cpp b/lib/PsychicHttp/src/async_worker.cpp index d7310cb..96f0055 100644 --- a/lib/PsychicHttp/src/async_worker.cpp +++ b/lib/PsychicHttp/src/async_worker.cpp @@ -2,202 +2,222 @@ bool is_on_async_worker_thread(void) { - // is our handle one of the known async handles? - TaskHandle_t handle = xTaskGetCurrentTaskHandle(); - for (int i = 0; i < ASYNC_WORKER_COUNT; i++) { - if (worker_handles[i] == handle) { - return true; - } + // is our handle one of the known async handles? + TaskHandle_t handle = xTaskGetCurrentTaskHandle(); + for (int i = 0; i < ASYNC_WORKER_COUNT; i++) + { + if (worker_handles[i] == handle) + { + return true; } - return false; + } + return false; } // Submit an HTTP req to the async worker queue -esp_err_t submit_async_req(httpd_req_t *req, httpd_req_handler_t handler) +esp_err_t submit_async_req(httpd_req_t* req, httpd_req_handler_t handler) { - // must create a copy of the request that we own - httpd_req_t* copy = NULL; - esp_err_t err = httpd_req_async_handler_begin(req, ©); - if (err != ESP_OK) { - return err; - } + // must create a copy of the request that we own + httpd_req_t* copy = NULL; + esp_err_t err = httpd_req_async_handler_begin(req, ©); + if (err != ESP_OK) + { + return err; + } - httpd_async_req_t async_req = { - .req = copy, - .handler = handler, - }; + httpd_async_req_t async_req = { + .req = copy, + .handler = handler, + }; - // How should we handle resource exhaustion? - // In this example, we immediately respond with an - // http error if no workers are available. - int ticks = 0; + // How should we handle resource exhaustion? + // In this example, we immediately respond with an + // http error if no workers are available. + int ticks = 0; - // counting semaphore: if success, we know 1 or - // more asyncReqTaskWorkers are available. - if (xSemaphoreTake(worker_ready_count, ticks) == false) { - ESP_LOGE(PH_TAG, "No workers are available"); - httpd_req_async_handler_complete(copy); // cleanup - return ESP_FAIL; - } + // counting semaphore: if success, we know 1 or + // more asyncReqTaskWorkers are available. + if (xSemaphoreTake(worker_ready_count, ticks) == false) + { + ESP_LOGE(PH_TAG, "No workers are available"); + httpd_req_async_handler_complete(copy); // cleanup + return ESP_FAIL; + } - // Since worker_ready_count > 0 the queue should already have space. - // But lets wait up to 100ms just to be safe. - if (xQueueSend(async_req_queue, &async_req, pdMS_TO_TICKS(100)) == false) { - ESP_LOGE(PH_TAG, "worker queue is full"); - httpd_req_async_handler_complete(copy); // cleanup - return ESP_FAIL; - } + // Since worker_ready_count > 0 the queue should already have space. + // But lets wait up to 100ms just to be safe. + if (xQueueSend(async_req_queue, &async_req, pdMS_TO_TICKS(100)) == false) + { + ESP_LOGE(PH_TAG, "worker queue is full"); + httpd_req_async_handler_complete(copy); // cleanup + return ESP_FAIL; + } - return ESP_OK; + return ESP_OK; } -void async_req_worker_task(void *p) +void async_req_worker_task(void* p) { - ESP_LOGI(PH_TAG, "starting async req task worker"); + ESP_LOGI(PH_TAG, "starting async req task worker"); - while (true) { + while (true) + { - // counting semaphore - this signals that a worker - // is ready to accept work - xSemaphoreGive(worker_ready_count); + // counting semaphore - this signals that a worker + // is ready to accept work + xSemaphoreGive(worker_ready_count); - // wait for a request - httpd_async_req_t async_req; - if (xQueueReceive(async_req_queue, &async_req, portMAX_DELAY)) { + // wait for a request + httpd_async_req_t async_req; + if (xQueueReceive(async_req_queue, &async_req, portMAX_DELAY)) + { - ESP_LOGI(PH_TAG, "invoking %s", async_req.req->uri); + ESP_LOGI(PH_TAG, "invoking %s", async_req.req->uri); - // call the handler - async_req.handler(async_req.req); + // call the handler + async_req.handler(async_req.req); - // Inform the server that it can purge the socket used for - // this request, if needed. - if (httpd_req_async_handler_complete(async_req.req) != ESP_OK) { - ESP_LOGE(PH_TAG, "failed to complete async req"); - } - } + // Inform the server that it can purge the socket used for + // this request, if needed. + if (httpd_req_async_handler_complete(async_req.req) != ESP_OK) + { + ESP_LOGE(PH_TAG, "failed to complete async req"); + } } + } - ESP_LOGW(PH_TAG, "worker stopped"); - vTaskDelete(NULL); + ESP_LOGW(PH_TAG, "worker stopped"); + vTaskDelete(NULL); } void start_async_req_workers(void) { - // counting semaphore keeps track of available workers - worker_ready_count = xSemaphoreCreateCounting( - ASYNC_WORKER_COUNT, // Max Count - 0); // Initial Count - if (worker_ready_count == NULL) { - ESP_LOGE(PH_TAG, "Failed to create workers counting Semaphore"); - return; - } - - // create queue - async_req_queue = xQueueCreate(1, sizeof(httpd_async_req_t)); - if (async_req_queue == NULL){ - ESP_LOGE(PH_TAG, "Failed to create async_req_queue"); - vSemaphoreDelete(worker_ready_count); - return; - } - - // start worker tasks - for (int i = 0; i < ASYNC_WORKER_COUNT; i++) { - - bool success = xTaskCreate(async_req_worker_task, "async_req_worker", - ASYNC_WORKER_TASK_STACK_SIZE, // stack size - (void *)0, // argument - ASYNC_WORKER_TASK_PRIORITY, // priority - &worker_handles[i]); - - if (!success) { - ESP_LOGE(PH_TAG, "Failed to start asyncReqWorker"); - continue; - } + // counting semaphore keeps track of available workers + worker_ready_count = xSemaphoreCreateCounting( + ASYNC_WORKER_COUNT, // Max Count + 0); // Initial Count + if (worker_ready_count == NULL) + { + ESP_LOGE(PH_TAG, "Failed to create workers counting Semaphore"); + return; + } + + // create queue + async_req_queue = xQueueCreate(1, sizeof(httpd_async_req_t)); + if (async_req_queue == NULL) + { + ESP_LOGE(PH_TAG, "Failed to create async_req_queue"); + vSemaphoreDelete(worker_ready_count); + return; + } + + // start worker tasks + for (int i = 0; i < ASYNC_WORKER_COUNT; i++) + { + + bool success = xTaskCreate(async_req_worker_task, "async_req_worker", + ASYNC_WORKER_TASK_STACK_SIZE, // stack size + (void*)0, // argument + ASYNC_WORKER_TASK_PRIORITY, // priority + &worker_handles[i]); + + if (!success) + { + ESP_LOGE(PH_TAG, "Failed to start asyncReqWorker"); + continue; } + } } /**** - * + * * This code is backported from the 5.1.x branch - * -****/ + * + ****/ -#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#ifndef MAX + #define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif /* Calculate the maximum size needed for the scratch buffer */ -#define HTTPD_SCRATCH_BUF MAX(HTTPD_MAX_REQ_HDR_LEN, HTTPD_MAX_URI_LEN) +#define HTTPD_SCRATCH_BUF MAX(HTTPD_MAX_REQ_HDR_LEN, HTTPD_MAX_URI_LEN) /** * @brief Auxiliary data structure for use during reception and processing * of requests and temporarily keeping responses */ -struct httpd_req_aux { - struct sock_db *sd; /*!< Pointer to socket database */ - char scratch[HTTPD_SCRATCH_BUF + 1]; /*!< Temporary buffer for our operations (1 byte extra for null termination) */ - size_t remaining_len; /*!< Amount of data remaining to be fetched */ - char *status; /*!< HTTP response's status code */ - char *content_type; /*!< HTTP response's content type */ - bool first_chunk_sent; /*!< Used to indicate if first chunk sent */ - unsigned req_hdrs_count; /*!< Count of total headers in request packet */ - unsigned resp_hdrs_count; /*!< Count of additional headers in response packet */ - struct resp_hdr { - const char *field; - const char *value; - } *resp_hdrs; /*!< Additional headers in response packet */ - struct http_parser_url url_parse_res; /*!< URL parsing result, used for retrieving URL elements */ +struct httpd_req_aux +{ + struct sock_db* sd; /*!< Pointer to socket database */ + char scratch[HTTPD_SCRATCH_BUF + 1]; /*!< Temporary buffer for our operations (1 byte extra for null termination) */ + size_t remaining_len; /*!< Amount of data remaining to be fetched */ + char* status; /*!< HTTP response's status code */ + char* content_type; /*!< HTTP response's content type */ + bool first_chunk_sent; /*!< Used to indicate if first chunk sent */ + unsigned req_hdrs_count; /*!< Count of total headers in request packet */ + unsigned resp_hdrs_count; /*!< Count of additional headers in response packet */ + struct resp_hdr + { + const char* field; + const char* value; + }* resp_hdrs; /*!< Additional headers in response packet */ + struct http_parser_url url_parse_res; /*!< URL parsing result, used for retrieving URL elements */ #ifdef CONFIG_HTTPD_WS_SUPPORT - bool ws_handshake_detect; /*!< WebSocket handshake detection flag */ - httpd_ws_type_t ws_type; /*!< WebSocket frame type */ - bool ws_final; /*!< WebSocket FIN bit (final frame or not) */ - uint8_t mask_key[4]; /*!< WebSocket mask key for this payload */ + bool ws_handshake_detect; /*!< WebSocket handshake detection flag */ + httpd_ws_type_t ws_type; /*!< WebSocket frame type */ + bool ws_final; /*!< WebSocket FIN bit (final frame or not) */ + uint8_t mask_key[4]; /*!< WebSocket mask key for this payload */ #endif }; -esp_err_t httpd_req_async_handler_begin(httpd_req_t *r, httpd_req_t **out) +esp_err_t httpd_req_async_handler_begin(httpd_req_t* r, httpd_req_t** out) { - if (r == NULL || out == NULL) { - return ESP_ERR_INVALID_ARG; - } + if (r == NULL || out == NULL) + { + return ESP_ERR_INVALID_ARG; + } - // alloc async req - httpd_req_t *async = (httpd_req_t *)malloc(sizeof(httpd_req_t)); - if (async == NULL) { - return ESP_ERR_NO_MEM; - } - memcpy((void *)async, (void *)r, sizeof(httpd_req_t)); + // alloc async req + httpd_req_t* async = (httpd_req_t*)malloc(sizeof(httpd_req_t)); + if (async == NULL) + { + return ESP_ERR_NO_MEM; + } + memcpy((void*)async, (void*)r, sizeof(httpd_req_t)); - // alloc async aux - async->aux = (httpd_req_aux *)malloc(sizeof(struct httpd_req_aux)); - if (async->aux == NULL) { - free(async); - return ESP_ERR_NO_MEM; - } - memcpy(async->aux, r->aux, sizeof(struct httpd_req_aux)); + // alloc async aux + async->aux = (httpd_req_aux*)malloc(sizeof(struct httpd_req_aux)); + if (async->aux == NULL) + { + free(async); + return ESP_ERR_NO_MEM; + } + memcpy(async->aux, r->aux, sizeof(struct httpd_req_aux)); - // not available in 4.4.x - // mark socket as "in use" - // struct httpd_req_aux *ra = r->aux; - //ra->sd->for_async_req = true; + // not available in 4.4.x + // mark socket as "in use" + // struct httpd_req_aux *ra = r->aux; + // ra->sd->for_async_req = true; - *out = async; + *out = async; - return ESP_OK; + return ESP_OK; } -esp_err_t httpd_req_async_handler_complete(httpd_req_t *r) +esp_err_t httpd_req_async_handler_complete(httpd_req_t* r) { - if (r == NULL) { - return ESP_ERR_INVALID_ARG; - } + if (r == NULL) + { + return ESP_ERR_INVALID_ARG; + } - // not available in 4.4.x - // struct httpd_req_aux *ra = (httpd_req_aux *)r->aux; - // ra->sd->for_async_req = false; + // not available in 4.4.x + // struct httpd_req_aux *ra = (httpd_req_aux *)r->aux; + // ra->sd->for_async_req = false; - free(r->aux); - free(r); + free(r->aux); + free(r); - return ESP_OK; + return ESP_OK; } \ No newline at end of file diff --git a/lib/PsychicHttp/src/async_worker.h b/lib/PsychicHttp/src/async_worker.h index e77c4a6..1fdd1df 100644 --- a/lib/PsychicHttp/src/async_worker.h +++ b/lib/PsychicHttp/src/async_worker.h @@ -5,9 +5,9 @@ #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" -#define ASYNC_WORKER_TASK_PRIORITY 5 -#define ASYNC_WORKER_TASK_STACK_SIZE (4*1024) -#define ASYNC_WORKER_COUNT 8 +#define ASYNC_WORKER_TASK_PRIORITY 5 +#define ASYNC_WORKER_TASK_STACK_SIZE (4 * 1024) +#define ASYNC_WORKER_COUNT 8 // Async requests are queued here while they wait to be processed by the workers static QueueHandle_t async_req_queue; @@ -18,19 +18,20 @@ static SemaphoreHandle_t worker_ready_count; // Each worker has its own thread static TaskHandle_t worker_handles[ASYNC_WORKER_COUNT]; -typedef esp_err_t (*httpd_req_handler_t)(httpd_req_t *req); +typedef esp_err_t (*httpd_req_handler_t)(httpd_req_t* req); -typedef struct { +typedef struct +{ httpd_req_t* req; httpd_req_handler_t handler; } httpd_async_req_t; bool is_on_async_worker_thread(void); -esp_err_t submit_async_req(httpd_req_t *req, httpd_req_handler_t handler); -void async_req_worker_task(void *p); +esp_err_t submit_async_req(httpd_req_t* req, httpd_req_handler_t handler); +void async_req_worker_task(void* p); void start_async_req_workers(void); -esp_err_t httpd_req_async_handler_begin(httpd_req_t *r, httpd_req_t **out); -esp_err_t httpd_req_async_handler_complete(httpd_req_t *r); +esp_err_t httpd_req_async_handler_begin(httpd_req_t* r, httpd_req_t** out); +esp_err_t httpd_req_async_handler_complete(httpd_req_t* r); -#endif //async_worker_h \ No newline at end of file +#endif // async_worker_h \ No newline at end of file diff --git a/lib/PsychicHttp/src/http_status.cpp b/lib/PsychicHttp/src/http_status.cpp index 64e0c07..bcb3ace 100644 --- a/lib/PsychicHttp/src/http_status.cpp +++ b/lib/PsychicHttp/src/http_status.cpp @@ -2,193 +2,193 @@ bool http_informational(int code) { - return code >= 100 && code < 200; + return code >= 100 && code < 200; } bool http_success(int code) { - return code >= 200 && code < 300; + return code >= 200 && code < 300; } bool http_redirection(int code) { - return code >= 300 && code < 400; + return code >= 300 && code < 400; } bool http_client_error(int code) { - return code >= 400 && code < 500; + return code >= 400 && code < 500; } bool http_server_error(int code) { - return code >= 500 && code < 600; + return code >= 500 && code < 600; } bool http_failure(int code) { - return code >= 400 && code < 600; + return code >= 400 && code < 600; } -const char *http_status_group(int code) +const char* http_status_group(int code) { - if (http_informational(code)) - return "Informational"; + if (http_informational(code)) + return "Informational"; - if (http_success(code)) - return "Success"; + if (http_success(code)) + return "Success"; - if (http_redirection(code)) - return "Redirection"; + if (http_redirection(code)) + return "Redirection"; - if (http_client_error(code)) - return "Client Error"; + if (http_client_error(code)) + return "Client Error"; - if (http_server_error(code)) - return "Server Error"; + if (http_server_error(code)) + return "Server Error"; - return "Unknown"; + return "Unknown"; } -const char *http_status_reason(int code) +const char* http_status_reason(int code) { - switch (code) - { + switch (code) + { /*####### 1xx - Informational #######*/ case 100: - return "Continue"; + return "Continue"; case 101: - return "Switching Protocols"; + return "Switching Protocols"; case 102: - return "Processing"; + return "Processing"; case 103: - return "Early Hints"; + return "Early Hints"; /*####### 2xx - Successful #######*/ case 200: - return "OK"; + return "OK"; case 201: - return "Created"; + return "Created"; case 202: - return "Accepted"; + return "Accepted"; case 203: - return "Non-Authoritative Information"; + return "Non-Authoritative Information"; case 204: - return "No Content"; + return "No Content"; case 205: - return "Reset Content"; + return "Reset Content"; case 206: - return "Partial Content"; + return "Partial Content"; case 207: - return "Multi-Status"; + return "Multi-Status"; case 208: - return "Already Reported"; + return "Already Reported"; case 226: - return "IM Used"; + return "IM Used"; /*####### 3xx - Redirection #######*/ case 300: - return "Multiple Choices"; + return "Multiple Choices"; case 301: - return "Moved Permanently"; + return "Moved Permanently"; case 302: - return "Found"; + return "Found"; case 303: - return "See Other"; + return "See Other"; case 304: - return "Not Modified"; + return "Not Modified"; case 305: - return "Use Proxy"; + return "Use Proxy"; case 307: - return "Temporary Redirect"; + return "Temporary Redirect"; case 308: - return "Permanent Redirect"; + return "Permanent Redirect"; /*####### 4xx - Client Error #######*/ case 400: - return "Bad Request"; + return "Bad Request"; case 401: - return "Unauthorized"; + return "Unauthorized"; case 402: - return "Payment Required"; + return "Payment Required"; case 403: - return "Forbidden"; + return "Forbidden"; case 404: - return "Not Found"; + return "Not Found"; case 405: - return "Method Not Allowed"; + return "Method Not Allowed"; case 406: - return "Not Acceptable"; + return "Not Acceptable"; case 407: - return "Proxy Authentication Required"; + return "Proxy Authentication Required"; case 408: - return "Request Timeout"; + return "Request Timeout"; case 409: - return "Conflict"; + return "Conflict"; case 410: - return "Gone"; + return "Gone"; case 411: - return "Length Required"; + return "Length Required"; case 412: - return "Precondition Failed"; + return "Precondition Failed"; case 413: - return "Content Too Large"; + return "Content Too Large"; case 414: - return "URI Too Long"; + return "URI Too Long"; case 415: - return "Unsupported Media Type"; + return "Unsupported Media Type"; case 416: - return "Range Not Satisfiable"; + return "Range Not Satisfiable"; case 417: - return "Expectation Failed"; + return "Expectation Failed"; case 418: - return "I'm a teapot"; + return "I'm a teapot"; case 421: - return "Misdirected Request"; + return "Misdirected Request"; case 422: - return "Unprocessable Content"; + return "Unprocessable Content"; case 423: - return "Locked"; + return "Locked"; case 424: - return "Failed Dependency"; + return "Failed Dependency"; case 425: - return "Too Early"; + return "Too Early"; case 426: - return "Upgrade Required"; + return "Upgrade Required"; case 428: - return "Precondition Required"; + return "Precondition Required"; case 429: - return "Too Many Requests"; + return "Too Many Requests"; case 431: - return "Request Header Fields Too Large"; + return "Request Header Fields Too Large"; case 451: - return "Unavailable For Legal Reasons"; + return "Unavailable For Legal Reasons"; /*####### 5xx - Server Error #######*/ case 500: - return "Internal Server Error"; + return "Internal Server Error"; case 501: - return "Not Implemented"; + return "Not Implemented"; case 502: - return "Bad Gateway"; + return "Bad Gateway"; case 503: - return "Service Unavailable"; + return "Service Unavailable"; case 504: - return "Gateway Timeout"; + return "Gateway Timeout"; case 505: - return "HTTP Version Not Supported"; + return "HTTP Version Not Supported"; case 506: - return "Variant Also Negotiates"; + return "Variant Also Negotiates"; case 507: - return "Insufficient Storage"; + return "Insufficient Storage"; case 508: - return "Loop Detected"; + return "Loop Detected"; case 510: - return "Not Extended"; + return "Not Extended"; case 511: - return "Network Authentication Required"; + return "Network Authentication Required"; default: - return "Unknown"; - } + return "Unknown"; + } } \ No newline at end of file diff --git a/lib/PsychicHttp/src/http_status.h b/lib/PsychicHttp/src/http_status.h index e03b735..d9f71d3 100644 --- a/lib/PsychicHttp/src/http_status.h +++ b/lib/PsychicHttp/src/http_status.h @@ -9,7 +9,7 @@ bool http_redirection(int code); bool http_client_error(int code); bool http_server_error(int code); bool http_failure(int code); -const char *http_status_group(int code); -const char *http_status_reason(int code); +const char* http_status_group(int code); +const char* http_status_reason(int code); #endif // MICRO_HTTP_STATUS_H \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 37ddf68..08f6a64 100644 --- a/platformio.ini +++ b/platformio.ini @@ -33,8 +33,8 @@ build_unflags = build_flags = -fexceptions -DTLS_CA_MAX_SIZE=2200 - -DTLS_CERT_MAX_SIZE=1500 - -DTLS_KEY_MAX_SIZE=1800 + -DTLS_CERT_MAX_SIZE=2200 + -DTLS_KEY_MAX_SIZE=2200 -DESP_PLATFORM -DESP32 -DARDUINO_ARCH_ESP32 diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 3465515..7a29d2b 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -74,7 +74,6 @@ CONFIG_ARDUINO_SELECTIVE_ESP_SR=n CONFIG_ARDUINO_SELECTIVE_Zigbee=n CONFIG_ARDUINO_SELECTIVE_SD=n CONFIG_ARDUINO_SELECTIVE_SD_MMC=n -CONFIG_ARDUINO_SELECTIVE_SPIFFS=n CONFIG_ARDUINO_SELECTIVE_FFat=n CONFIG_ARDUINO_SELECTIVE_LittleFS=n CONFIG_ARDUINO_SELECTIVE_PPP=n @@ -98,11 +97,15 @@ CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH="resources/github_root_ca.pem" CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH=y CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH=y -CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 +CONFIG_HTTPD_MAX_REQ_HDR_LEN=2048 CONFIG_HTTPD_MAX_URI_LEN=512 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y CONFIG_HTTPD_PURGE_BUF_LEN=32 CONFIG_HTTPD_WS_SUPPORT=y -CONFIG_ESP_HTTPS_SERVER_ENABLE=n +CONFIG_ESP_HTTPS_SERVER_ENABLE=y CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE=y -CONFIG_BOOTLOADER_WDT_TIME_MS=120000 \ No newline at end of file +CONFIG_BOOTLOADER_WDT_TIME_MS=120000 +CONFIG_LWIP_MAX_SOCKETS=24 +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=1024 +CONFIG_ARDUINO_LOOP_STACK_SIZE=12288 \ No newline at end of file diff --git a/src/Config.h b/src/Config.h index aaa471f..0814eab 100644 --- a/src/Config.h +++ b/src/Config.h @@ -5,7 +5,7 @@ #define NUKI_HUB_VERSION "9.07" #define NUKI_HUB_VERSION_INT (uint32_t)907 #define NUKI_HUB_BUILD "unknownbuildnr" -#define NUKI_HUB_DATE "2024-12-30" +#define NUKI_HUB_DATE "2024-12-31" #define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest" #define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json" diff --git a/src/PreferencesKeys.h b/src/PreferencesKeys.h index f1b961a..e172907 100644 --- a/src/PreferencesKeys.h +++ b/src/PreferencesKeys.h @@ -67,6 +67,7 @@ #define preference_debug_hex_data (char*)"dbgHexData" #define preference_debug_command (char*)"dbgCommand" #define preference_connect_mode (char*)"nukiConnMode" +#define preference_http_auth_type (char*)"httpdAuthType" // CHANGE DOES NOT REQUIRE REBOOT TO TAKE EFFECT #define preference_find_best_rssi (char*)"nwbestrssi" @@ -222,8 +223,9 @@ inline void initPreferences(Preferences* preferences) preferences->putBool(preference_debug_hex_data, false); preferences->putBool(preference_debug_command, false); preferences->putBool(preference_connect_mode, true); - + preferences->putBool(preference_http_auth_type, false); preferences->putBool(preference_retain_gpio, false); + #ifndef CONFIG_IDF_TARGET_ESP32H2 WiFi.begin(); @@ -370,7 +372,7 @@ private: preference_opener_continuous_mode, preference_lock_max_keypad_code_count, preference_opener_max_keypad_code_count, preference_lock_max_timecontrol_entry_count, preference_opener_max_timecontrol_entry_count, preference_enable_bootloop_reset, preference_mqtt_ca, preference_mqtt_crt, preference_mqtt_key, preference_mqtt_hass_discovery, preference_mqtt_hass_cu_url, preference_buffer_size, preference_ip_dhcp_enabled, preference_ip_address, - preference_ip_subnet, preference_ip_gateway, preference_ip_dns_server, preference_network_hardware, + preference_ip_subnet, preference_ip_gateway, preference_ip_dns_server, preference_network_hardware, preference_http_auth_type, preference_rssi_publish_interval, preference_hostname, preference_network_timeout, preference_restart_on_disconnect, preference_restart_ble_beacon_lost, preference_query_interval_lockstate, preference_timecontrol_topic_per_entry, preference_keypad_topic_per_entry, preference_query_interval_configuration, preference_query_interval_battery, preference_query_interval_keypad, preference_keypad_control_enabled, @@ -402,7 +404,7 @@ private: preference_publish_authdata, preference_publish_debug_info, preference_official_hybrid_enabled, preference_mqtt_hass_enabled, preference_retain_gpio, preference_official_hybrid_actions, preference_official_hybrid_retry, preference_conf_info_enabled, preference_disable_non_json, preference_update_from_mqtt, preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_webserial_enabled, preference_hass_device_discovery, - preference_ntw_reconfigure, preference_keypad_check_code_enabled, preference_disable_network_not_connected, preference_find_best_rssi, + preference_ntw_reconfigure, preference_keypad_check_code_enabled, preference_disable_network_not_connected, preference_find_best_rssi, preference_http_auth_type, preference_debug_connect, preference_debug_communication, preference_debug_readable_data, preference_debug_hex_data, preference_debug_command, preference_connect_mode, preference_lock_force_id, preference_lock_force_doorsensor, preference_lock_force_keypad, preference_opener_force_id, preference_opener_force_keypad }; diff --git a/src/WebCfgServer.cpp b/src/WebCfgServer.cpp index ebf4d31..945c9f6 100644 --- a/src/WebCfgServer.cpp +++ b/src/WebCfgServer.cpp @@ -5,7 +5,9 @@ #include "RestartReason.h" #include #ifdef CONFIG_SOC_SPIRAM_SUPPORTED -#include +#include "esp_psram.h" +#include "FS.h" +#include "SPIFFS.h" #endif #ifndef CONFIG_IDF_TARGET_ESP32H2 #include @@ -75,64 +77,80 @@ WebCfgServer::WebCfgServer(NukiNetwork* network, Preferences* preferences, bool void WebCfgServer::initialize() { - _psychicServer->on("/", HTTP_GET, [&](PsychicRequest *request) + _psychicServer->onOpen([&](PsychicClient* client) { Log->printf("[http] connection #%u connected from %s\n", client->socket(), client->localIP().toString().c_str()); }); + _psychicServer->onClose([&](PsychicClient* client) { Log->printf("[http] connection #%u closed from %s\n", client->socket(), client->localIP().toString().c_str()); }); + + HTTPAuthMethod auth_type = BASIC_AUTH; + if (_preferences->getBool(preference_http_auth_type, false)) + { + auth_type = DIGEST_AUTH; + } + + _psychicServer->on("/", HTTP_GET, [&](PsychicRequest *request, PsychicResponse* resp) { if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in."); } + +#ifndef CONFIG_IDF_TARGET_ESP32H2 if(!_network->isApOpen()) { -#ifndef NUKI_HUB_UPDATER - return buildHtml(request); -#else - return buildOtaHtml(request); #endif - } +#ifndef NUKI_HUB_UPDATER + return buildHtml(request, resp); +#else + return buildOtaHtml(request, resp); +#endif #ifndef CONFIG_IDF_TARGET_ESP32H2 + } else { - return buildWifiConnectHtml(request); + return buildWifiConnectHtml(request, resp); } #endif }); - _psychicServer->on("/style.css", HTTP_GET, [&](PsychicRequest *request) + _psychicServer->on("/style.css", HTTP_GET, [&](PsychicRequest *request, PsychicResponse* resp) { if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in."); } - return sendCss(request); + + return sendCss(request, resp); }); - _psychicServer->on("/favicon.ico", HTTP_GET, [&](PsychicRequest *request) + _psychicServer->on("/favicon.ico", HTTP_GET, [&](PsychicRequest *request, PsychicResponse* resp) { if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in."); } - return sendFavicon(request); + + return sendFavicon(request, resp); }); if(_network->isApOpen()) { #ifndef CONFIG_IDF_TARGET_ESP32H2 - _psychicServer->on("/ssidlist", HTTP_GET, [&](PsychicRequest *request) + _psychicServer->on("/ssidlist", HTTP_GET, [&](PsychicRequest *request, PsychicResponse* resp) { if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in."); } - return buildSSIDListHtml(request); + + return buildSSIDListHtml(request, resp); }); - _psychicServer->on("/savewifi", HTTP_POST, [&](PsychicRequest *request) + _psychicServer->on("/savewifi", HTTP_POST, [&](PsychicRequest *request, PsychicResponse* resp) { if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in."); } + String message = ""; - bool connected = processWiFi(request, message); - esp_err_t res = buildConfirmHtml(request, message, 10, true); + bool connected = processWiFi(request, resp, message); + esp_err_t res = buildConfirmHtml(request, resp, message, 10, true); if(connected) { @@ -142,11 +160,11 @@ void WebCfgServer::initialize() } return res; }); - _psychicServer->on("/reboot", HTTP_GET, [&](PsychicRequest *request) + _psychicServer->on("/reboot", HTTP_GET, [&](PsychicRequest *request, PsychicResponse* resp) { if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in."); } String value = ""; @@ -160,14 +178,14 @@ void WebCfgServer::initialize() } else { - return buildConfirmHtml(request, "No confirm code set.", 3, true); + return buildConfirmHtml(request, resp, "No confirm code set.", 3, true); } if(value != _confirmCode) { - return request->redirect("/"); + return resp->redirect("/"); } - esp_err_t res = buildConfirmHtml(request, "Rebooting...", 2, true); + esp_err_t res = buildConfirmHtml(request, resp, "Rebooting...", 2, true); waitAndProcess(true, 1000); restartEsp(RestartReason::RequestedViaWebServer); return res; @@ -176,11 +194,11 @@ void WebCfgServer::initialize() } else { - _psychicServer->on("/get", HTTP_GET, [&](PsychicRequest *request) + _psychicServer->on("/get", HTTP_GET, [&](PsychicRequest *request, PsychicResponse* resp) { if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in."); } String value = ""; @@ -206,14 +224,14 @@ void WebCfgServer::initialize() } else { - return buildConfirmHtml(request, "No confirm code set.", 3, true); + return buildConfirmHtml(request, resp, "No confirm code set.", 3, true); } if(value != _confirmCode) { - return request->redirect("/"); + return resp->redirect("/"); } - esp_err_t res = buildConfirmHtml(request, "Rebooting...", 2, true); + esp_err_t res = buildConfirmHtml(request, resp, "Rebooting...", 2, true); waitAndProcess(true, 1000); restartEsp(RestartReason::RequestedViaWebServer); return res; @@ -221,78 +239,86 @@ void WebCfgServer::initialize() #ifndef NUKI_HUB_UPDATER else if (value == "info") { - return buildInfoHtml(request); + return buildInfoHtml(request, resp); } else if (value == "debugon") { _preferences->putBool(preference_publish_debug_info, true); - return buildConfirmHtml(request, "Debug On", 3, true); + return buildConfirmHtml(request, resp, "Debug On", 3, true); } else if (value == "debugoff") { _preferences->putBool(preference_publish_debug_info, false); - return buildConfirmHtml(request, "Debug Off", 3, true); + return buildConfirmHtml(request, resp, "Debug Off", 3, true); } else if (value == "export") { - return sendSettings(request); + return sendSettings(request, resp); } else if (value == "impexpcfg") { - return buildImportExportHtml(request); + return buildImportExportHtml(request, resp); } else if (value == "status") { - return buildStatusHtml(request); + return buildStatusHtml(request, resp); } else if (value == "acclvl") { - return buildAccLvlHtml(request); + return buildAccLvlHtml(request, resp); } else if (value == "custntw") { - return buildCustomNetworkConfigHtml(request); + return buildCustomNetworkConfigHtml(request, resp); } else if (value == "advanced") { - return buildAdvancedConfigHtml(request); + return buildAdvancedConfigHtml(request, resp); } else if (value == "cred") { - return buildCredHtml(request); + return buildCredHtml(request, resp); } else if (value == "ntwconfig") { - return buildNetworkConfigHtml(request); + return buildNetworkConfigHtml(request, resp); } else if (value == "mqttconfig") { - return buildMqttConfigHtml(request); + return buildMqttConfigHtml(request, resp); } else if (value == "mqttcaconfig") { - return buildMqttSSLConfigHtml(request, 0); + return buildMqttSSLConfigHtml(request, resp, 0); } else if (value == "mqttcrtconfig") { - return buildMqttSSLConfigHtml(request, 1); + return buildMqttSSLConfigHtml(request, resp, 1); } else if (value == "mqttkeyconfig") { - return buildMqttSSLConfigHtml(request, 2); + return buildMqttSSLConfigHtml(request, resp, 2); + } + else if (value == "httpcrtconfig") + { + return buildHttpSSLConfigHtml(request, resp, 1); + } + else if (value == "httpkeyconfig") + { + return buildHttpSSLConfigHtml(request, resp, 2); } else if (value == "nukicfg") { - return buildNukiConfigHtml(request); + return buildNukiConfigHtml(request, resp); } else if (value == "gpiocfg") { - return buildGpioConfigHtml(request); + return buildGpioConfigHtml(request, resp); } #ifndef CONFIG_IDF_TARGET_ESP32H2 else if (value == "wifi") { - return buildConfigureWifiHtml(request); + return buildConfigureWifiHtml(request, resp); } else if (value == "wifimanager") { @@ -307,17 +333,17 @@ void WebCfgServer::initialize() } else { - return buildConfirmHtml(request, "No confirm code set.", 3, true); + return buildConfirmHtml(request, resp, "No confirm code set.", 3, true); } if(value != _confirmCode) { - return request->redirect("/"); + return resp->redirect("/"); } if(!_allowRestartToPortal) { - return buildConfirmHtml(request, "Can't reset WiFi when network device is Ethernet", 3, true); + return buildConfirmHtml(request, resp, "Can't reset WiFi when network device is Ethernet", 3, true); } - esp_err_t res = buildConfirmHtml(request, "Restarting. Connect to ESP access point (\"NukiHub\" with password \"NukiHubESP32\") to reconfigure Wi-Fi.", 0); + esp_err_t res = buildConfirmHtml(request, resp, "Restarting. Connect to ESP access point (\"NukiHub\" with password \"NukiHubESP32\") to reconfigure Wi-Fi.", 0); waitAndProcess(false, 1000); _network->reconfigureDevice(); return res; @@ -326,11 +352,11 @@ void WebCfgServer::initialize() #endif else if (value == "ota") { - return buildOtaHtml(request); + return buildOtaHtml(request, resp); } else if (value == "otadebug") { - return buildOtaHtml(request, true); + return buildOtaHtml(request, resp, true); } else if (value == "reboottoota") { @@ -345,14 +371,14 @@ void WebCfgServer::initialize() } else { - return buildConfirmHtml(request, "No confirm code set.", 3, true); + return buildConfirmHtml(request, resp, "No confirm code set.", 3, true); } if(value != _confirmCode) { - return request->redirect("/"); + return resp->redirect("/"); } - esp_err_t res = buildConfirmHtml(request, "Rebooting to other partition...", 2, true); + esp_err_t res = buildConfirmHtml(request, resp, "Rebooting to other partition...", 2, true); waitAndProcess(true, 1000); esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL)); restartEsp(RestartReason::OTAReboot); @@ -361,34 +387,36 @@ void WebCfgServer::initialize() else if (value == "autoupdate") { #ifndef NUKI_HUB_UPDATER - return processUpdate(request); + return processUpdate(request, resp); #else - return request->redirect("/"); + return resp->redirect("/"); #endif } else { + #ifndef CONFIG_IDF_TARGET_ESP32H2 if(!_network->isApOpen()) { + #endif #ifndef NUKI_HUB_UPDATER - return buildHtml(request); + return buildHtml(request, resp); #else - return buildOtaHtml(request); + return buildOtaHtml(request, resp); #endif - } #ifndef CONFIG_IDF_TARGET_ESP32H2 + } else { - return buildWifiConnectHtml(request); + return buildWifiConnectHtml(request, resp); } #endif } }); - _psychicServer->on("/post", HTTP_POST, [&](PsychicRequest *request) + _psychicServer->on("/post", HTTP_POST, [&](PsychicRequest *request, PsychicResponse* resp) { if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in."); } String value = ""; @@ -405,20 +433,24 @@ void WebCfgServer::initialize() if (value == "savecfg") { String message = ""; - bool restart = processArgs(request, message); + bool restart = processArgs(request, resp, message); if(request->hasParam("mqttssl")) { - return buildConfirmHtml(request, message, 3, true, "/get?page=mqttconfig"); + return buildConfirmHtml(request, resp, message, 3, true, "/get?page=mqttconfig"); + } + else if(request->hasParam("httpssl")) + { + return buildConfirmHtml(request, resp, message, 3, true, "/get?page=ntwconfig"); } else { - return buildConfirmHtml(request, message, 3, true); + return buildConfirmHtml(request, resp, message, 3, true); } } else if (value == "savegpiocfg") { - processGpioArgs(request); - esp_err_t res = buildConfirmHtml(request, "Saving GPIO configuration. Restarting.", 3, true); + processGpioArgs(request, resp); + esp_err_t res = buildConfirmHtml(request, resp, "Saving GPIO configuration. Restarting.", 3, true); Log->println(F("Restarting")); waitAndProcess(true, 1000); restartEsp(RestartReason::GpioConfigurationUpdated); @@ -426,60 +458,62 @@ void WebCfgServer::initialize() } else if (value == "unpairlock") { - return processUnpair(request, false); + return processUnpair(request, resp, false); } else if (value == "unpairopener") { - return processUnpair(request, true); + return processUnpair(request, resp, true); } else if (value == "factoryreset") { - return processFactoryReset(request); + return processFactoryReset(request, resp); } else if (value == "import") { String message = ""; - bool restart = processImport(request, message); - return buildConfirmHtml(request, message, 3, true); + bool restart = processImport(request, resp, message); + return buildConfirmHtml(request, resp, message, 3, true); } else #else if (1 == 1) #endif { + #ifndef CONFIG_IDF_TARGET_ESP32H2 if(!_network->isApOpen()) { + #endif #ifndef NUKI_HUB_UPDATER - return buildHtml(request); + return buildHtml(request, resp); #else - return buildOtaHtml(request); + return buildOtaHtml(request, resp); #endif - } #ifndef CONFIG_IDF_TARGET_ESP32H2 + } else { - return buildWifiConnectHtml(request); + return buildWifiConnectHtml(request, resp); } #endif } }); PsychicUploadHandler *updateHandler = new PsychicUploadHandler(); - updateHandler->onUpload([&](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool final) + updateHandler->onUpload([&](PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool last) { if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in."); } - return handleOtaUpload(request, filename, index, data, len, final); - } - ); - updateHandler->onRequest([&](PsychicRequest *request) + return handleOtaUpload(request, filename, index, data, len, last); + }); + + updateHandler->onRequest([&](PsychicRequest* request, PsychicResponse* resp) { if(strlen(_credUser) > 0 && strlen(_credPassword) > 0 && !request->authenticate(_credUser, _credPassword)) { - return request->requestAuthentication(BASIC_AUTH, "Nuki Hub", "You must log in."); + return request->requestAuthentication(auth_type, "Nuki Hub", "You must log in."); } String result; @@ -488,7 +522,10 @@ void WebCfgServer::initialize() Log->print("Update code or data OK Update.errorString() "); Log->println(Update.errorString()); result = "Update OK."; - esp_err_t res = request->reply(200,"text/html",result.c_str()); + resp->setCode(200); + resp->setContentType("text/html"); + resp->setContent(result.c_str()); + esp_err_t res = resp->send(); restartEsp(RestartReason::OTACompleted); return res; } @@ -497,7 +534,10 @@ void WebCfgServer::initialize() result = " Update.errorString() " + String(Update.errorString()); Log->print("ERROR : error "); Log->println(result.c_str()); - esp_err_t res = request->reply(500, "text/html", result.c_str()); + resp->setCode(500); + resp->setContentType("text/html"); + resp->setContent(result.c_str()); + esp_err_t res = resp->send(); restartEsp(RestartReason::OTAAborted); return res; } @@ -531,12 +571,12 @@ void WebCfgServer::printCheckBox(PsychicStreamResponse *response, const char *to } #ifndef CONFIG_IDF_TARGET_ESP32H2 -esp_err_t WebCfgServer::buildSSIDListHtml(PsychicRequest *request) +esp_err_t WebCfgServer::buildSSIDListHtml(PsychicRequest *request, PsychicResponse* resp) { _network->scan(true, false); createSsidList(); - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); for (int i = 0; i < _ssidList.size(); i++) @@ -585,10 +625,10 @@ void WebCfgServer::createSsidList() } } -esp_err_t WebCfgServer::buildWifiConnectHtml(PsychicRequest *request) +esp_err_t WebCfgServer::buildWifiConnectHtml(PsychicRequest *request, PsychicResponse* resp) { String header = ""; - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); buildHtmlHeader(&response, header); response.print("

Available WiFi networks

"); @@ -622,7 +662,7 @@ esp_err_t WebCfgServer::buildWifiConnectHtml(PsychicRequest *request) return response.endSend(); } -bool WebCfgServer::processWiFi(PsychicRequest *request, String& message) +bool WebCfgServer::processWiFi(PsychicRequest *request, PsychicResponse* resp, String& message) { bool res = false; int params = request->params(); @@ -752,9 +792,9 @@ bool WebCfgServer::processWiFi(PsychicRequest *request, String& message) } #endif -esp_err_t WebCfgServer::buildOtaHtml(PsychicRequest *request, bool debug) +esp_err_t WebCfgServer::buildOtaHtml(PsychicRequest *request, PsychicResponse* resp, bool debug) { - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); buildHtmlHeader(&response); @@ -1117,9 +1157,9 @@ esp_err_t WebCfgServer::handleOtaUpload(PsychicRequest *request, const String& f } } -esp_err_t WebCfgServer::buildConfirmHtml(PsychicRequest *request, const String &message, uint32_t redirectDelay, bool redirect, String redirectTo) +esp_err_t WebCfgServer::buildConfirmHtml(PsychicRequest *request, PsychicResponse* resp, const String &message, uint32_t redirectDelay, bool redirect, String redirectTo) { - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); String header; @@ -1139,25 +1179,23 @@ esp_err_t WebCfgServer::buildConfirmHtml(PsychicRequest *request, const String & return response.endSend(); } -esp_err_t WebCfgServer::sendCss(PsychicRequest *request) +esp_err_t WebCfgServer::sendCss(PsychicRequest* request, PsychicResponse* resp) { // escaped by https://www.cescaper.com/ - PsychicResponse response(request); - response.addHeader("Cache-Control", "public, max-age=3600"); - response.setCode(200); - response.setContentType("text/css"); - response.setContent(stylecss); - return response.send(); + resp->addHeader("Cache-Control", "public, max-age=3600"); + resp->setCode(200); + resp->setContentType("text/css"); + resp->setContent(stylecss); + return resp->send(); } -esp_err_t WebCfgServer::sendFavicon(PsychicRequest *request) +esp_err_t WebCfgServer::sendFavicon(PsychicRequest* request, PsychicResponse* resp) { - PsychicResponse response(request); - response.addHeader("Cache-Control", "public, max-age=604800"); - response.setCode(200); - response.setContentType("image/png"); - response.setContent((const uint8_t *)favicon_32x32, sizeof(favicon_32x32)); - return response.send(); + resp->addHeader("Cache-Control", "public, max-age=604800"); + resp->setCode(200); + resp->setContentType("image/png"); + resp->setContent((const uint8_t *)favicon_32x32, sizeof(favicon_32x32)); + return resp->send(); } String WebCfgServer::generateConfirmCode() @@ -1166,7 +1204,7 @@ String WebCfgServer::generateConfirmCode() return String(code); } -void WebCfgServer::printInputField(PsychicStreamResponse *response, +void WebCfgServer::printInputField(PsychicStreamResponse* response, const char *token, const char *description, const char *value, @@ -1223,7 +1261,7 @@ void WebCfgServer::printInputField(PsychicStreamResponse *response, } #ifndef NUKI_HUB_UPDATER -esp_err_t WebCfgServer::sendSettings(PsychicRequest *request) +esp_err_t WebCfgServer::sendSettings(PsychicRequest *request, PsychicResponse* resp) { bool redacted = false; bool pairing = false; @@ -1425,11 +1463,13 @@ esp_err_t WebCfgServer::sendSettings(PsychicRequest *request) } serializeJsonPretty(json, jsonPretty); - - return request->reply(200, "application/json", jsonPretty.c_str()); + resp->setCode(200); + resp->setContentType("application/json"); + resp->setContent(jsonPretty.c_str()); + return resp->send(); } -bool WebCfgServer::processArgs(PsychicRequest *request, String& message) +bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, String& message) { bool configChanged = false; bool aclLvlChanged = false; @@ -1562,6 +1602,74 @@ bool WebCfgServer::processArgs(PsychicRequest *request, String& message) configChanged = true; } } + #ifdef CONFIG_SOC_SPIRAM_SUPPORTED + else if(key == "HTTPCRT") + { + if (!SPIFFS.begin(true)) { + Log->println("SPIFFS Mount Failed"); + } + else + { + if(value != "") + { + File file = SPIFFS.open("/http_ssl.crt", FILE_WRITE); + if (!file) { + Log->println("Failed to open /http_ssl.crt for writing"); + } + else + { + if (!file.print(value)) + { + Log->println("Failed to write /http_ssl.crt"); + } + file.close(); + } + } + else + { + if (!SPIFFS.remove("/http_ssl.crt")) { + Serial.println("Failed to delete /http_ssl.crt"); + } + } + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } + else if(key == "HTTPKEY") + { + if (!SPIFFS.begin(true)) { + Log->println("SPIFFS Mount Failed"); + } + else + { + if(value != "") + { + File file = SPIFFS.open("/http_ssl.key", FILE_WRITE); + if (!file) { + Log->println("Failed to open /http_ssl.key for writing"); + } + else + { + if (!file.print(value)) + { + Log->println("Failed to write /http_ssl.key"); + } + file.close(); + } + } + else + { + if (!SPIFFS.remove("/http_ssl.key")) { + Serial.println("Failed to delete /http_ssl.key"); + } + } + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } + #endif else if(key == "NWHW") { if(_preferences->getInt(preference_network_hardware, 0) != value.toInt()) @@ -2426,6 +2534,16 @@ bool WebCfgServer::processArgs(PsychicRequest *request, String& message) //configChanged = true; } } + else if(key == "CREDDIGEST") + { + if(_preferences->getBool(preference_http_auth_type, false) != (value == "1")) + { + _preferences->putBool(preference_http_auth_type, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } else if(key == "ACLLCKLCK") { aclPrefs[0] = ((value == "1") ? 1 : 0); @@ -2996,7 +3114,7 @@ bool WebCfgServer::processArgs(PsychicRequest *request, String& message) configChanged = true; } - if(pass1 != "" && pass1 == pass2) + if(pass1 != "" && pass1 != "*" && pass1 == pass2) { if(_preferences->getString(preference_cred_password, "") != pass1) { @@ -3137,7 +3255,7 @@ bool WebCfgServer::processArgs(PsychicRequest *request, String& message) return configChanged; } -bool WebCfgServer::processImport(PsychicRequest *request, String& message) +bool WebCfgServer::processImport(PsychicRequest *request, PsychicResponse* resp, String& message) { bool configChanged = false; unsigned char currentBleAddress[6]; @@ -3361,7 +3479,7 @@ bool WebCfgServer::processImport(PsychicRequest *request, String& message) return configChanged; } -void WebCfgServer::processGpioArgs(PsychicRequest *request) +void WebCfgServer::processGpioArgs(PsychicRequest *request, PsychicResponse* resp) { int params = request->params(); std::vector pinConfiguration; @@ -3393,9 +3511,9 @@ void WebCfgServer::processGpioArgs(PsychicRequest *request) _gpio->savePinConfiguration(pinConfiguration); } -esp_err_t WebCfgServer::buildImportExportHtml(PsychicRequest *request) +esp_err_t WebCfgServer::buildImportExportHtml(PsychicRequest *request, PsychicResponse* resp) { - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); buildHtmlHeader(&response); response.print("

Import configuration

"); @@ -3411,10 +3529,10 @@ esp_err_t WebCfgServer::buildImportExportHtml(PsychicRequest *request) return response.endSend(); } -esp_err_t WebCfgServer::buildCustomNetworkConfigHtml(PsychicRequest *request) +esp_err_t WebCfgServer::buildCustomNetworkConfigHtml(PsychicRequest *request, PsychicResponse* resp) { String header = ""; - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); buildHtmlHeader(&response, header); response.print("
"); @@ -3442,10 +3560,10 @@ esp_err_t WebCfgServer::buildCustomNetworkConfigHtml(PsychicRequest *request) return response.endSend(); } -esp_err_t WebCfgServer::buildHtml(PsychicRequest *request) +esp_err_t WebCfgServer::buildHtml(PsychicRequest *request, PsychicResponse* resp) { String header = ""; - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); buildHtmlHeader(&response, header); if(_rebootRequired) @@ -3541,9 +3659,9 @@ esp_err_t WebCfgServer::buildHtml(PsychicRequest *request) return response.endSend(); } -esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request) +esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request, PsychicResponse* resp) { - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); buildHtmlHeader(&response); response.print(""); @@ -3553,6 +3671,7 @@ esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request) printInputField(&response, "CREDUSER", "User (# to clear)", _preferences->getString(preference_cred_user).c_str(), 30, "id=\"inputuser\"", false, true); printInputField(&response, "CREDPASS", "Password", "*", 30, "id=\"inputpass\"", true, true); printInputField(&response, "CREDPASSRE", "Retype password", "*", 30, "id=\"inputpass2\"", true); + printCheckBox(&response, "CREDDIGEST", "Use Digest Authentication (more secure)", _preferences->getBool(preference_http_auth_type, false), ""); response.print(""); response.print("
"); response.print("
"); @@ -3626,9 +3745,9 @@ esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request) return response.endSend(); } -esp_err_t WebCfgServer::buildNetworkConfigHtml(PsychicRequest *request) +esp_err_t WebCfgServer::buildNetworkConfigHtml(PsychicRequest *request, PsychicResponse* resp) { - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); buildHtmlHeader(&response); response.print("
"); @@ -3644,6 +3763,13 @@ esp_err_t WebCfgServer::buildNetworkConfigHtml(PsychicRequest *request) printCheckBox(&response, "RSTDISC", "Restart on disconnect", _preferences->getBool(preference_restart_on_disconnect), ""); printCheckBox(&response, "CHECKUPDATE", "Check for Firmware Updates every 24h", _preferences->getBool(preference_check_updates), ""); printCheckBox(&response, "FINDBESTRSSI", "Find WiFi AP with strongest signal", _preferences->getBool(preference_find_best_rssi, false), ""); + #ifdef CONFIG_SOC_SPIRAM_SUPPORTED + if(esp_psram_get_size() > 0) + { + response.print("Set HTTP SSL Certificate"); + response.print("Set HTTP SSL Key"); + } + #endif response.print(""); response.print("

IP Address assignment

"); response.print(""); @@ -3659,9 +3785,9 @@ esp_err_t WebCfgServer::buildNetworkConfigHtml(PsychicRequest *request) return response.endSend(); } -esp_err_t WebCfgServer::buildMqttConfigHtml(PsychicRequest *request) +esp_err_t WebCfgServer::buildMqttConfigHtml(PsychicRequest *request, PsychicResponse* resp) { - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); buildHtmlHeader(&response); response.print(""); @@ -3685,7 +3811,7 @@ esp_err_t WebCfgServer::buildMqttConfigHtml(PsychicRequest *request) printCheckBox(&response, "OPENERCONT", "Set Nuki Opener Lock/Unlock action in Home Assistant to Continuous mode", _preferences->getBool(preference_opener_continuous_mode), ""); } response.print(""); - response.print(""); + response.print(""); response.print(""); printInputField(&response, "NETTIMEOUT", "MQTT Timeout until restart (seconds; -1 to disable)", _preferences->getInt(preference_network_timeout), 5, ""); printCheckBox(&response, "MQTTLOG", "Enable MQTT logging", _preferences->getBool(preference_mqtt_log_enabled), ""); @@ -3705,9 +3831,9 @@ esp_err_t WebCfgServer::buildMqttConfigHtml(PsychicRequest *request) return response.endSend(); } -esp_err_t WebCfgServer::buildMqttSSLConfigHtml(PsychicRequest *request, int type) +esp_err_t WebCfgServer::buildMqttSSLConfigHtml(PsychicRequest *request, PsychicResponse* resp, int type) { - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); buildHtmlHeader(&response); response.print(""); @@ -3736,9 +3862,84 @@ esp_err_t WebCfgServer::buildMqttSSLConfigHtml(PsychicRequest *request, int type return response.endSend(); } -esp_err_t WebCfgServer::buildAdvancedConfigHtml(PsychicRequest *request) +esp_err_t WebCfgServer::buildHttpSSLConfigHtml(PsychicRequest *request, PsychicResponse* resp, int type) { - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); + response.beginSend(); + buildHtmlHeader(&response); + response.print(""); + response.print(""); + response.print(""); + response.print("

HTTP SSL Configuration

"); + response.print("
Set MQTT SSL CA Certificate
Set MQTT SSL Client Certificate
Set MQTT SSL Client Certificate
Set MQTT SSL Client Key
"); + + if (type == 1) + { + char cert[4400] = {0}; + + #ifdef CONFIG_SOC_SPIRAM_SUPPORTED + if (!SPIFFS.begin(true)) { + Log->println("SPIFFS Mount Failed"); + } + else + { + File file = SPIFFS.open("/http_ssl.crt"); + if (!file || file.isDirectory()) { + Log->println("http_ssl.crt not found"); + } + else + { + Log->println("Reading http_ssl.crt"); + uint32_t i = 0; + while(file.available()){ + cert[i] = file.read(); + i++; + } + file.close(); + } + } + #endif + printTextarea(&response, "HTTPCRT", "HTTP SSL Certificate (*, optional)", cert, 4400, true, true); + } + else + { + char key[2200] = {0}; + + #ifdef CONFIG_SOC_SPIRAM_SUPPORTED + if (!SPIFFS.begin(true)) { + Log->println("SPIFFS Mount Failed"); + } + else + { + File file = SPIFFS.open("/http_ssl.key"); + if (!file || file.isDirectory()) { + Log->println("http_ssl.key not found"); + } + else + { + Log->println("Reading http_ssl.key"); + uint32_t i = 0; + while(file.available()){ + key[i] = file.read(); + i++; + } + file.close(); + } + } + #endif + printTextarea(&response, "HTTPKEY", "HTTP SSL Key (*, optional)", key, 2200, true, true); + } + response.print("
"); + response.print("
"); + response.print("
"); + response.print(""); + response.print(""); + return response.endSend(); +} + +esp_err_t WebCfgServer::buildAdvancedConfigHtml(PsychicRequest *request, PsychicResponse* resp) +{ + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); buildHtmlHeader(&response); response.print("
"); @@ -3815,7 +4016,7 @@ esp_err_t WebCfgServer::buildAdvancedConfigHtml(PsychicRequest *request) return response.endSend(); } -esp_err_t WebCfgServer::buildStatusHtml(PsychicRequest *request) +esp_err_t WebCfgServer::buildStatusHtml(PsychicRequest *request, PsychicResponse* resp) { JsonDocument json; String jsonStr; @@ -3913,7 +4114,10 @@ esp_err_t WebCfgServer::buildStatusHtml(PsychicRequest *request) } serializeJson(json, jsonStr); - return request->reply(200, "application/json", jsonStr.c_str()); + resp->setCode(200); + resp->setContentType("application/json"); + resp->setContent(jsonStr.c_str()); + return resp->send(); } String WebCfgServer::pinStateToString(uint8_t value) @@ -3931,9 +4135,9 @@ String WebCfgServer::pinStateToString(uint8_t value) } } -esp_err_t WebCfgServer::buildAccLvlHtml(PsychicRequest *request) +esp_err_t WebCfgServer::buildAccLvlHtml(PsychicRequest *request, PsychicResponse* resp) { - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); buildHtmlHeader(&response); @@ -4115,9 +4319,9 @@ esp_err_t WebCfgServer::buildAccLvlHtml(PsychicRequest *request) return response.endSend(); } -esp_err_t WebCfgServer::buildNukiConfigHtml(PsychicRequest *request) +esp_err_t WebCfgServer::buildNukiConfigHtml(PsychicRequest *request, PsychicResponse* resp) { - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); buildHtmlHeader(&response); response.print(""); @@ -4158,9 +4362,9 @@ esp_err_t WebCfgServer::buildNukiConfigHtml(PsychicRequest *request) return response.endSend(); } -esp_err_t WebCfgServer::buildGpioConfigHtml(PsychicRequest *request) +esp_err_t WebCfgServer::buildGpioConfigHtml(PsychicRequest *request, PsychicResponse* resp) { - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); buildHtmlHeader(&response); response.print(""); @@ -4213,9 +4417,9 @@ esp_err_t WebCfgServer::buildGpioConfigHtml(PsychicRequest *request) } #ifndef CONFIG_IDF_TARGET_ESP32H2 -esp_err_t WebCfgServer::buildConfigureWifiHtml(PsychicRequest *request) +esp_err_t WebCfgServer::buildConfigureWifiHtml(PsychicRequest *request, PsychicResponse* resp) { - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); buildHtmlHeader(&response); response.print(""); @@ -4229,11 +4433,11 @@ esp_err_t WebCfgServer::buildConfigureWifiHtml(PsychicRequest *request) } #endif -esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request) +esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request, PsychicResponse* resp) { uint32_t aclPrefs[17]; _preferences->getBytes(preference_acl, &aclPrefs, sizeof(aclPrefs)); - PsychicStreamResponse response(request, "text/html"); + PsychicStreamResponse response(resp, "text/html"); response.beginSend(); buildHtmlHeader(&response); response.print("

System Information

");
@@ -4308,6 +4512,10 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request)
     response.print(_preferences->getString(preference_cred_password, "").length() > 0 ? "***" : "Not set");
     response.print("\nWeb configurator enabled: ");
     response.print(_preferences->getBool(preference_webserver_enabled, true) ? "Yes" : "No");
+    //response.print("\nHTTP SSL CRT: ");
+    //response.print(_preferences->getString(preference_http_crt, "").length() > 0 ? "***" : "Not set");
+    //response.print("\nHTTP SSL Key: ");
+    //response.print(_preferences->getString(preference_http_key, "").length() > 0 ? "***" : "Not set");
     response.print("\nPublish debug information enabled: ");
     response.print(_preferences->getBool(preference_publish_debug_info, false) ? "Yes" : "No");
     response.print("\nMQTT log enabled: ");
@@ -4836,7 +5044,7 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request)
     return response.endSend();
 }
 
-esp_err_t WebCfgServer::processUnpair(PsychicRequest *request, bool opener)
+esp_err_t WebCfgServer::processUnpair(PsychicRequest *request, PsychicResponse* resp, bool opener)
 {
     String value = "";
     if(request->hasParam("CONFIRMTOKEN"))
@@ -4850,10 +5058,10 @@ esp_err_t WebCfgServer::processUnpair(PsychicRequest *request, bool opener)
 
     if(value != _confirmCode)
     {
-        return buildConfirmHtml(request, "Confirm code is invalid.", 3, true);
+        return buildConfirmHtml(request, resp, "Confirm code is invalid.", 3, true);
     }
 
-    esp_err_t res = buildConfirmHtml(request, opener ? "Unpairing Nuki Opener and restarting." : "Unpairing Nuki Lock and restarting.", 3, true);
+    esp_err_t res = buildConfirmHtml(request, resp, opener ? "Unpairing Nuki Opener and restarting." : "Unpairing Nuki Lock and restarting.", 3, true);
 
     if(!opener && _nuki != nullptr)
     {
@@ -4872,7 +5080,7 @@ esp_err_t WebCfgServer::processUnpair(PsychicRequest *request, bool opener)
     return res;
 }
 
-esp_err_t WebCfgServer::processUpdate(PsychicRequest *request)
+esp_err_t WebCfgServer::processUpdate(PsychicRequest *request, PsychicResponse* resp)
 {
     esp_err_t res;
     String value = "";
@@ -4887,20 +5095,20 @@ esp_err_t WebCfgServer::processUpdate(PsychicRequest *request)
 
     if(value != _confirmCode)
     {
-        return buildConfirmHtml(request, "Confirm code is invalid.", 3, true);
+        return buildConfirmHtml(request, resp, "Confirm code is invalid.", 3, true);
     }
 
     if(request->hasParam("beta"))
     {
         if(request->hasParam("debug"))
         {
-            res = buildConfirmHtml(request, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest DEBUG BETA version", 2, true); + res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest DEBUG BETA version", 2, true); _preferences->putString(preference_ota_updater_url, GITHUB_BETA_UPDATER_BINARY_URL_DBG); _preferences->putString(preference_ota_main_url, GITHUB_BETA_RELEASE_BINARY_URL_DBG); } else { - res = buildConfirmHtml(request, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest BETA version", 2, true); + res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest BETA version", 2, true); _preferences->putString(preference_ota_updater_url, GITHUB_BETA_UPDATER_BINARY_URL); _preferences->putString(preference_ota_main_url, GITHUB_BETA_RELEASE_BINARY_URL); } @@ -4909,13 +5117,13 @@ esp_err_t WebCfgServer::processUpdate(PsychicRequest *request) { if(request->hasParam("debug")) { - res = buildConfirmHtml(request, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest DEBUG DEVELOPMENT version", 2, true); + res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest DEBUG DEVELOPMENT version", 2, true); _preferences->putString(preference_ota_updater_url, GITHUB_MASTER_UPDATER_BINARY_URL_DBG); _preferences->putString(preference_ota_main_url, GITHUB_MASTER_RELEASE_BINARY_URL_DBG); } else { - res = buildConfirmHtml(request, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest DEVELOPMENT version", 2, true); + res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest DEVELOPMENT version", 2, true); _preferences->putString(preference_ota_updater_url, GITHUB_MASTER_UPDATER_BINARY_URL); _preferences->putString(preference_ota_main_url, GITHUB_MASTER_RELEASE_BINARY_URL); } @@ -4924,13 +5132,13 @@ esp_err_t WebCfgServer::processUpdate(PsychicRequest *request) { if(request->hasParam("debug")) { - res = buildConfirmHtml(request, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest DEBUG RELEASE version", 2, true); + res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest DEBUG RELEASE version", 2, true); _preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL_DBG); _preferences->putString(preference_ota_main_url, GITHUB_LATEST_UPDATER_BINARY_URL_DBG); } else { - res = buildConfirmHtml(request, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest RELEASE version", 2, true); + res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest RELEASE version", 2, true); _preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL); _preferences->putString(preference_ota_main_url, GITHUB_LATEST_RELEASE_BINARY_URL); } @@ -4940,7 +5148,7 @@ esp_err_t WebCfgServer::processUpdate(PsychicRequest *request) return res; } -esp_err_t WebCfgServer::processFactoryReset(PsychicRequest *request) +esp_err_t WebCfgServer::processFactoryReset(PsychicRequest *request, PsychicResponse* resp) { esp_err_t res; String value = ""; @@ -4956,7 +5164,7 @@ esp_err_t WebCfgServer::processFactoryReset(PsychicRequest *request) bool resetWifi = false; if(value.length() == 0 || value != _confirmCode) { - return buildConfirmHtml(request, "Confirm code is invalid.", 3, true); + return buildConfirmHtml(request, resp, "Confirm code is invalid.", 3, true); } else { @@ -4973,11 +5181,11 @@ esp_err_t WebCfgServer::processFactoryReset(PsychicRequest *request) if(value2 == "1") { resetWifi = true; - res = buildConfirmHtml(request, "Factory resetting Nuki Hub, unpairing Nuki Lock and Nuki Opener and resetting WiFi.", 3, true); + res = buildConfirmHtml(request, resp, "Factory resetting Nuki Hub, unpairing Nuki Lock and Nuki Opener and resetting WiFi.", 3, true); } else { - res = buildConfirmHtml(request, "Factory resetting Nuki Hub, unpairing Nuki Lock and Nuki Opener.", 3, true); + res = buildConfirmHtml(request, resp, "Factory resetting Nuki Hub, unpairing Nuki Lock and Nuki Opener.", 3, true); } } @@ -4991,7 +5199,7 @@ esp_err_t WebCfgServer::processFactoryReset(PsychicRequest *request) { _nukiOpener->unpair(); } - + String ssid = _preferences->getString(preference_wifi_ssid, ""); String pass = _preferences->getString(preference_wifi_pass, ""); diff --git a/src/WebCfgServer.h b/src/WebCfgServer.h index 68359e4..0865d81 100644 --- a/src/WebCfgServer.h +++ b/src/WebCfgServer.h @@ -48,29 +48,30 @@ public: private: #ifndef NUKI_HUB_UPDATER - esp_err_t sendSettings(PsychicRequest *request); - bool processArgs(PsychicRequest *request, String& message); - bool processImport(PsychicRequest *request, String& message); - void processGpioArgs(PsychicRequest *request); - esp_err_t buildHtml(PsychicRequest *request); - esp_err_t buildAccLvlHtml(PsychicRequest *request); - esp_err_t buildCredHtml(PsychicRequest *request); - esp_err_t buildImportExportHtml(PsychicRequest *request); - esp_err_t buildNetworkConfigHtml(PsychicRequest *request); - esp_err_t buildMqttConfigHtml(PsychicRequest *request); - esp_err_t buildMqttSSLConfigHtml(PsychicRequest *request, int type=0); - esp_err_t buildStatusHtml(PsychicRequest *request); - esp_err_t buildAdvancedConfigHtml(PsychicRequest *request); - esp_err_t buildNukiConfigHtml(PsychicRequest *request); - esp_err_t buildGpioConfigHtml(PsychicRequest *request); + esp_err_t sendSettings(PsychicRequest *request, PsychicResponse* resp); + bool processArgs(PsychicRequest *request, PsychicResponse* resp, String& message); + bool processImport(PsychicRequest *request, PsychicResponse* resp, String& message); + void processGpioArgs(PsychicRequest *request, PsychicResponse* resp); + esp_err_t buildHtml(PsychicRequest *request, PsychicResponse* resp); + esp_err_t buildAccLvlHtml(PsychicRequest *request, PsychicResponse* resp); + esp_err_t buildCredHtml(PsychicRequest *request, PsychicResponse* resp); + esp_err_t buildImportExportHtml(PsychicRequest *request, PsychicResponse* resp); + esp_err_t buildNetworkConfigHtml(PsychicRequest *request, PsychicResponse* resp); + esp_err_t buildMqttConfigHtml(PsychicRequest *request, PsychicResponse* resp); + esp_err_t buildMqttSSLConfigHtml(PsychicRequest *request, PsychicResponse* resp, int type=0); + esp_err_t buildHttpSSLConfigHtml(PsychicRequest *request, PsychicResponse* resp, int type=0); + esp_err_t buildStatusHtml(PsychicRequest *request, PsychicResponse* resp); + esp_err_t buildAdvancedConfigHtml(PsychicRequest *request, PsychicResponse* resp); + esp_err_t buildNukiConfigHtml(PsychicRequest *request, PsychicResponse* resp); + esp_err_t buildGpioConfigHtml(PsychicRequest *request, PsychicResponse* resp); #ifndef CONFIG_IDF_TARGET_ESP32H2 - esp_err_t buildConfigureWifiHtml(PsychicRequest *request); + esp_err_t buildConfigureWifiHtml(PsychicRequest *request, PsychicResponse* resp); #endif - esp_err_t buildInfoHtml(PsychicRequest *request); - esp_err_t buildCustomNetworkConfigHtml(PsychicRequest *request); - esp_err_t processUnpair(PsychicRequest *request, bool opener); - esp_err_t processUpdate(PsychicRequest *request); - esp_err_t processFactoryReset(PsychicRequest *request); + esp_err_t buildInfoHtml(PsychicRequest *request, PsychicResponse* resp); + esp_err_t buildCustomNetworkConfigHtml(PsychicRequest *request, PsychicResponse* resp); + esp_err_t processUnpair(PsychicRequest *request, PsychicResponse* resp, bool opener); + esp_err_t processUpdate(PsychicRequest *request, PsychicResponse* resp); + esp_err_t processFactoryReset(PsychicRequest *request, PsychicResponse* resp); void printTextarea(PsychicStreamResponse *response, const char *token, const char *description, const char *value, const size_t& maxLength, const bool& enabled = true, const bool& showLengthRestriction = false); void printDropDown(PsychicStreamResponse *response, const char *token, const char *description, const String preselectedValue, std::vector> options, const String className); void buildNavigationMenuEntry(PsychicStreamResponse *response, const char *title, const char *targetPath, const char* warningMessage = ""); @@ -94,30 +95,30 @@ private: bool _brokerConfigured = false; bool _rebootRequired = false; #endif - + std::vector _ssidList; std::vector _rssiList; String generateConfirmCode(); String _confirmCode = "----"; - - esp_err_t buildSSIDListHtml(PsychicRequest *request); - esp_err_t buildConfirmHtml(PsychicRequest *request, const String &message, uint32_t redirectDelay = 5, bool redirect = false, String redirectTo = "/"); - esp_err_t buildOtaHtml(PsychicRequest *request, bool debug = false); - esp_err_t sendCss(PsychicRequest *request); - esp_err_t sendFavicon(PsychicRequest *request); + + esp_err_t buildSSIDListHtml(PsychicRequest *request, PsychicResponse* resp); + esp_err_t buildConfirmHtml(PsychicRequest *request, PsychicResponse* resp, const String &message, uint32_t redirectDelay = 5, bool redirect = false, String redirectTo = "/"); + esp_err_t buildOtaHtml(PsychicRequest *request, PsychicResponse* resp, bool debug = false); + esp_err_t sendCss(PsychicRequest *request, PsychicResponse* resp); + esp_err_t sendFavicon(PsychicRequest *request, PsychicResponse* resp); void createSsidList(); void buildHtmlHeader(PsychicStreamResponse *response, String additionalHeader = ""); void waitAndProcess(const bool blocking, const uint32_t duration); esp_err_t handleOtaUpload(PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool final); void printCheckBox(PsychicStreamResponse *response, const char* token, const char* description, const bool value, const char* htmlClass); #ifndef CONFIG_IDF_TARGET_ESP32H2 - esp_err_t buildWifiConnectHtml(PsychicRequest *request); - bool processWiFi(PsychicRequest *request, String& message); - + esp_err_t buildWifiConnectHtml(PsychicRequest *request, PsychicResponse* resp); + bool processWiFi(PsychicRequest *request, PsychicResponse* resp, String& message); + #endif void printInputField(PsychicStreamResponse *response, const char* token, const char* description, const char* value, const size_t& maxLength, const char* args, const bool& isPassword = false, const bool& showLengthRestriction = false); void printInputField(PsychicStreamResponse *response, const char* token, const char* description, const int value, size_t maxLength, const char* args); - + PsychicHttpServer* _psychicServer = nullptr; NukiNetwork* _network = nullptr; Preferences* _preferences = nullptr; diff --git a/src/main.cpp b/src/main.cpp index 40958ea..6e9d3c0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,11 @@ #include "esp32-hal-log.h" #include "hal/wdt_hal.h" #include "esp_chip_info.h" +#ifdef CONFIG_SOC_SPIRAM_SUPPORTED +#include "esp_psram.h" +#include "FS.h" +#include "SPIFFS.h" +#endif #ifndef NUKI_HUB_UPDATER #include "NukiWrapper.h" @@ -65,8 +70,10 @@ int64_t restartTs = 10 * 60 * 1000; #endif PsychicHttpServer* psychicServer = nullptr; +PsychicHttpsServer* psychicSSLServer = nullptr; NukiNetwork* network = nullptr; WebCfgServer* webCfgServer = nullptr; +WebCfgServer* webCfgServerSSL = nullptr; Preferences* preferences = nullptr; RTC_NOINIT_ATTR int espRunning; @@ -80,6 +87,7 @@ RTC_NOINIT_ATTR bool disableNetwork; RTC_NOINIT_ATTR bool wifiFallback; RTC_NOINIT_ATTR bool ethCriticalFailure; +bool doOta = false; bool restartReason_isValid; RestartReason currentRestartReason = RestartReason::NotApplicable; @@ -132,7 +140,7 @@ void setReroute() esp_log_level_set("httpd_parse", ESP_LOG_ERROR); esp_log_level_set("httpd_txrx", ESP_LOG_ERROR); esp_log_level_set("httpd_uri", ESP_LOG_ERROR); - esp_log_level_set("event", ESP_LOG_ERROR); + esp_log_level_set("event", ESP_LOG_ERROR); esp_log_level_set("psychic", ESP_LOG_ERROR); esp_log_level_set("ARDUINO", ESP_LOG_DEBUG); esp_log_level_set("nvs", ESP_LOG_ERROR); @@ -494,7 +502,6 @@ void setup() preferences = new Preferences(); preferences->begin("nukihub", false); initPreferences(preferences); - bool doOta = false; uint8_t partitionType = checkPartition(); initializeRestartReason(); @@ -539,16 +546,84 @@ void setup() if(!doOta) { - psychicServer = new PsychicHttpServer; - psychicServer->config.max_uri_handlers = 10; - psychicServer->config.stack_size = HTTPD_TASK_SIZE; - psychicServer->listen(80); - webCfgServer = new WebCfgServer(network, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicServer); - webCfgServer->initialize(); - psychicServer->onNotFound([](PsychicRequest* request) + #ifdef CONFIG_SOC_SPIRAM_SUPPORTED + bool failed = false; + + if (esp_psram_get_size() <= 0) { + Log->println("Not running on PSRAM enabled device"); + failed = true; + } + else { - return request->redirect("/"); - }); + if (!SPIFFS.begin(true)) { + Log->println("SPIFFS Mount Failed"); + failed = true; + } + else + { + File file = SPIFFS.open("/http_ssl.crt"); + if (!file || file.isDirectory()) { + failed = true; + Log->println("http_ssl.crt not found"); + } + else + { + char cert[4400] = {0}; + + Log->println("Reading http_ssl.crt"); + uint32_t i = 0; + while(file.available()){ + cert[i] = file.read(); + i++; + } + file.close(); + + File file2 = SPIFFS.open("/http_ssl.key"); + if (!file2 || file2.isDirectory()) { + failed = true; + Log->println("http_ssl.key not found"); + } + else + { + char key[2200] = {0}; + + Log->println("Reading http_ssl.key"); + i = 0; + while(file2.available()){ + key[i] = file2.read(); + i++; + } + file2.close(); + + psychicSSLServer = new PsychicHttpsServer; + psychicSSLServer->ssl_config.httpd.max_open_sockets = 8; + psychicSSLServer->setCertificate(cert, key); + psychicSSLServer->config.stack_size = HTTPD_TASK_SIZE; + webCfgServerSSL = new WebCfgServer(network, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicSSLServer); + webCfgServerSSL->initialize(); + psychicSSLServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) { + return response->redirect("/"); + }); + psychicSSLServer->begin(); + } + } + } + } + + if (failed) + { + #endif + psychicServer = new PsychicHttpServer; + psychicServer->config.stack_size = HTTPD_TASK_SIZE; + webCfgServer = new WebCfgServer(network, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicServer); + webCfgServer->initialize(); + psychicServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) { + return response->redirect("/"); + }); + psychicServer->begin(); + #ifdef CONFIG_SOC_SPIRAM_SUPPORTED + } + #endif } #else if(preferences->getBool(preference_enable_bootloop_reset, false)) @@ -635,19 +710,86 @@ void setup() if(!doOta && !disableNetwork && (forceEnableWebServer || preferences->getBool(preference_webserver_enabled, true) || preferences->getBool(preference_webserial_enabled, false))) { - psychicServer = new PsychicHttpServer; - psychicServer->config.max_uri_handlers = 10; - psychicServer->config.stack_size = HTTPD_TASK_SIZE; - psychicServer->listen(80); - if(forceEnableWebServer || preferences->getBool(preference_webserver_enabled, true)) { - webCfgServer = new WebCfgServer(nuki, nukiOpener, network, gpio, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicServer); - webCfgServer->initialize(); - psychicServer->onNotFound([](PsychicRequest* request) + #ifdef CONFIG_SOC_SPIRAM_SUPPORTED + bool failed = false; + + if (esp_psram_get_size() <= 0) { + Log->println("Not running on PSRAM enabled device"); + failed = true; + } + else { - return request->redirect("/"); - }); + if (!SPIFFS.begin(true)) { + Log->println("SPIFFS Mount Failed"); + failed = true; + } + else + { + File file = SPIFFS.open("/http_ssl.crt"); + if (!file || file.isDirectory()) { + failed = true; + Log->println("http_ssl.crt not found"); + } + else + { + char cert[4400] = {0}; + + Log->println("Reading http_ssl.crt"); + uint32_t i = 0; + while(file.available()){ + cert[i] = file.read(); + i++; + } + file.close(); + + File file2 = SPIFFS.open("/http_ssl.key"); + if (!file2 || file2.isDirectory()) { + failed = true; + Log->println("http_ssl.key not found"); + } + else + { + char key[2200] = {0}; + + Log->println("Reading http_ssl.key"); + i = 0; + while(file2.available()){ + key[i] = file2.read(); + i++; + } + file2.close(); + + psychicSSLServer = new PsychicHttpsServer; + psychicSSLServer->ssl_config.httpd.max_open_sockets = 8; + psychicSSLServer->setCertificate(cert, key); + psychicSSLServer->config.stack_size = HTTPD_TASK_SIZE; + webCfgServerSSL = new WebCfgServer(nuki, nukiOpener, network, gpio, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicSSLServer); + webCfgServerSSL->initialize(); + psychicSSLServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) { + return response->redirect("/"); + }); + psychicSSLServer->begin(); + } + } + } + } + + if (failed) + { + #endif + psychicServer = new PsychicHttpServer; + psychicServer->config.stack_size = HTTPD_TASK_SIZE; + webCfgServer = new WebCfgServer(nuki, nukiOpener, network, gpio, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicServer); + webCfgServer->initialize(); + psychicServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) { + return response->redirect("/"); + }); + psychicServer->begin(); + #ifdef CONFIG_SOC_SPIRAM_SUPPORTED + } + #endif } /* #ifdef DEBUG_NUKIHUB @@ -656,7 +798,7 @@ void setup() if(preferences->getBool(preference_webserial_enabled, false)) { WebSerial.setAuthentication(preferences->getString(preference_cred_user), preferences->getString(preference_cred_password)); - WebSerial.begin(asyncServer); + WebSerial.begin(psychicServer); WebSerial.setBuffer(1024); } #endif diff --git a/updater/sdkconfig.defaults b/updater/sdkconfig.defaults index 3d1adba..274c771 100644 --- a/updater/sdkconfig.defaults +++ b/updater/sdkconfig.defaults @@ -18,7 +18,6 @@ CONFIG_ARDUINO_SELECTIVE_ESP_SR=n CONFIG_ARDUINO_SELECTIVE_Zigbee=n CONFIG_ARDUINO_SELECTIVE_SD=n CONFIG_ARDUINO_SELECTIVE_SD_MMC=n -CONFIG_ARDUINO_SELECTIVE_SPIFFS=n CONFIG_ARDUINO_SELECTIVE_FFat=n CONFIG_ARDUINO_SELECTIVE_LittleFS=n CONFIG_ARDUINO_SELECTIVE_PPP=n @@ -43,10 +42,16 @@ CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL=y CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH=y CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH=y -CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 +CONFIG_HTTPD_MAX_REQ_HDR_LEN=2048 CONFIG_HTTPD_MAX_URI_LEN=512 CONFIG_HTTPD_ERR_RESP_NO_DELAY=y CONFIG_HTTPD_PURGE_BUF_LEN=32 CONFIG_HTTPD_WS_SUPPORT=y -CONFIG_ESP_HTTPS_SERVER_ENABLE=n -CONFIG_LOG_MASTER_LEVEL=y \ No newline at end of file +CONFIG_ESP_HTTPS_SERVER_ENABLE=y +CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE=y +CONFIG_BOOTLOADER_WDT_TIME_MS=120000 +CONFIG_BOOTLOADER_WDT_TIME_MS=120000 +CONFIG_LWIP_MAX_SOCKETS=24 +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=1024 +CONFIG_ARDUINO_LOOP_STACK_SIZE=12288 \ No newline at end of file