From ed921a89744097a030d34f714ff221842331a5ff Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Wed, 14 Aug 2024 13:55:27 +0200 Subject: [PATCH] Proposal of API change for v2 Feature: Middleware / Filters #161 --- README.md | 20 ++-- benchmark/psychichttp/src/main.cpp | 8 +- benchmark/psychichttps/src/main.cpp | 6 +- examples/arduino/arduino.ino | 24 ++--- .../arduino/arduino_captive_portal/README.md | 2 +- .../arduino_captive_portal.ino | 2 +- examples/arduino/arduino_ota/arduino_ota.ino | 8 +- examples/esp-idf/main/main.cpp | 24 ++--- examples/platformio/src/main.cpp | 102 ++++++++---------- examples/websockets/src/main.cpp | 2 +- src/PsychicCore.h | 8 +- src/PsychicEndpoint.cpp | 53 +++++---- src/PsychicEndpoint.h | 11 +- src/PsychicEventSource.cpp | 9 +- src/PsychicEventSource.h | 11 +- src/PsychicHandler.cpp | 49 ++++----- src/PsychicHandler.h | 28 ++--- src/PsychicHttp.h | 1 + src/PsychicHttpServer.cpp | 80 +++++++------- src/PsychicHttpServer.h | 15 +-- src/PsychicJson.cpp | 57 +++++----- src/PsychicJson.h | 13 +-- src/PsychicMiddleware.cpp | 16 ++- src/PsychicMiddleware.h | 20 ++-- src/PsychicMiddlewareChain.cpp | 69 ++++++------ src/PsychicMiddlewareChain.h | 15 ++- src/PsychicMiddlewares.h | 44 ++++++++ src/PsychicRequest.cpp | 93 ---------------- src/PsychicRequest.h | 13 --- src/PsychicResponse.cpp | 67 +++++++++++- src/PsychicResponse.h | 15 +++ src/PsychicStaticFileHander.cpp | 6 +- src/PsychicStaticFileHandler.h | 2 +- src/PsychicUploadHandler.cpp | 10 +- src/PsychicUploadHandler.h | 2 +- src/PsychicWebHandler.cpp | 8 +- src/PsychicWebHandler.h | 2 +- src/PsychicWebSocket.cpp | 2 +- src/PsychicWebSocket.h | 2 +- 39 files changed, 457 insertions(+), 462 deletions(-) create mode 100644 src/PsychicMiddlewares.h diff --git a/README.md b/README.md index ee71b77..5997092 100644 --- a/README.md +++ b/README.md @@ -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) @@ -195,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"); @@ -209,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: @@ -223,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()); }); ``` @@ -291,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 @@ -349,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 @@ -371,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. @@ -429,7 +429,7 @@ Here is a basic example of using WebSockets: 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) { @@ -449,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 diff --git a/benchmark/psychichttp/src/main.cpp b/benchmark/psychichttp/src/main.cpp index 93b40eb..6f91624 100644 --- a/benchmark/psychichttp/src/main.cpp +++ b/benchmark/psychichttp/src/main.cpp @@ -170,7 +170,7 @@ void setup() } // our index - server.on("/", HTTP_GET, [](PsychicRequest* request) { return request->reply(200, "text/html", htmlContent); }); + server.on("/", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->send(200, "text/html", htmlContent); }); // serve static files from LittleFS/www on / server.serveStatic("/", LittleFS, "/www/"); @@ -180,7 +180,7 @@ void setup() // client->sendMessage("Hello!"); }); websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) { - request->reply(frame); + response->send(frame); return ESP_OK; }); server.on("/ws", &websocketHandler); @@ -189,7 +189,7 @@ void setup() server.on("/events", &eventSource); // 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 JsonDocument output; output["msg"] = "status"; @@ -206,7 +206,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()); }); server.begin(); } diff --git a/benchmark/psychichttps/src/main.cpp b/benchmark/psychichttps/src/main.cpp index 000f84e..9915c8e 100644 --- a/benchmark/psychichttps/src/main.cpp +++ b/benchmark/psychichttps/src/main.cpp @@ -196,7 +196,7 @@ void setup() // our index server.on("/", HTTP_GET, [](PsychicRequest* request) - { return request->reply(200, "text/html", htmlContent); }); + { return response->send(200, "text/html", htmlContent); }); // serve static files from LittleFS/www on / server.serveStatic("/", LittleFS, "/www/"); @@ -204,7 +204,7 @@ void setup() // a websocket echo server websocketHandler.onFrame([](PsychicWebSocketRequest* request, httpd_ws_frame* frame) { - request->reply(frame); + response->send(frame); return ESP_OK; }); server.on("/ws", &websocketHandler); @@ -227,7 +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/examples/arduino/arduino.ino b/examples/arduino/arduino.ino index c8c5b5b..03cd955 100644 --- a/examples/arduino/arduino.ino +++ b/examples/arduino/arduino.ino @@ -220,11 +220,11 @@ void setup() //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. @@ -267,14 +267,14 @@ 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) { 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 @@ -296,7 +296,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()); }); //how to redirect a request @@ -310,7 +310,7 @@ void setup() { 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 @@ -318,7 +318,7 @@ void setup() { 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 @@ -349,13 +349,13 @@ 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()); }); //you can set up a custom 404 handler. server.onNotFound([](PsychicRequest *request) { - 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 @@ -394,7 +394,7 @@ void setup() 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 @@ -443,7 +443,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 @@ -456,7 +456,7 @@ void setup() }); 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->localIP().toString()); diff --git a/examples/arduino/arduino_captive_portal/README.md b/examples/arduino/arduino_captive_portal/README.md index f965eda..f9bbaae 100644 --- a/examples/arduino/arduino_captive_portal/README.md +++ b/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/examples/arduino/arduino_captive_portal/arduino_captive_portal.ino b/examples/arduino/arduino_captive_portal/arduino_captive_portal.ino index 13dc9c8..4b59f7e 100644 --- a/examples/arduino/arduino_captive_portal/arduino_captive_portal.ino +++ b/examples/arduino/arduino_captive_portal/arduino_captive_portal.ino @@ -32,7 +32,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/examples/arduino/arduino_ota/arduino_ota.ino b/examples/arduino/arduino_ota/arduino_ota.ino index c22c80e..6f88d14 100644 --- a/examples/arduino/arduino_ota/arduino_ota.ino +++ b/examples/arduino/arduino_ota/arduino_ota.ino @@ -109,7 +109,7 @@ 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"); + return response->send(404, "text/html", "Custom 404 Handler"); }); // OTA @@ -182,13 +182,13 @@ void setup() 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 }); @@ -203,7 +203,7 @@ void setup() 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/examples/esp-idf/main/main.cpp b/examples/esp-idf/main/main.cpp index 5b7843b..20e41f4 100644 --- a/examples/esp-idf/main/main.cpp +++ b/examples/esp-idf/main/main.cpp @@ -223,11 +223,11 @@ void setup() // 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. @@ -278,13 +278,13 @@ 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) { 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) @@ -305,7 +305,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()); }); // how to redirect a request server.on("/redirect", HTTP_GET, [](PsychicRequest* request) @@ -316,14 +316,14 @@ void setup() { 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) { 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) @@ -352,11 +352,11 @@ 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()); }); // you can set up a custom 404 handler. server.onNotFound([](PsychicRequest* request) - { 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 PsychicUploadHandler* uploadHandler = new PsychicUploadHandler(); @@ -394,7 +394,7 @@ void setup() 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 server.on("/upload/*", HTTP_POST, uploadHandler); @@ -442,7 +442,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 server.on("/multipart", HTTP_POST, multipartHandler); @@ -455,7 +455,7 @@ void setup() 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->localIP().toString().c_str()); }); server.on("/ws", &websocketHandler); diff --git a/examples/platformio/src/main.cpp b/examples/platformio/src/main.cpp index 67b3f71..d19e0af 100644 --- a/examples/platformio/src/main.cpp +++ b/examples/platformio/src/main.cpp @@ -241,18 +241,6 @@ bool setupSDCard() } #endif -bool PERMISSIVE_CORS(PsychicRequest* request) -{ - if (request->hasHeader("Origin")) { - request->addResponseHeader("Access-Control-Allow-Origin", "*"); - request->addResponseHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); - request->addResponseHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept"); - request->addResponseHeader("Access-Control-Max-Age", "86400"); - } - - return true; -} - void setup() { esp_log_level_set(PH_TAG, ESP_LOG_DEBUG); @@ -328,7 +316,7 @@ void setup() // 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) { + redirectServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) { String url = "https://" + request->host() + request->url(); return request->redirect(url.c_str()); }); } @@ -344,11 +332,11 @@ void setup() // curl -i http://psychic.local/ PsychicStaticFileHandler* handler = server.serveStatic("/", LittleFS, "/www/"); handler->setCacheControl("max-age=60"); - handler->setFilter(ON_STA_FILTER); + handler->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. @@ -373,11 +361,11 @@ void setup() // 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) { + server.on("/api", HTTP_POST, [](PsychicRequest* request, PsychicResponse* resp, JsonVariant& json) { JsonObject input = json.as(); // create our response json - PsychicJsonResponse response = PsychicJsonResponse(request); + PsychicJsonResponse response(resp); JsonObject output = response.getRoot(); output["msg"] = "status"; @@ -396,15 +384,15 @@ void setup() // ip - get info about the client // curl -i http://psychic.local/ip - server.on("/ip", HTTP_GET, [](PsychicRequest* request) { + server.on("/ip", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { String output = "Your IP is: " + request->client()->remoteIP().toString(); - 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->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()); }); @@ -413,9 +401,9 @@ void setup() // 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) { + server.on("/api", HTTP_GET, [](PsychicRequest* request, PsychicResponse* resp) { // create our response json - PsychicJsonResponse response = PsychicJsonResponse(request); + PsychicJsonResponse response = PsychicJsonResponse(resp); JsonObject output = response.getRoot(); output["msg"] = "status"; @@ -434,9 +422,9 @@ void setup() // curl -i -X GET 'http://psychic.local/any' // curl -i -X POST 'http://psychic.local/any' - server.on("/any", HTTP_ANY, [](PsychicRequest* request) { + server.on("/any", HTTP_ANY, [](PsychicRequest* request, PsychicResponse* resp) { // create our response json - PsychicJsonResponse response = PsychicJsonResponse(request); + PsychicJsonResponse response = PsychicJsonResponse(resp); JsonObject output = response.getRoot(); output["msg"] = "status"; @@ -448,15 +436,15 @@ void setup() }); // curl -i 'http://psychic.local/simple' - server.on("/simple", HTTP_GET, [](PsychicRequest* request) { - return request->reply("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) { + server.on("^/regex/([\\d]+)/?$", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { // look up our regex matches std::smatch matches; if (request->getRegexMatches(matches)) { @@ -465,55 +453,53 @@ void setup() output += "Matched URI: " + String(matches.str(0).c_str()) + "
\n"; output += "Match 1: " + String(matches.str(1).c_str()) + "
\n"; - return request->reply(output.c_str()); + return response->send(output.c_str()); } else - return request->reply("No regex match."); + 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) { - PsychicJsonResponse response = PsychicJsonResponse(request); + server.on("/json", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { + PsychicJsonResponse jsonResponse = PsychicJsonResponse(response); char key[16]; char value[32]; - JsonObject root = response.getRoot(); + 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(); + return jsonResponse.send(); }); // how to redirect a request // curl -i http://psychic.local/redirect - server.on("/redirect", HTTP_GET, [](PsychicRequest* request) { return request->redirect("/alien.png"); }); + server.on("/redirect", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { return response->redirect("/alien.png"); }); // how to do basic auth // curl -i --user admin:admin http://psychic.local/auth-basic - server.on("/auth-basic", HTTP_GET, [](PsychicRequest* request) { + server.on("/auth-basic", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { 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 // curl -i --user admin:admin http://psychic.local/auth-digest - server.on("/auth-digest", HTTP_GET, [](PsychicRequest* request) { + server.on("/auth-digest", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { 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 // curl -i -b cookie.txt -c cookie.txt http://psychic.local/cookies - server.on("/cookies", HTTP_GET, [](PsychicRequest* request) { - PsychicResponse response(request); - + server.on("/cookies", HTTP_GET, [](PsychicRequest* request, PsychicResponse* response) { int counter = 0; char cookie[14]; size_t size = sizeof(cookie); @@ -524,25 +510,25 @@ void setup() } sprintf(cookie, "%d", 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 // curl -F "param1=value1" -F "param2=value2" -X POST http://psychic.local/post - 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. // 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"); }); // handle a very basic upload as post body PsychicUploadHandler* uploadHandler = new PsychicUploadHandler(); @@ -575,11 +561,11 @@ 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 @@ -617,7 +603,7 @@ void setup() }); // gets called after upload has been handled - multipartHandler->onRequest([](PsychicRequest* request) { + multipartHandler->onRequest([](PsychicRequest* request, PsychicResponse* response) { String output; if (request->hasParam("file_upload")) { PsychicWebParameter* file = request->getParam("file_upload"); @@ -632,7 +618,7 @@ void setup() if (request->hasParam("param2")) 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 @@ -643,14 +629,14 @@ void setup() // form only multipart handler // curl -F "param1=multi" -F "param2=part" http://psychic.local/multipart-data PsychicUploadHandler* multipartFormHandler = new PsychicUploadHandler(); - multipartFormHandler->onRequest([](PsychicRequest* request) { + 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 request->reply(output.c_str()); + return response->send(output.c_str()); }); server.on("/multipart-data", HTTP_POST, multipartFormHandler); @@ -681,22 +667,22 @@ void setup() // 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) { + server.on("/post-filter", HTTP_POST, [](PsychicRequest* request, PsychicResponse* response) { String output; output += "Secret: " + request->getParam("secret")->value() + "
\n"; - return request->reply(output.c_str()); + return response->send(output.c_str()); }) - ->setFilter([](PsychicRequest* request) { + ->addFilter([](PsychicRequest* request) { request->loadParams(); return request->hasParam("secret"); }); // this will send CORS headers on every request that contains the Origin: header - server.setFilter(PERMISSIVE_CORS); + server.addMiddleware(new PermissiveCorsMiddleware()); // this will respond to CORS requests (note: the global server filter will automatically add the CORS headers) - server.on("*", HTTP_OPTIONS, [](PsychicRequest* request) { return request->reply(200); }); + server.on("*", HTTP_OPTIONS, [](PsychicRequest* request, PsychicResponse* response) { return response->send(200); }); server.begin(); } diff --git a/examples/websockets/src/main.cpp b/examples/websockets/src/main.cpp index be14621..63cc778 100644 --- a/examples/websockets/src/main.cpp +++ b/examples/websockets/src/main.cpp @@ -179,7 +179,7 @@ void setup() //send a throttle message if we're full if (!uxQueueSpacesAvailable(wsMessages)) - return request->reply("Queue Full"); + return response->send("Queue Full"); return ESP_OK; }); websocketHandler.onClose([](PsychicWebSocketClient* client) diff --git a/src/PsychicCore.h b/src/PsychicCore.h index fd6e5cb..6109eb3 100644 --- a/src/PsychicCore.h +++ b/src/PsychicCore.h @@ -49,20 +49,20 @@ class PsychicRequest; class PsychicResponse; class PsychicWebSocketRequest; class PsychicClient; -class PsychicMiddlewareChain; // filter function definition typedef std::function PsychicRequestFilterFunction; // middleware function definition -typedef std::function PsychicMiddlewareFunction; +typedef std::function PsychicMiddlewareCallback; +typedef std::function PsychicMiddlewareFunction; // client connect callback typedef std::function PsychicClientCallback; // callback definitions -typedef std::function PsychicHttpRequestCallback; -typedef std::function PsychicJsonRequestCallback; +typedef std::function PsychicHttpRequestCallback; +typedef std::function PsychicJsonRequestCallback; typedef std::function PsychicUploadCallback; struct HTTPHeader { diff --git a/src/PsychicEndpoint.cpp b/src/PsychicEndpoint.cpp index ffd0be9..4e9c8dd 100644 --- a/src/PsychicEndpoint.cpp +++ b/src/PsychicEndpoint.cpp @@ -55,24 +55,18 @@ esp_err_t PsychicEndpoint::requestCallback(httpd_req_t* req) #endif PsychicEndpoint* self = (PsychicEndpoint*)req->user_ctx; - PsychicHandler* handler = self->handler(); PsychicRequest request(self->_server, req); + PsychicResponse response(&request); - // 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, &response); - // pass it to our handler - return handler->handleRequest(&request); - } - // pass it to our generic handlers - else - return PsychicHttpServer::requestHandler(req); - } else - return request.reply(500, "text/html", "No handler registered."); + if (err == HTTPD_404_NOT_FOUND) + return PsychicHttpServer::requestHandler(req); + + if (err == ESP_ERR_HTTPD_INVALID_REQ) + return response.error(HTTPD_500_INTERNAL_SERVER_ERROR, "No handler registered."); + + return err; } bool PsychicEndpoint::matches(const char* uri) @@ -114,14 +108,33 @@ void PsychicEndpoint::setURIMatchFunction(httpd_uri_match_func_t match_fn) _uri_match_fn = match_fn; } -PsychicEndpoint* PsychicEndpoint::setFilter(PsychicRequestFilterFunction fn) +PsychicEndpoint* PsychicEndpoint::addFilter(PsychicRequestFilterFunction fn) +{ + _handler->addFilter(fn); + return this; +} + +PsychicEndpoint* PsychicEndpoint::addMiddleware(PsychicMiddleware* middleware) { - _handler->setFilter(fn); + _handler->addMiddleware(middleware); return this; } -PsychicEndpoint* PsychicEndpoint::setAuthentication(const char* username, const char* password, HTTPAuthMethod method, const char* realm, const char* authFailMsg) +PsychicEndpoint* PsychicEndpoint::addMiddleware(PsychicMiddlewareFunction fn) { - _handler->setAuthentication(username, password, method, realm, authFailMsg); + _handler->addMiddleware(fn); return this; -}; \ No newline at end of file +} + +bool PsychicEndpoint::removeMiddleware(PsychicMiddleware* middleware) +{ + return _handler->removeMiddleware(middleware); +} + +esp_err_t PsychicEndpoint::process(PsychicRequest* request, PsychicResponse* response) +{ + if (_handler == NULL) + return ESP_ERR_HTTPD_INVALID_REQ; + + return _handler->process(request, response); +} diff --git a/src/PsychicEndpoint.h b/src/PsychicEndpoint.h index b729eed..6811621 100644 --- a/src/PsychicEndpoint.h +++ b/src/PsychicEndpoint.h @@ -4,6 +4,7 @@ #include "PsychicCore.h" class PsychicHandler; +class PsychicMiddleware; #ifdef ENABLE_ASYNC #include "async_worker.h" @@ -32,8 +33,14 @@ class PsychicEndpoint bool matches(const char* uri); - PsychicEndpoint* setFilter(PsychicRequestFilterFunction fn); - PsychicEndpoint* setAuthentication(const char* username, const char* password, HTTPAuthMethod method = BASIC_AUTH, const char* realm = "", const char* authFailMsg = ""); + // called to process this endpoint with its middleware chain + esp_err_t process(PsychicRequest* request, PsychicResponse* response); + + PsychicEndpoint* addFilter(PsychicRequestFilterFunction fn); + + PsychicEndpoint* addMiddleware(PsychicMiddleware* middleware); + PsychicEndpoint* addMiddleware(PsychicMiddlewareFunction fn); + bool removeMiddleware(PsychicMiddleware* middleware); String uri(); diff --git a/src/PsychicEventSource.cpp b/src/PsychicEventSource.cpp index b7b0441..d6b9e83 100644 --- a/src/PsychicEventSource.cpp +++ b/src/PsychicEventSource.cpp @@ -50,10 +50,10 @@ 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); + PsychicEventSourceResponse response(resp); esp_err_t err = response.send(); // lookup our client @@ -234,8 +234,7 @@ void PsychicEventSourceClient::_sendEventSentCallback(esp_err_t err, int socket, // PsychicEventSourceResponse /*****************************************/ -PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicRequest* request) - : PsychicResponse(request) +PsychicEventSourceResponse::PsychicEventSourceResponse(PsychicResponse* response) : _response(response) { } @@ -258,7 +257,7 @@ esp_err_t PsychicEventSourceResponse::send() int result; do { - result = httpd_send(_request->request(), out.c_str(), out.length()); + result = httpd_send(_response->request(), out.c_str(), out.length()); } while (result == HTTPD_SOCK_ERR_TIMEOUT); if (result < 0) diff --git a/src/PsychicEventSource.h b/src/PsychicEventSource.h index 0f8a67f..b55cfb6 100644 --- a/src/PsychicEventSource.h +++ b/src/PsychicEventSource.h @@ -80,16 +80,19 @@ class PsychicEventSource : public PsychicHandler 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); }; -class PsychicEventSourceResponse : public PsychicResponse +class PsychicEventSourceResponse { + protected: + PsychicResponse* _response; + 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); diff --git a/src/PsychicHandler.cpp b/src/PsychicHandler.cpp index 001c254..f1c4eab 100644 --- a/src/PsychicHandler.cpp +++ b/src/PsychicHandler.cpp @@ -1,24 +1,21 @@ #include "PsychicHandler.h" PsychicHandler::PsychicHandler() : _server(NULL), - _username(""), - _password(""), - _method(DIGEST_AUTH), - _realm(""), - _authFailMsg(""), + _chain(new PsychicMiddlewareChain()), _subprotocol("") { } PsychicHandler::~PsychicHandler() { + delete _chain; // actual PsychicClient deletion handled by PsychicServer // for (PsychicClient *client : _clients) // delete(client); _clients.clear(); } -PsychicHandler* PsychicHandler::setFilter(PsychicRequestFilterFunction fn) +PsychicHandler* PsychicHandler::addFilter(PsychicRequestFilterFunction fn) { _filters.push_back(fn); return this; @@ -44,26 +41,6 @@ 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) { PsychicClient* c = PsychicHandler::getClient(client); @@ -125,7 +102,7 @@ const std::list& PsychicHandler::getClientList() return _clients; } -PsychicHandler* PsychicHandler::addMiddleware(PsychicMiddleware *middleware) +PsychicHandler* PsychicHandler::addMiddleware(PsychicMiddleware* middleware) { _chain->add(middleware); return this; @@ -133,12 +110,22 @@ PsychicHandler* PsychicHandler::addMiddleware(PsychicMiddleware *middleware) PsychicHandler* PsychicHandler::addMiddleware(PsychicMiddlewareFunction fn) { - PsychicMiddleware *mw = new PsychicMiddleware(fn); - _chain->add(mw); + _chain->add(fn); return this; } -bool PsychicHandler::runMiddleware(PsychicRequest* request, PsychicResponse* response) +bool PsychicHandler::removeMiddleware(PsychicMiddleware* middleware) { - return _chain->run(request, response); + return _chain->remove(middleware); +} + +esp_err_t PsychicHandler::process(PsychicRequest* request, PsychicResponse* response) +{ + if (!filter(request)) + return HTTPD_404_NOT_FOUND; + + if (!canHandle(request)) + return HTTPD_404_NOT_FOUND; + + return _chain->run(request, response, std::bind(&PsychicHandler::handleRequest, this, std::placeholders::_1, std::placeholders::_2)); } \ No newline at end of file diff --git a/src/PsychicHandler.h b/src/PsychicHandler.h index c3e4363..702462c 100644 --- a/src/PsychicHandler.h +++ b/src/PsychicHandler.h @@ -3,11 +3,11 @@ #include "PsychicCore.h" #include "PsychicRequest.h" -#include "PsychicMiddleware.h" -#include "PsychicMiddlewareChain.h" class PsychicEndpoint; class PsychicHttpServer; +class PsychicMiddleware; +class PsychicMiddlewareChain; /* * HANDLER :: Can be attached to any endpoint or as a generic request handler. @@ -18,15 +18,9 @@ class PsychicHandler friend PsychicEndpoint; protected: - std::list _filters; - PsychicMiddlewareChain* _chain; PsychicHttpServer* _server; - - String _username; - String _password; - HTTPAuthMethod _method; - String _realm; - String _authFailMsg; + PsychicMiddlewareChain* _chain; + std::list _filters; String _subprotocol; @@ -36,10 +30,6 @@ class PsychicHandler PsychicHandler(); virtual ~PsychicHandler(); - 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); @@ -59,16 +49,20 @@ class PsychicHandler int count() { return _clients.size(); }; const std::list& getClientList(); - PsychicHandler* setFilter(PsychicRequestFilterFunction fn); + // called to process this handler with its middleware chain and filers + esp_err_t process(PsychicRequest* request, PsychicResponse* response); + + //bool filter(PsychicRequest* request); + PsychicHandler* addFilter(PsychicRequestFilterFunction fn); bool filter(PsychicRequest* request); PsychicHandler* addMiddleware(PsychicMiddleware* middleware); PsychicHandler* addMiddleware(PsychicMiddlewareFunction fn); - bool runMiddleware(PsychicRequest* request, PsychicResponse* response); + bool removeMiddleware(PsychicMiddleware *middleware); // derived classes must implement these functions virtual bool canHandle(PsychicRequest* request) { return true; }; - virtual esp_err_t handleRequest(PsychicRequest* request) = 0; + virtual esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) { return HTTPD_404_NOT_FOUND; }; }; #endif \ No newline at end of file diff --git a/src/PsychicHttp.h b/src/PsychicHttp.h index 91f80ff..dca1e5a 100644 --- a/src/PsychicHttp.h +++ b/src/PsychicHttp.h @@ -19,6 +19,7 @@ #include "PsychicUploadHandler.h" #include "PsychicWebSocket.h" #include +#include "PsychicMiddlewares.h" #ifdef ENABLE_ASYNC #include "async_worker.h" diff --git a/src/PsychicHttpServer.cpp b/src/PsychicHttpServer.cpp index 71b5834..3fc1260 100644 --- a/src/PsychicHttpServer.cpp +++ b/src/PsychicHttpServer.cpp @@ -11,7 +11,8 @@ #endif PsychicHttpServer::PsychicHttpServer(uint16_t port) : _onOpen(NULL), - _onClose(NULL) + _onClose(NULL), + _chain(new PsychicMiddlewareChain()) { maxRequestBodySize = MAX_REQUEST_BODY_SIZE; maxUploadSize = MAX_UPLOAD_SIZE; @@ -65,6 +66,7 @@ PsychicHttpServer::~PsychicHttpServer() _rewrites.clear(); delete defaultEndpoint; + delete _chain; } void PsychicHttpServer::destroy(void* ctx) @@ -379,14 +381,14 @@ bool PsychicHttpServer::removeEndpoint(PsychicEndpoint* endpoint) return true; } -PsychicHttpServer* PsychicHttpServer::setFilter(PsychicRequestFilterFunction fn) +PsychicHttpServer* PsychicHttpServer::addFilter(PsychicRequestFilterFunction fn) { _filters.push_back(fn); return this; } -bool PsychicHttpServer::filter(PsychicRequest* request) +bool PsychicHttpServer::_filter(PsychicRequest* request) { // run through our filter chain. for (auto& filter : _filters) { @@ -405,14 +407,13 @@ PsychicHttpServer* PsychicHttpServer::addMiddleware(PsychicMiddleware *middlewar PsychicHttpServer* PsychicHttpServer::addMiddleware(PsychicMiddlewareFunction fn) { - PsychicMiddleware *mw = new PsychicMiddleware(fn); - _chain->add(mw); + _chain->add(fn); return this; } -bool PsychicHttpServer::runMiddleware(PsychicRequest* request, PsychicResponse* response) +bool PsychicHttpServer::removeMiddleware(PsychicMiddleware* middleware) { - return _chain->run(request, response); + return _chain->remove(middleware); } void PsychicHttpServer::onNotFound(PsychicHttpRequestCallback fn) @@ -445,64 +446,65 @@ esp_err_t PsychicHttpServer::requestHandler(httpd_req_t* req) server->_rewriteRequest(&request); // run it through our global server filter list - if (!server->filter(&request)) - return request.reply(400); + if(!server->_filter(&request)) + return response.send(400); - // run it through our global server middleware. - // false means the chain didnt complete and a response was sent. - if (!server->runMiddleware(&request, &response)) - return ESP_OK; + // then runs the request through the filter chain + esp_err_t ret = server->_chain->run(&request, &response, std::bind(&PsychicHttpServer::_process, server, std::placeholders::_1, std::placeholders::_2)); + + if (ret == HTTPD_404_NOT_FOUND) { + return PsychicHttpServer::notFoundHandler(req, HTTPD_404_NOT_FOUND); + } + return ret; +} + +esp_err_t PsychicHttpServer::_process(PsychicRequest* request, PsychicResponse* response) +{ // loop through our endpoints and see if anyone wants it. - for (auto* endpoint : server->_endpoints) { - if (endpoint->matches(request.uri().c_str())) { - if (endpoint->_method == request.method() || endpoint->_method == HTTP_ANY) { - request.setEndpoint(endpoint); + for (auto* endpoint : _endpoints) { + if (endpoint->matches(request->uri().c_str())) { + if (endpoint->_method == request->method() || endpoint->_method == HTTP_ANY) { + request->setEndpoint(endpoint); PsychicHandler* handler = endpoint->handler(); - if (handler->filter(&request)) { - if (handler->runMiddleware(&request, &response)) - return handler->handleRequest(&request); - else - return ESP_OK; - } + return handler->process(request, response); } } } // loop through our global handlers and see if anyone wants it - for (auto* handler : server->_handlers) { - if (handler->filter(&request)) { - if (handler->runMiddleware(&request, &response)) - return handler->handleRequest(&request); - else - return ESP_OK; - } + for (auto* handler : _handlers) { + esp_err_t ret = handler->process(request, response); + if (ret != HTTPD_404_NOT_FOUND) + return ret; } - //if nothing hits, then try our 404 handler. - return PsychicHttpServer::notFoundHandler(req, HTTPD_404_NOT_FOUND); + 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); + PsychicResponse response(&request); // pull up our default handler / endpoint PsychicHandler* handler = server->defaultEndpoint->handler(); - if (handler->filter(&request) && handler->canHandle(&request)) - return handler->handleRequest(&request); + if (!handler) + return response.send(404); + + esp_err_t ret = handler->process(&request, &response); + if (ret != HTTPD_404_NOT_FOUND) + return ret; // not sure how we got this far. - return ESP_ERR_HTTPD_INVALID_REQ; + return response.send(404); } -esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest* request) +esp_err_t PsychicHttpServer::defaultNotFoundHandler(PsychicRequest* request, PsychicResponse* response) { - request->reply(404, "text/html", "That URI does not exist."); - - return ESP_OK; + return response->send(404, "text/html", "That URI does not exist."); } void PsychicHttpServer::onOpen(PsychicClientCallback handler) diff --git a/src/PsychicHttpServer.h b/src/PsychicHttpServer.h index ed08f45..72155d7 100644 --- a/src/PsychicHttpServer.h +++ b/src/PsychicHttpServer.h @@ -4,6 +4,8 @@ #include "PsychicClient.h" #include "PsychicCore.h" #include "PsychicHandler.h" +#include "PsychicMiddleware.h" +#include "PsychicMiddlewareChain.h" #include "PsychicRewrite.h" #ifdef PSY_ENABLE_REGEX @@ -17,8 +19,6 @@ enum PsychicHttpMethod { class PsychicEndpoint; class PsychicHandler; class PsychicStaticFileHandler; -class PsychicMiddleware; -class PsychicMiddlewareChain; class PsychicHttpServer { @@ -29,10 +29,10 @@ class PsychicHttpServer std::list _clients; std::list _rewrites; std::list _filters; - PsychicMiddlewareChain* _chain; PsychicClientCallback _onOpen; PsychicClientCallback _onClose; + PsychicMiddlewareChain* _chain; esp_err_t _start(); virtual esp_err_t _startServer(); @@ -41,6 +41,8 @@ class PsychicHttpServer httpd_uri_match_func_t _uri_match_fn = nullptr; bool _rewriteRequest(PsychicRequest* request); + esp_err_t _process(PsychicRequest* request, PsychicResponse* response); + bool _filter(PsychicRequest* request); public: PsychicHttpServer(uint16_t port = 80); @@ -101,16 +103,15 @@ class PsychicHttpServer bool removeEndpoint(const char* uri, int method); bool removeEndpoint(PsychicEndpoint* endpoint); - PsychicHttpServer* setFilter(PsychicRequestFilterFunction fn); - bool filter(PsychicRequest* request); + PsychicHttpServer* addFilter(PsychicRequestFilterFunction fn); PsychicHttpServer* addMiddleware(PsychicMiddleware* middleware); PsychicHttpServer* addMiddleware(PsychicMiddlewareFunction fn); - bool runMiddleware(PsychicRequest* request, PsychicResponse* response); + bool 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); + 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); diff --git a/src/PsychicJson.cpp b/src/PsychicJson.cpp index 2f58607..cc68bff 100644 --- a/src/PsychicJson.cpp +++ b/src/PsychicJson.cpp @@ -1,19 +1,19 @@ #include "PsychicJson.h" #ifdef ARDUINOJSON_6_COMPATIBILITY -PsychicJsonResponse::PsychicJsonResponse(PsychicRequest* request, bool isArray, size_t maxJsonBufferSize) : PsychicResponse(request), - _jsonBuffer(maxJsonBufferSize) +PsychicJsonResponse::PsychicJsonResponse(PsychicResponse* response, bool isArray, size_t maxJsonBufferSize) : __response(response), + _jsonBuffer(maxJsonBufferSize) { - setContentType(JSON_MIMETYPE); + response->setContentType(JSON_MIMETYPE); if (isArray) _root = _jsonBuffer.createNestedArray(); else _root = _jsonBuffer.createNestedObject(); } #else -PsychicJsonResponse::PsychicJsonResponse(PsychicRequest* request, bool isArray) : PsychicResponse(request) +PsychicJsonResponse::PsychicJsonResponse(PsychicResponse* response, bool isArray) : _response(response) { - setContentType(JSON_MIMETYPE); + _response->setContentType(JSON_MIMETYPE); if (isArray) _root = _jsonBuffer.add(); else @@ -45,29 +45,24 @@ esp_err_t PsychicJsonResponse::send() buffer_size = JSON_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; + if (buffer == NULL) { + return _response->error(HTTPD_500_INTERNAL_SERVER_ERROR, "Unable to allocate memory."); } // send it in one shot or no? - if (length < JSON_BUFFER_SIZE) - { + if (length < JSON_BUFFER_SIZE) { serializeJson(_root, buffer, buffer_size); - this->setContent((uint8_t*)buffer, length); - this->setContentType(JSON_MIMETYPE); + _response->setContent((uint8_t*)buffer, length); + _response->setContentType(JSON_MIMETYPE); - err = PsychicResponse::send(); - } - else - { + err = _response->send(); + } else { // helper class that acts as a stream to print chunked responses - ChunkPrinter dest(this, (uint8_t*)buffer, buffer_size); + ChunkPrinter dest(_response, (uint8_t*)buffer, buffer_size); // keep our headers - this->sendHeaders(); + _response->sendHeaders(); serializeJson(_root, dest); @@ -75,7 +70,7 @@ esp_err_t PsychicJsonResponse::send() dest.flush(); // done with our chunked response too - err = this->finishChunking(); + err = _response->finishChunking(); } // let the buffer go @@ -86,14 +81,14 @@ esp_err_t PsychicJsonResponse::send() #ifdef ARDUINOJSON_6_COMPATIBILITY PsychicJsonHandler::PsychicJsonHandler(size_t maxJsonBufferSize) : _onRequest(NULL), - _maxJsonBufferSize(maxJsonBufferSize){}; + _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) { @@ -105,31 +100,29 @@ void PsychicJsonHandler::onRequest(PsychicJsonRequestCallback fn) _onRequest = fn; } -esp_err_t PsychicJsonHandler::handleRequest(PsychicRequest* request) +esp_err_t PsychicJsonHandler::handleRequest(PsychicRequest* request, PsychicResponse* response) { // process basic stuff - PsychicWebHandler::handleRequest(request); + PsychicWebHandler::handleRequest(request, response); - if (_onRequest) - { + if (_onRequest) { #ifdef ARDUINOJSON_6_COMPATIBILITY DynamicJsonDocument jsonBuffer(this->_maxJsonBufferSize); DeserializationError error = deserializeJson(jsonBuffer, request->body()); if (error) - return request->reply(400); + return response->send(400); JsonVariant json = jsonBuffer.as(); #else JsonDocument jsonBuffer; DeserializationError error = deserializeJson(jsonBuffer, request->body()); if (error) - return request->reply(400); + return response->send(400); JsonVariant json = jsonBuffer.as(); #endif - return _onRequest(request, json); - } - else - return request->reply(500); + return _onRequest(request, response, json); + } else + return response->send(500); } \ No newline at end of file diff --git a/src/PsychicJson.h b/src/PsychicJson.h index 5698909..ee2455c 100644 --- a/src/PsychicJson.h +++ b/src/PsychicJson.h @@ -30,7 +30,7 @@ constexpr const char* JSON_MIMETYPE = "application/json"; * Json Response * */ -class PsychicJsonResponse : public PsychicResponse +class PsychicJsonResponse { protected: #ifdef ARDUINOJSON_5_COMPATIBILITY @@ -41,16 +41,17 @@ class PsychicJsonResponse : public PsychicResponse JsonDocument _jsonBuffer; #endif + PsychicResponse* _response; JsonVariant _root; size_t _contentLength; public: #ifdef ARDUINOJSON_5_COMPATIBILITY - PsychicJsonResponse(PsychicRequest* request, bool isArray = false); + PsychicJsonResponse(PsychicResponse* response, bool isArray = false); #elif ARDUINOJSON_VERSION_MAJOR == 6 - PsychicJsonResponse(PsychicRequest* request, bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); + PsychicJsonResponse(PsychicResponse* response, bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE); #else - PsychicJsonResponse(PsychicRequest* request, bool isArray = false); + PsychicJsonResponse(PsychicResponse* response, bool isArray = false); #endif ~PsychicJsonResponse() @@ -60,7 +61,7 @@ class PsychicJsonResponse : public PsychicResponse JsonVariant& getRoot(); size_t getLength(); - virtual esp_err_t send() override; + esp_err_t send(); }; class PsychicJsonHandler : public PsychicWebHandler @@ -84,7 +85,7 @@ class PsychicJsonHandler : public PsychicWebHandler #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/src/PsychicMiddleware.cpp b/src/PsychicMiddleware.cpp index 18ff663..7aeff91 100644 --- a/src/PsychicMiddleware.cpp +++ b/src/PsychicMiddleware.cpp @@ -3,15 +3,11 @@ #include "PsychicRequest.h" #include "PsychicResponse.h" -PsychicMiddleware::PsychicMiddleware(PsychicMiddlewareFunction callback) : - _callback(callback) - { - } - -PsychicMiddleware::~PsychicMiddleware() {} - -void PsychicMiddleware::run(PsychicMiddlewareChain *chain, PsychicRequest *request, PsychicResponse *response) +PsychicMiddlewareClosure::PsychicMiddlewareClosure(PsychicMiddlewareFunction fn) : _fn(fn) +{ + assert(_fn); +} +esp_err_t PsychicMiddlewareClosure::run(PsychicMiddlewareCallback next, PsychicRequest* request, PsychicResponse* response) { - if (_callback) - _callback(chain, request, response); + return _fn(next, request, response); } \ No newline at end of file diff --git a/src/PsychicMiddleware.h b/src/PsychicMiddleware.h index 46f9815..2af5520 100644 --- a/src/PsychicMiddleware.h +++ b/src/PsychicMiddleware.h @@ -4,22 +4,26 @@ #include "PsychicCore.h" #include "PsychicRequest.h" #include "PsychicResponse.h" -#include "PsychicMiddlewareChain.h" /* * PsychicMiddleware :: fancy callback wrapper for handling requests and responses. * */ -class PsychicMiddleware { +class PsychicMiddleware +{ public: - //void *_context; - String _name; - PsychicMiddlewareFunction _callback; + virtual ~PsychicMiddleware() {} + virtual esp_err_t run(PsychicMiddlewareCallback next, PsychicRequest* request, PsychicResponse* response) = 0; +}; - PsychicMiddleware(PsychicMiddlewareFunction middleware); - virtual ~PsychicMiddleware(); +class PsychicMiddlewareClosure : public PsychicMiddleware +{ + protected: + PsychicMiddlewareFunction _fn; - void run(PsychicMiddlewareChain *chain, PsychicRequest *request, PsychicResponse *response); + public: + PsychicMiddlewareClosure(PsychicMiddlewareFunction fn); + esp_err_t run(PsychicMiddlewareCallback next, PsychicRequest* request, PsychicResponse* response) override; }; #endif \ No newline at end of file diff --git a/src/PsychicMiddlewareChain.cpp b/src/PsychicMiddlewareChain.cpp index ff5f048..7139e16 100644 --- a/src/PsychicMiddlewareChain.cpp +++ b/src/PsychicMiddlewareChain.cpp @@ -1,52 +1,47 @@ #include "PsychicMiddlewareChain.h" -PsychicMiddlewareChain::PsychicMiddlewareChain() : - _request(nullptr), - _response(nullptr) - { - } +PsychicMiddlewareChain::PsychicMiddlewareChain() {} -//TODO: memory management -PsychicMiddlewareChain::~PsychicMiddlewareChain() {} +PsychicMiddlewareChain::~PsychicMiddlewareChain() +{ + for (auto middleware : _middleware) { + delete middleware; + } + _middleware.clear(); +} -void PsychicMiddlewareChain::add(PsychicMiddleware *middleware) +void PsychicMiddlewareChain::add(PsychicMiddleware* middleware) { _middleware.push_back(middleware); } -bool PsychicMiddlewareChain::run(PsychicRequest *request, PsychicResponse *response) +void PsychicMiddlewareChain::add(PsychicMiddlewareFunction fn) { - //save our in/out objects - _request = request; - _response = response; - _finished = false; - - //start at the beginning. - auto _iterator = _middleware.begin(); - - //is it valid? - if (_iterator != _middleware.end()) - { - PsychicMiddleware* mw = *_iterator; - mw->run(this, _request, _response); - } + _middleware.push_back(new PsychicMiddlewareClosure(fn)); +} - //let them know if we finished or not. - return _finished; +bool PsychicMiddlewareChain::remove(PsychicMiddleware* middleware) +{ + _middleware.remove(middleware); + return true; } -void PsychicMiddlewareChain::next() +esp_err_t PsychicMiddlewareChain::run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareCallback finalizer) { - //get the next one! - _iterator++; - - //is there a next one? - if (_iterator != _middleware.end()) - { - PsychicMiddleware* mw = *_iterator; - mw->run(this, _request, _response); + if (_middleware.size() == 0) { + return finalizer(request, response); } - //nope, we're done. - else - _finished = true; + + PsychicMiddlewareCallback next; + std::list::iterator it = _middleware.begin(); + + next = [this, &next, &it, finalizer](PsychicRequest* request, PsychicResponse* response) { + if (it != _middleware.end()) { + return (*it++)->run(next, request, response); + } else { + return finalizer(request, response); + } + }; + + return (*it)->run(next, request, response); } \ No newline at end of file diff --git a/src/PsychicMiddlewareChain.h b/src/PsychicMiddlewareChain.h index a3702ae..f24373b 100644 --- a/src/PsychicMiddlewareChain.h +++ b/src/PsychicMiddlewareChain.h @@ -6,27 +6,24 @@ #include "PsychicResponse.h" #include "PsychicMiddleware.h" -class PsychicMiddleware; - /* * PsychicMiddlewareChain - handle tracking and executing our chain of middleware objects * */ -class PsychicMiddlewareChain { +class PsychicMiddlewareChain +{ protected: std::list _middleware; - std::list::iterator _iterator; - PsychicRequest* _request; - PsychicResponse* _response; - boolean _finished = false; public: PsychicMiddlewareChain(); virtual ~PsychicMiddlewareChain(); void add(PsychicMiddleware* middleware); - bool run(PsychicRequest *request, PsychicResponse *response); - void next(); + void add(PsychicMiddlewareFunction fn); + bool remove(PsychicMiddleware* middleware); + + esp_err_t run(PsychicRequest* request, PsychicResponse* response, PsychicMiddlewareCallback finalizer); }; #endif \ No newline at end of file diff --git a/src/PsychicMiddlewares.h b/src/PsychicMiddlewares.h new file mode 100644 index 0000000..494af38 --- /dev/null +++ b/src/PsychicMiddlewares.h @@ -0,0 +1,44 @@ +#pragma once + +#include "PsychicMiddleware.h" + +class AuthcMiddleware : public PsychicMiddleware +{ + protected: + String _username; + String _password; + HTTPAuthMethod _method; + String _realm; + String _authFailMsg; + bool _authc = false; + + public: + AuthcMiddleware(const char* username, const char* password, HTTPAuthMethod method = BASIC_AUTH, const char* realm = "", const char* authFailMsg = "") + : _username(username), _password(password), _method(method), _realm(realm), _authFailMsg(authFailMsg), _authc(!_username.isEmpty() && !_password.isEmpty()) {} + + esp_err_t run(PsychicMiddlewareCallback next, PsychicRequest* request, PsychicResponse* response) override + { + if (_authc && !request->authenticate(_username.c_str(), _password.c_str())) { + return request->requestAuthentication(_method, _realm.c_str(), _authFailMsg.c_str()); + } + return next(request, response); + } +}; + +class PermissiveCorsMiddleware : public PsychicMiddleware +{ + public: + esp_err_t run(PsychicMiddlewareCallback next, PsychicRequest* request, PsychicResponse* response) override + { + // gives the chance to other most important middleware to run first + esp_err_t ret = next(request, response); + // add CORS headers + if (ret == ESP_OK && request->hasHeader("Origin")) { + response->addHeader("Access-Control-Allow-Origin", "*"); + response->addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + response->addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept"); + response->addHeader("Access-Control-Max-Age", "86400"); + } + return ret; + } +}; diff --git a/src/PsychicRequest.cpp b/src/PsychicRequest.cpp index 3715648..f1fc89f 100644 --- a/src/PsychicRequest.cpp +++ b/src/PsychicRequest.cpp @@ -272,15 +272,6 @@ bool PsychicRequest::isMultipart() return (this->contentType().indexOf("multipart/form-data") >= 0); } -esp_err_t PsychicRequest::redirect(const char* url) -{ - PsychicResponse response(this); - response.setCode(301); - response.addHeader("Location", url); - - return response.send(); -} - bool PsychicRequest::hasCookie(const char* key, size_t* size) { char buffer; @@ -602,87 +593,3 @@ esp_err_t PsychicRequest::requestAuthentication(HTTPAuthMethod mode, const char* response.setContent(authStr.c_str()); 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(); -} - -esp_err_t PsychicRequest::reply(int code, const char* contentType, const uint8_t* content, size_t len) -{ - PsychicResponse response(this); - - response.setCode(code); - response.setContentType(contentType); - response.setContent(content, len); - - return response.send(); -} - -esp_err_t PsychicRequest::reply(PsychicResponse* response) -{ - esp_err_t err = response->send(); - delete response; - return err; -} - -PsychicResponse* PsychicRequest::beginReply(int code) -{ - PsychicResponse* response = new PsychicResponse(this); - response->setCode(code); - return response; -} - -PsychicResponse* PsychicRequest::beginReply(int code, const char* contentType) -{ - PsychicResponse* response = new PsychicResponse(this); - response->setCode(code); - response->setContentType(contentType); - return response; -} - -PsychicResponse* PsychicRequest::beginReply(int code, const char* contentType, const char* content) -{ - PsychicResponse* response = new PsychicResponse(this); - response->setCode(code); - response->setContentType(contentType); - response->setContent(content); - return response; -} - -PsychicResponse* PsychicRequest::beginReply(int code, const char* contentType, const uint8_t* content, size_t len) -{ - PsychicResponse* response = new PsychicResponse(this); - response->setCode(code); - response->setContentType(contentType); - response->setContent(content, len); - return response; -} diff --git a/src/PsychicRequest.h b/src/PsychicRequest.h index ba9484b..369cc49 100644 --- a/src/PsychicRequest.h +++ b/src/PsychicRequest.h @@ -5,7 +5,6 @@ #include "PsychicCore.h" #include "PsychicEndpoint.h" #include "PsychicHttpServer.h" -#include "PsychicResponse.h" #include "PsychicWebParameter.h" #ifdef PSY_ENABLE_REGEX @@ -137,18 +136,6 @@ class PsychicRequest 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); - esp_err_t reply(int code, const char* contentType, const uint8_t* content, size_t len); - esp_err_t reply(PsychicResponse* response); - - PsychicResponse* beginReply(int code); - PsychicResponse* beginReply(int code, const char* contentType); - PsychicResponse* beginReply(int code, const char* contentType, const char* content); - PsychicResponse* beginReply(int code, const char* contentType, const uint8_t* content, size_t len); }; #endif // PsychicRequest_h \ No newline at end of file diff --git a/src/PsychicResponse.cpp b/src/PsychicResponse.cpp index 5777998..a64528f 100644 --- a/src/PsychicResponse.cpp +++ b/src/PsychicResponse.cpp @@ -5,6 +5,7 @@ PsychicResponse::PsychicResponse(PsychicRequest* request) : _request(request), _code(200), _status(""), + _contentType(emptyString), _contentLength(0), _body("") { @@ -72,7 +73,7 @@ void PsychicResponse::setCode(int code) void PsychicResponse::setContentType(const char* contentType) { - httpd_resp_set_type(_request->request(), contentType); + _contentType = contentType; } void PsychicResponse::setContent(const char* content) @@ -103,6 +104,9 @@ esp_err_t PsychicResponse::send() sprintf(_status, "%u %s", _code, http_status_reason(_code)); httpd_resp_set_status(_request->request(), _status); + // set the content type + httpd_resp_set_type(_request->request(), _contentType.c_str()); + // our headers too this->sendHeaders(); @@ -144,4 +148,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/src/PsychicResponse.h b/src/PsychicResponse.h index c11dbe5..3f2cefd 100644 --- a/src/PsychicResponse.h +++ b/src/PsychicResponse.h @@ -14,6 +14,7 @@ class PsychicResponse int _code; char _status[60]; std::list _headers; + String _contentType; int64_t _contentLength; const char* _body; @@ -24,6 +25,8 @@ class PsychicResponse void setCode(int code); void setContentType(const char* contentType); + String& getContentType() { return _contentType; } + void setContentLength(int64_t contentLength) { _contentLength = contentLength; } int64_t getContentLength(int64_t contentLength) { return _contentLength; } @@ -41,6 +44,18 @@ class PsychicResponse void sendHeaders(); 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(); }; + + #endif // PsychicResponse_h \ No newline at end of file diff --git a/src/PsychicStaticFileHander.cpp b/src/PsychicStaticFileHander.cpp index dae6937..649bcb5 100644 --- a/src/PsychicStaticFileHander.cpp +++ b/src/PsychicStaticFileHander.cpp @@ -155,7 +155,7 @@ uint8_t PsychicStaticFileHandler::_countBits(const uint8_t value) const return n; } -esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest* request) +esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest* request, PsychicResponse* response) { if (_file == true) { @@ -164,7 +164,7 @@ esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest* request) if (_last_modified.length() && _last_modified == request->header("If-Modified-Since")) { _file.close(); - request->reply(304); // Not modified + response->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)) @@ -195,7 +195,7 @@ esp_err_t PsychicStaticFileHandler::handleRequest(PsychicRequest* request) } else { - return request->reply(404); + return response->send(404); } return ESP_OK; diff --git a/src/PsychicStaticFileHandler.h b/src/PsychicStaticFileHandler.h index c17249e..1cbe5f7 100644 --- a/src/PsychicStaticFileHandler.h +++ b/src/PsychicStaticFileHandler.h @@ -33,7 +33,7 @@ class PsychicStaticFileHandler : public PsychicWebHandler 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; + esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override; PsychicStaticFileHandler* setIsDir(bool isDir); PsychicStaticFileHandler* setDefaultFile(const char* filename); PsychicStaticFileHandler* setCacheControl(const char* cache_control); diff --git a/src/PsychicUploadHandler.cpp b/src/PsychicUploadHandler.cpp index 9fef9e6..85c50fe 100644 --- a/src/PsychicUploadHandler.cpp +++ b/src/PsychicUploadHandler.cpp @@ -10,7 +10,7 @@ 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; @@ -45,14 +45,14 @@ esp_err_t PsychicUploadHandler::handleRequest(PsychicRequest* request) 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) - request->reply(400, "text/html", "No multipart boundary found."); + 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; } diff --git a/src/PsychicUploadHandler.h b/src/PsychicUploadHandler.h index 8391df1..ef19a19 100644 --- a/src/PsychicUploadHandler.h +++ b/src/PsychicUploadHandler.h @@ -24,7 +24,7 @@ class PsychicUploadHandler : public PsychicWebHandler ~PsychicUploadHandler(); bool canHandle(PsychicRequest* request) override; - esp_err_t handleRequest(PsychicRequest* request) override; + esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override; PsychicUploadHandler* onUpload(PsychicUploadCallback fn); }; diff --git a/src/PsychicWebHandler.cpp b/src/PsychicWebHandler.cpp index 65ba597..9bdcaca 100644 --- a/src/PsychicWebHandler.cpp +++ b/src/PsychicWebHandler.cpp @@ -13,7 +13,7 @@ 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()); @@ -28,7 +28,7 @@ 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); - request->reply(400, "text/html", 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; @@ -37,14 +37,14 @@ esp_err_t PsychicWebHandler::handleRequest(PsychicRequest* request) // get our body loaded up. esp_err_t err = request->loadBody(); if (err != ESP_OK) - return request->reply(400, "text/html", "Error loading request body."); + return response->send(400, "text/html", "Error loading request body."); // load our params in. request->loadParams(); // okay, pass on to our callback. if (this->_requestCallback != NULL) - err = this->_requestCallback(request); + err = this->_requestCallback(request, response); return err; } diff --git a/src/PsychicWebHandler.h b/src/PsychicWebHandler.h index 4dafa9c..db640f7 100644 --- a/src/PsychicWebHandler.h +++ b/src/PsychicWebHandler.h @@ -22,7 +22,7 @@ class PsychicWebHandler : public PsychicHandler ~PsychicWebHandler(); virtual bool canHandle(PsychicRequest* request) override; - virtual esp_err_t handleRequest(PsychicRequest* request) override; + virtual esp_err_t handleRequest(PsychicRequest* request, PsychicResponse* response) override; PsychicWebHandler* onRequest(PsychicHttpRequestCallback fn); virtual void openCallback(PsychicClient* client); diff --git a/src/PsychicWebSocket.cpp b/src/PsychicWebSocket.cpp index cf51d6f..a553f9c 100644 --- a/src/PsychicWebSocket.cpp +++ b/src/PsychicWebSocket.cpp @@ -181,7 +181,7 @@ 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()); diff --git a/src/PsychicWebSocket.h b/src/PsychicWebSocket.h index 8817c40..a3541f4 100644 --- a/src/PsychicWebSocket.h +++ b/src/PsychicWebSocket.h @@ -60,7 +60,7 @@ class PsychicWebSocketHandler : public PsychicHandler 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);