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..958e5d9 100644 --- a/lib/PsychicHttp/src/PsychicHttpServer.cpp +++ b/lib/PsychicHttp/src/PsychicHttpServer.cpp @@ -1,155 +1,328 @@ #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" #include "WiFi.h" +#ifdef PSY_ENABLE_ETHERNET + #include "ETH.h" +#endif -PsychicHttpServer::PsychicHttpServer() : - _onOpen(NULL), - _onClose(NULL) +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; +} + +bool PsychicHttpServer::isConnected() +{ + if (WiFi.softAPIP()) + return true; + if (WiFi.localIP()) + return true; + +#ifdef PSY_ENABLE_ETHERNET + if (ETH.localIP()) + return true; +#endif + + return false; +} + +esp_err_t PsychicHttpServer::start() +{ + if (_running) + return ESP_OK; + + // starting without network will crash us. + if (!isConnected()) { + ESP_LOGE(PH_TAG, "Server start failed - no network."); + return ESP_FAIL; + } + esp_err_t ret; - #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 +333,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 +347,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 +535,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 +561,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,49 +593,49 @@ 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 +bool ON_STA_FILTER(PsychicRequest* request) +{ return WiFi.localIP() == request->client()->localIP(); - #else - return false; - #endif } -bool ON_AP_FILTER(PsychicRequest *request) { - #ifndef CONFIG_IDF_TARGET_ESP32H2 +bool ON_AP_FILTER(PsychicRequest* request) +{ return WiFi.softAPIP() == request->client()->localIP(); - #else - return false; - #endif } String urlDecode(const char* encoded) @@ -350,25 +648,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..c4afb6c 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,124 @@ 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 isConnected(); + bool isRunning() { return _running; } + esp_err_t begin() { return start(); } + esp_err_t end() { return stop(); } + esp_err_t start(); + esp_err_t stop(); + void reset(); - PsychicHandler& addHandler(PsychicHandler* handler); + httpd_uri_match_func_t getURIMatchFunction(); + void setURIMatchFunction(httpd_uri_match_func_t match_fn); + + PsychicRewrite* addRewrite(PsychicRewrite* rewrite); + void removeRewrite(PsychicRewrite* rewrite); + PsychicRewrite* rewrite(const char* from, const char* to); + + PsychicHandler* addHandler(PsychicHandler* handler); void removeHandler(PsychicHandler* handler); - void 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..0f4de06 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,71 +276,111 @@ 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. - _addParams(_query, false); + // 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; + } } - //did we get form data as body? - if (this->method() == HTTP_POST && this->contentType().startsWith("application/x-www-form-urlencoded")) - { - _addParams(_body, true); + _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); } } -void PsychicRequest::_addParams(const String& params, bool post){ +void PsychicRequest::_addParams(const String& params, bool post) +{ size_t start = 0; - while (start < params.length()){ + while (start < params.length()) { int end = params.indexOf('&', start); - if (end < 0) end = params.length(); + if (end < 0) + end = params.length(); int equal = params.indexOf('=', start); - if (equal < 0 || equal > end) equal = end; + if (equal < 0 || equal > end) + equal = end; String name = params.substring(start, equal); String value = equal + 1 < end ? params.substring(equal + 1, end) : String(); addParam(name, value, true, post); @@ -304,7 +388,7 @@ void PsychicRequest::_addParams(const String& params, bool post){ } } -PsychicWebParameter * PsychicRequest::addParam(const String &name, const String &value, bool decode, bool post) +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)); @@ -312,40 +396,37 @@ PsychicWebParameter * PsychicRequest::addParam(const String &name, const String return addParam(new PsychicWebParameter(name, value, post)); } -PsychicWebParameter * PsychicRequest::addParam(PsychicWebParameter *param) { +PsychicWebParameter* PsychicRequest::addParam(PsychicWebParameter* param) +{ // ESP_LOGD(PH_TAG, "Adding param: '%s' = '%s'", param->name().c_str(), param->value().c_str()); _params.push_back(param); return param; } -int PsychicRequest::params() -{ - return _params.size(); -} - -bool PsychicRequest::hasParam(const char *key) +bool PsychicRequest::hasParam(const char* key) { return getParam(key) != NULL; } -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; return NULL; } -PsychicWebParameter * PsychicRequest::getParam(int index) +PsychicWebParameter* PsychicRequest::getParam(const char* key, bool isPost, bool isFile) { - if (_params.size() > index){ - std::list::iterator it = _params.begin(); - for(int i=0; iname().equals(key) && isPost == param->isPost() && isFile == param->isFile()) + return param; return NULL; } @@ -368,7 +449,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 +458,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 +486,61 @@ 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); + 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 +555,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 +579,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 +596,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..73f6613 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,23 @@ 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); - int params(); - bool hasParam(const char *key); - PsychicWebParameter * getParam(const char *name); - PsychicWebParameter * getParam(int index); - + PsychicWebParameter* addParam(PsychicWebParameter* param); + PsychicWebParameter* addParam(const String& name, const String& value, bool decode = true, bool post = false); + bool hasParam(const char* key); + bool hasParam(const char* key, bool isPost, bool isFile = false); + PsychicWebParameter* getParam(const char* name); + PsychicWebParameter* getParam(const char* name, 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..8723090 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"); + 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