diff --git a/.github/workflows/build-examples-master.yml b/.github/workflows/build-examples-master.yml index 0bf6840..c07fade 100644 --- a/.github/workflows/build-examples-master.yml +++ b/.github/workflows/build-examples-master.yml @@ -14,6 +14,7 @@ jobs: - HTML-Forms - HTTPS-and-HTTP - Middleware + - Middleware-Remove - Parameters - Parameter-Validation - Put-Post-Echo diff --git a/.github/workflows/build-examples-pr.yml b/.github/workflows/build-examples-pr.yml index ac7fe71..499a10b 100644 --- a/.github/workflows/build-examples-pr.yml +++ b/.github/workflows/build-examples-pr.yml @@ -13,6 +13,7 @@ jobs: - HTML-Forms - HTTPS-and-HTTP - Middleware + - Middleware-Remove - Parameters - Parameter-Validation - Put-Post-Echo @@ -51,4 +52,4 @@ jobs: name: "CI%3A Build Examples" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - \ No newline at end of file + diff --git a/examples/Middleware-Remove/Middleware-Remove.ino b/examples/Middleware-Remove/Middleware-Remove.ino new file mode 100644 index 0000000..97062be --- /dev/null +++ b/examples/Middleware-Remove/Middleware-Remove.ino @@ -0,0 +1,185 @@ +/** + * Example for the ESP32 HTTP(S) Webserver + * + * IMPORTANT NOTE: + * To run this script, your need to + * 1) Enter your WiFi SSID and PSK below this comment + * 2) Make sure to have certificate data available. You will find a + * shell script and instructions to do so in the library folder + * under extras/ + * + * This script will install an HTTPS Server on your ESP32 with the following + * functionalities: + * - Do the same as the Middleware.ino example + * - Show how to add and remove additional middlewares with different datatypes + */ + +// TODO: Configure your WiFi here +#define WIFI_SSID "" +#define WIFI_PSK "" + +// Include certificate data (see note above) +#include "cert.h" +#include "private_key.h" + +// We will use wifi +#include + +// For the middleware +#include + +// Includes for the server +#include +#include +#include +#include +#include + +// The HTTPS Server comes in a separate namespace. For easier use, include it here. +using namespace httpsserver; + +// Create an SSL certificate object from the files included above +SSLCert cert = SSLCert( + example_crt_DER, example_crt_DER_len, + example_key_DER, example_key_DER_len +); + +// Create an SSL-enabled server that uses the certificate +// The contstructor takes some more parameters, but we go for default values here. +HTTPSServer secureServer = HTTPSServer(&cert); + +// Declare some handler functions for the various URLs on the server +void handleRoot(HTTPRequest * req, HTTPResponse * res); +void handle404(HTTPRequest * req, HTTPResponse * res); + +// Declare a middleware function. +// Parameters: +// req: Request data, can be used to access URL, HTTP Method, Headers, ... +// res: Response data, can be used to access HTTP Status, Headers, ... +// next: This function is used to pass control down the chain. If you have done your work +// with the request object, you may decide if you want to process the request. +// If you do so, you call the next() function, and the next middleware function (if +// there is any) or the actual requestHandler will be called. +// If you want to skip the request, you do not call next, and set for example status +// code 403 on the response to show that the user is not allowed to access a specific +// resource. +// The Authentication examples provides more details on this. +void middlewareLogging(HTTPRequest * req, HTTPResponse * res, std::function next); + +void middlewareRawPointer(HTTPRequest * req, HTTPResponse * res, std::function next); + +void setup() { + // For logging + Serial.begin(115200); + + // Connect to WiFi + Serial.println("Setting up WiFi"); + WiFi.begin(WIFI_SSID, WIFI_PSK); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print("Connected. IP="); + Serial.println(WiFi.localIP()); + + // For every resource available on the server, we need to create a ResourceNode + // The ResourceNode links URL and HTTP method to a handler function + ResourceNode * nodeRoot = new ResourceNode("/", "GET", &handleRoot); + ResourceNode * node404 = new ResourceNode("", "GET", &handle404); + + // Add the root node to the server + secureServer.registerNode(nodeRoot); + + // Add the 404 not found node to the server. + // The path is ignored for the default node. + secureServer.setDefaultNode(node404); + + // Add the middleware. The function will be called globally for every request + // Note: The functions are called in the order they are added to the server. + // Also, if you want a middleware to handle only specific requests, you can check + // the URL within the middleware function. + secureServer.addMiddleware(middlewareLogging); + secureServer.addMiddleware(middlewareRawPointer); + // add a std::function middleware function + const HTTPSMiddlewareFunction std_function{middlewareLogging}; + secureServer.addMiddleware(std_function); + const auto outside_variable = 10; + // Add a lambda middleware function + const auto lamda = [outside_variable](HTTPRequest * req, HTTPResponse * res, std::function next) { + Serial.print("Middleware Lambda with outside variable "); + Serial.println(outside_variable); + }; + secureServer.addMiddleware(lamda); + + Serial.println("Removing middlewares..."); + // Remove the raw function pointer middleware + secureServer.removeMiddleware(middlewareRawPointer); + // Remove the std::function middleware + secureServer.removeMiddleware(std_function); + // Remove the lambda middleware + secureServer.removeMiddleware(lamda); + + Serial.println("Starting server..."); + secureServer.start(); + if (secureServer.isRunning()) { + Serial.println("Server ready."); + } +} + +void loop() { + // This call will let the server do its work + secureServer.loop(); + + // Other code would go here... + delay(1); +} + +// We want to log the following information for every request: +// - Response Status +// - Request Method +// - Request String (URL + Parameters) +void middlewareLogging(HTTPRequest * req, HTTPResponse * res, std::function next) { + // We want to print the response status, so we need to call next() first. + next(); + // After the call, the status is (hopefully) set by the handler function, so we can + // access it for logging. + Serial.printf("middlewareLogging(): %3d\t%s\t\t%s\n", + // Status code (like: 200) + res->getStatusCode(), + // Method used for the request (like: GET) + req->getMethod().c_str(), + // Request string (like /index.html) + req->getRequestString().c_str()); +} + +void middlewareRawPointer(HTTPRequest * req, HTTPResponse * res, std::function next) { + Serial.print("Middleware Raw Pointer"); + next(); +} + +// For details on the implementation of the hanlder functions, refer to the Static-Page example. +void handleRoot(HTTPRequest * req, HTTPResponse * res) { + res->setHeader("Content-Type", "text/html"); + res->println(""); + res->println(""); + res->println("Hello World!"); + res->println(""); + res->println("

Hello World!

"); + res->print("

Your server is running for "); + res->print((int)(millis()/1000), DEC); + res->println(" seconds.

"); + res->println(""); + res->println(""); +} + +void handle404(HTTPRequest * req, HTTPResponse * res) { + req->discardRequestBody(); + res->setStatusCode(404); + res->setStatusText("Not Found"); + res->setHeader("Content-Type", "text/html"); + res->println(""); + res->println(""); + res->println("Not Found"); + res->println("

404 Not Found

The requested resource was not found on this server.

"); + res->println(""); +} diff --git a/src/HTTPMiddlewareFunction.hpp b/src/HTTPMiddlewareFunction.hpp index e8b7cec..ae4379a 100644 --- a/src/HTTPMiddlewareFunction.hpp +++ b/src/HTTPMiddlewareFunction.hpp @@ -9,6 +9,7 @@ namespace httpsserver { class HTTPRequest; + using HTTPSMiddlewareFunctionType = void(HTTPRequest * req, HTTPResponse * res, std::function next); /** * \brief A middleware function that can be registered at the server. * @@ -21,6 +22,6 @@ namespace httpsserver { * handling in case of missing authentication. Don't forget to call next in case you want to access your * resources, though. */ - typedef void (HTTPSMiddlewareFunction)(HTTPRequest * req, HTTPResponse * res, std::function next); + typedef std::function HTTPSMiddlewareFunction; } #endif /* SRC_HTTPMIDDLEWAREFUNCTION_HPP_ */ diff --git a/src/ResourceResolver.cpp b/src/ResourceResolver.cpp index 6eab51d..b47b2a8 100644 --- a/src/ResourceResolver.cpp +++ b/src/ResourceResolver.cpp @@ -2,6 +2,21 @@ namespace httpsserver { +ResourceResolver::HTTPSMiddlewareFunctionCallback::HTTPSMiddlewareFunctionCallback(const HTTPSMiddlewareFunction callback, const HTTPSMiddlewareFunction* callback_std_function, const HTTPSMiddlewareFunctionType* callback_raw_pointer) : _callback(callback), _callback_std_function(callback_std_function), _callback_raw_pointer(callback_raw_pointer) { +}; + +HTTPSMiddlewareFunction ResourceResolver::HTTPSMiddlewareFunctionCallback::getCallback() { + return _callback; +}; + +const HTTPSMiddlewareFunction* ResourceResolver::HTTPSMiddlewareFunctionCallback::getStdFunctionPointer() { + return _callback_std_function; +}; + +const HTTPSMiddlewareFunctionType* ResourceResolver::HTTPSMiddlewareFunctionCallback::getRawFunctionPointer() { + return _callback_raw_pointer; +}; + ResourceResolver::ResourceResolver() { _nodes = new std::vector(); _defaultNode = NULL; @@ -160,15 +175,71 @@ void ResourceResolver::resolveNode(const std::string &method, const std::string } } -void ResourceResolver::addMiddleware(const HTTPSMiddlewareFunction * mwFunction) { +void ResourceResolver::updateMiddlewareList() { + _middleware.clear(); + _middleware.reserve(_middleware_callback.size()); + for (auto& callback : _middleware_callback) { + _middleware.push_back(callback.getCallback()); + } +} + +void ResourceResolver::addMiddleware(const HTTPSMiddlewareFunction &mwFunction) { + const HTTPSMiddlewareFunctionCallback callback{ + mwFunction, + &mwFunction, + nullptr + }; _middleware.push_back(mwFunction); + _middleware_callback.push_back(callback); } -void ResourceResolver::removeMiddleware(const HTTPSMiddlewareFunction * mwFunction) { - _middleware.erase(std::remove(_middleware.begin(), _middleware.end(), mwFunction), _middleware.end()); +void ResourceResolver::addMiddleware(void (*mwFunction)(HTTPRequest * req, HTTPResponse * res, std::function next)) { + auto mwFunction_callback = HTTPSMiddlewareFunction(mwFunction); + const HTTPSMiddlewareFunctionCallback callback{ + mwFunction_callback, + &mwFunction_callback, + mwFunction + }; + _middleware.push_back(mwFunction_callback); + _middleware_callback.push_back(callback); +} + +void ResourceResolver::removeMiddleware(const HTTPSMiddlewareFunction &mwFunction) { + bool found = false; + for (auto it = _middleware_callback.begin(); it != _middleware_callback.end();) { + auto element = *it; + const auto callback = element.getStdFunctionPointer(); + const auto callback_supplied = &mwFunction; + if (callback != nullptr && callback == callback_supplied) { + it = _middleware_callback.erase(it); + found = true; + } else { + ++it; + } + } + if (found) { + updateMiddlewareList(); + } +} + +void ResourceResolver::removeMiddleware(void (*mwFunction)(HTTPRequest * req, HTTPResponse * res, std::function next)) { + bool found = false; + for (auto it = _middleware_callback.begin(); it != _middleware_callback.end();) { + auto element = *it; + auto callback = element.getRawFunctionPointer(); + if (callback != nullptr && callback == mwFunction) { + it = _middleware_callback.erase(it); + found = true; + } else { + ++it; + } + } + if (found) { + updateMiddlewareList(); + } } -const std::vector ResourceResolver::getMiddleware() { +const std::vector ResourceResolver::getMiddleware() { return _middleware; } diff --git a/src/ResourceResolver.hpp b/src/ResourceResolver.hpp index cb67e7f..7fbc990 100644 --- a/src/ResourceResolver.hpp +++ b/src/ResourceResolver.hpp @@ -30,20 +30,36 @@ class ResourceResolver { void resolveNode(const std::string &method, const std::string &url, ResolvedResource &resolvedResource, HTTPNodeType nodeType); /** Add a middleware function to the end of the middleware function chain. See HTTPSMiddlewareFunction.hpp for details. */ - void addMiddleware(const HTTPSMiddlewareFunction * mwFunction); + void addMiddleware(const HTTPSMiddlewareFunction &mwFunction); + void addMiddleware(void (*mwFunction)(HTTPRequest *req, HTTPResponse *res, std::function next)); /** Remove a specific function from the middleware function chain. */ - void removeMiddleware(const HTTPSMiddlewareFunction * mwFunction); + void removeMiddleware(const HTTPSMiddlewareFunction &mwFunction); + void removeMiddleware(void (*mwFunction)(HTTPRequest * req, HTTPResponse * res, std::function next)); /** Get the current middleware chain with a resource function at the end */ - const std::vector getMiddleware(); + const std::vector getMiddleware(); private: + class HTTPSMiddlewareFunctionCallback { + private: + HTTPSMiddlewareFunction _callback; + const HTTPSMiddlewareFunction* _callback_std_function; + const HTTPSMiddlewareFunctionType* _callback_raw_pointer; + public: + HTTPSMiddlewareFunctionCallback(HTTPSMiddlewareFunction callback, const HTTPSMiddlewareFunction* const callback_std_function, const HTTPSMiddlewareFunctionType* callback_raw_pointer); + HTTPSMiddlewareFunction getCallback(); + const HTTPSMiddlewareFunction* getStdFunctionPointer(); + const HTTPSMiddlewareFunctionType* getRawFunctionPointer(); + }; // This vector holds all nodes (with callbacks) that are registered std::vector * _nodes; HTTPNode * _defaultNode; // Middleware functions, if any are registered. Will be called in order of the vector. - std::vector _middleware; + std::vector _middleware; + std::vector _middleware_callback; + + void updateMiddlewareList(); }; } /* namespace httpsserver */