mirror of
https://github.com/me-no-dev/ESPAsyncWebServer.git
synced 2025-09-30 00:00:56 +02:00
Merge pull request #98 from mathieucarbou/middleware
Add support for Middleware
This commit is contained in:
76
README.md
76
README.md
@@ -50,8 +50,9 @@ Dependency:
|
||||
- Removed ESPIDF Editor (this is not the role of a web server library to do that - get the source files from the original repos if required)
|
||||
- Support overriding default response headers
|
||||
- Support resumable downloads using HEAD and bytes range
|
||||
- **Support for middleware**
|
||||
|
||||
## Documentation
|
||||
## Original Documentation
|
||||
|
||||
Usage and API stays the same as the original library.
|
||||
Please look at the original libraries for more examples and documentation.
|
||||
@@ -136,3 +137,76 @@ If you have smaller messages, you can increase `WS_MAX_QUEUED_MESSAGES` to 128.
|
||||
```
|
||||
|
||||
This will send error 400 instead of 200.
|
||||
|
||||
## Middleware
|
||||
|
||||
Middleware is a way to intercept requests to perform some operations on them, like authentication, authorization, logging, etc and also act on the response headers.
|
||||
|
||||
Middleware can either be attached to individual handlers, attached at the server level (thus applied to all handlers), or both.
|
||||
They will be executed in the order they are attached, and they can stop the request processing by sending a response themselves.
|
||||
|
||||
You can have a look at the [SimpleServer.ino](https://github.com/mathieucarbou/ESPAsyncWebServer/blob/main/examples/SimpleServer/SimpleServer.ino) example for some use cases.
|
||||
|
||||
For example, such middleware would handle authentication and set some attributes on the request to make them available for the next middleware and for the handler which will process the request.
|
||||
|
||||
```c++
|
||||
AsyncMiddlewareFunction complexAuth([](AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
if (!request->authenticate("user", "password")) {
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
|
||||
request->setAttribute("user", "Mathieu");
|
||||
request->setAttribute("role", "staff");
|
||||
|
||||
next(); // continue processing
|
||||
|
||||
// you can act one the response object
|
||||
request->getResponse()->addHeader("X-Rate-Limit", "200");
|
||||
});
|
||||
```
|
||||
|
||||
**Here are the list of available middlewares:**
|
||||
|
||||
- `AsyncMiddlewareFunction`: can convert a lambda function (`ArMiddlewareCallback`) to a middleware
|
||||
- `AuthenticationMiddleware`: to handle basic/digest authentication globally or per handler
|
||||
- `AuthorizationMiddleware`: to handle authorization globally or per handler
|
||||
- `CorsMiddleware`: to handle CORS preflight request globally or per handler
|
||||
- `HeaderFilterMiddleware`: to filter out headers from the request
|
||||
- `HeaderFreeMiddleware`: to only keep some headers from the request, and remove the others
|
||||
- `LoggerMiddleware`: to log requests globally or per handler with the same pattern as curl. Will also record request processing time
|
||||
- `RateLimitMiddleware`: to limit the number of requests on a windows of time globally or per handler
|
||||
|
||||
## How to use authentication with AuthenticationMiddleware
|
||||
|
||||
Do not use the `setUsername()` and `setPassword()` methods on the hanlders anymore.
|
||||
They are deprecated.
|
||||
These methods were causing a copy of the username and password for each handler, which is not efficient.
|
||||
|
||||
Now, you can use the `AuthenticationMiddleware` to handle authentication globally or per handler.
|
||||
|
||||
```c++
|
||||
AuthenticationMiddleware authMiddleware;
|
||||
|
||||
// [...]
|
||||
|
||||
authMiddleware.setAuthType(AuthenticationMiddleware::AuthType::AUTH_DIGEST);
|
||||
authMiddleware.setRealm("My app name");
|
||||
authMiddleware.setUsername("admin");
|
||||
authMiddleware.setPassword("admin");
|
||||
|
||||
// [...]
|
||||
|
||||
server.addMiddleware(&authMiddleware); // globally add authentication to the server
|
||||
|
||||
// [...]
|
||||
|
||||
myHandler.addMiddleware(&authMiddleware); // add authentication to a specific handler
|
||||
```
|
||||
|
||||
## Migration to Middleware to improve performance and memory usage
|
||||
|
||||
- `AsyncEventSource.authorizeConnect(...)` => do not use this method anymore: add a common `AuthorizationMiddleware` to the handler or server, and make sure to add it AFTER the `AuthenticationMiddleware` if you use authentication.
|
||||
- `AsyncWebHandler.setAuthentication(...)` => do not use this method anymore: add a common `AuthenticationMiddleware` to the handler or server
|
||||
- `ArUploadHandlerFunction` and `ArBodyHandlerFunction` => these callbacks receiving body data and upload and not calling anymore the authentication code for performance reasons.
|
||||
These callbacks can be called multiple times, so this is up to the user to now call the `AuthenticationMiddleware` if needed and ideally when the method is called for the first time.
|
||||
These callbacks are also not triggering the whole middleware chain since they are not part of the request processing workflow (they are not the final handler).
|
||||
|
@@ -50,8 +50,9 @@ Dependency:
|
||||
- Removed ESPIDF Editor (this is not the role of a web server library to do that - get the source files from the original repos if required)
|
||||
- Support overriding default response headers
|
||||
- Support resumable downloads using HEAD and bytes range
|
||||
- **Support for middleware**
|
||||
|
||||
## Documentation
|
||||
## Original Documentation
|
||||
|
||||
Usage and API stays the same as the original library.
|
||||
Please look at the original libraries for more examples and documentation.
|
||||
@@ -136,3 +137,76 @@ If you have smaller messages, you can increase `WS_MAX_QUEUED_MESSAGES` to 128.
|
||||
```
|
||||
|
||||
This will send error 400 instead of 200.
|
||||
|
||||
## Middleware
|
||||
|
||||
Middleware is a way to intercept requests to perform some operations on them, like authentication, authorization, logging, etc and also act on the response headers.
|
||||
|
||||
Middleware can either be attached to individual handlers, attached at the server level (thus applied to all handlers), or both.
|
||||
They will be executed in the order they are attached, and they can stop the request processing by sending a response themselves.
|
||||
|
||||
You can have a look at the [SimpleServer.ino](https://github.com/mathieucarbou/ESPAsyncWebServer/blob/main/examples/SimpleServer/SimpleServer.ino) example for some use cases.
|
||||
|
||||
For example, such middleware would handle authentication and set some attributes on the request to make them available for the next middleware and for the handler which will process the request.
|
||||
|
||||
```c++
|
||||
AsyncMiddlewareFunction complexAuth([](AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
if (!request->authenticate("user", "password")) {
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
|
||||
request->setAttribute("user", "Mathieu");
|
||||
request->setAttribute("role", "staff");
|
||||
|
||||
next(); // continue processing
|
||||
|
||||
// you can act one the response object
|
||||
request->getResponse()->addHeader("X-Rate-Limit", "200");
|
||||
});
|
||||
```
|
||||
|
||||
**Here are the list of available middlewares:**
|
||||
|
||||
- `AsyncMiddlewareFunction`: can convert a lambda function (`ArMiddlewareCallback`) to a middleware
|
||||
- `AuthenticationMiddleware`: to handle basic/digest authentication globally or per handler
|
||||
- `AuthorizationMiddleware`: to handle authorization globally or per handler
|
||||
- `CorsMiddleware`: to handle CORS preflight request globally or per handler
|
||||
- `HeaderFilterMiddleware`: to filter out headers from the request
|
||||
- `HeaderFreeMiddleware`: to only keep some headers from the request, and remove the others
|
||||
- `LoggerMiddleware`: to log requests globally or per handler with the same pattern as curl. Will also record request processing time
|
||||
- `RateLimitMiddleware`: to limit the number of requests on a windows of time globally or per handler
|
||||
|
||||
## How to use authentication with AuthenticationMiddleware
|
||||
|
||||
Do not use the `setUsername()` and `setPassword()` methods on the hanlders anymore.
|
||||
They are deprecated.
|
||||
These methods were causing a copy of the username and password for each handler, which is not efficient.
|
||||
|
||||
Now, you can use the `AuthenticationMiddleware` to handle authentication globally or per handler.
|
||||
|
||||
```c++
|
||||
AuthenticationMiddleware authMiddleware;
|
||||
|
||||
// [...]
|
||||
|
||||
authMiddleware.setAuthType(AuthenticationMiddleware::AuthType::AUTH_DIGEST);
|
||||
authMiddleware.setRealm("My app name");
|
||||
authMiddleware.setUsername("admin");
|
||||
authMiddleware.setPassword("admin");
|
||||
|
||||
// [...]
|
||||
|
||||
server.addMiddleware(&authMiddleware); // globally add authentication to the server
|
||||
|
||||
// [...]
|
||||
|
||||
myHandler.addMiddleware(&authMiddleware); // add authentication to a specific handler
|
||||
```
|
||||
|
||||
## Migration to Middleware to improve performance and memory usage
|
||||
|
||||
- `AsyncEventSource.authorizeConnect(...)` => do not use this method anymore: add a common `AuthorizationMiddleware` to the handler or server, and make sure to add it AFTER the `AuthenticationMiddleware` if you use authentication.
|
||||
- `AsyncWebHandler.setAuthentication(...)` => do not use this method anymore: add a common `AuthenticationMiddleware` to the handler or server
|
||||
- `ArUploadHandlerFunction` and `ArBodyHandlerFunction` => these callbacks receiving body data and upload and not calling anymore the authentication code for performance reasons.
|
||||
These callbacks can be called multiple times, so this is up to the user to now call the `AuthenticationMiddleware` if needed and ideally when the method is called for the first time.
|
||||
These callbacks are also not triggering the whole middleware chain since they are not part of the request processing workflow (they are not the final handler).
|
||||
|
@@ -28,6 +28,45 @@ AsyncWebServer server(80);
|
||||
AsyncEventSource events("/events");
|
||||
AsyncWebSocket ws("/ws");
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Middlewares
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// log incoming requests
|
||||
LoggingMiddleware requestLogger;
|
||||
|
||||
// CORS
|
||||
CorsMiddleware cors;
|
||||
|
||||
// maximum 5 requests per 10 seconds
|
||||
RateLimitMiddleware rateLimit;
|
||||
|
||||
// filter out specific headers from the incoming request
|
||||
HeaderFilterMiddleware headerFilter;
|
||||
|
||||
// remove all headers from the incoming request except the ones provided in the constructor
|
||||
HeaderFreeMiddleware headerFree;
|
||||
|
||||
// simple digest authentication
|
||||
AuthenticationMiddleware simpleDigestAuth;
|
||||
|
||||
// complex authentication which adds request attributes for the next middlewares and handler
|
||||
AsyncMiddlewareFunction complexAuth([](AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
if (!request->authenticate("user", "password")) {
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
request->setAttribute("user", "Mathieu");
|
||||
request->setAttribute("role", "staff");
|
||||
|
||||
next();
|
||||
|
||||
request->getResponse()->addHeader("X-Rate-Limit", "200");
|
||||
});
|
||||
|
||||
AuthorizationMiddleware authz([](AsyncWebServerRequest* request) { return request->getAttribute("role") == "staff"; });
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const char* PARAM_MESSAGE PROGMEM = "message";
|
||||
const char* SSE_HTLM PROGMEM = R"(
|
||||
<!DOCTYPE html>
|
||||
@@ -127,6 +166,77 @@ void setup() {
|
||||
request->send(200);
|
||||
});
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Middlewares at server level (will apply to all requests)
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
requestLogger.setOutput(Serial);
|
||||
|
||||
simpleDigestAuth.setUsername("admin");
|
||||
simpleDigestAuth.setPassword("admin");
|
||||
simpleDigestAuth.setRealm("MyApp");
|
||||
|
||||
rateLimit.setMaxRequests(5);
|
||||
rateLimit.setWindowSize(10);
|
||||
|
||||
headerFilter.filter("X-Remove-Me");
|
||||
headerFree.keep("X-Keep-Me");
|
||||
headerFree.keep("host");
|
||||
|
||||
// global middleware
|
||||
server.addMiddleware(&requestLogger);
|
||||
server.addMiddlewares({&rateLimit, &cors, &headerFilter});
|
||||
|
||||
cors.setOrigin("http://192.168.4.1");
|
||||
cors.setMethods("POST, GET, OPTIONS, DELETE");
|
||||
cors.setHeaders("X-Custom-Header");
|
||||
cors.setAllowCredentials(false);
|
||||
cors.setMaxAge(600);
|
||||
|
||||
// Test CORS preflight request
|
||||
// curl -v -X OPTIONS -H "origin: http://192.168.4.1" http://192.168.4.1/middleware/cors
|
||||
server.on("/middleware/cors", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
request->send(200, "text/plain", "Hello, world!");
|
||||
});
|
||||
|
||||
// curl -v -X GET -H "x-remove-me: value" http://192.168.4.1/middleware/test-header-filter
|
||||
// - requestLogger will log the incoming headers (including x-remove-me)
|
||||
// - headerFilter will remove x-remove-me header
|
||||
// - handler will log the remaining headers
|
||||
server.on("/middleware/test-header-filter", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
for (auto& h : request->getHeaders())
|
||||
Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str());
|
||||
request->send(200);
|
||||
});
|
||||
|
||||
// curl -v -X GET -H "x-keep-me: value" http://192.168.4.1/middleware/test-header-free
|
||||
// - requestLogger will log the incoming headers (including x-keep-me)
|
||||
// - headerFree will remove all headers except x-keep-me and host
|
||||
// - handler will log the remaining headers (x-keep-me and host)
|
||||
server.on("/middleware/test-header-free", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
for (auto& h : request->getHeaders())
|
||||
Serial.printf("Request Header: %s = %s\n", h.name().c_str(), h.value().c_str());
|
||||
request->send(200);
|
||||
})
|
||||
.addMiddleware(&headerFree);
|
||||
|
||||
// simple digest authentication
|
||||
// curl -v -X GET -H "x-remove-me: value" --digest -u admin:admin http://192.168.4.1/middleware/auth-simple
|
||||
server.on("/middleware/auth-simple", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
request->send(200, "text/plain", "Hello, world!");
|
||||
})
|
||||
.addMiddleware(&simpleDigestAuth);
|
||||
|
||||
// curl -v -X GET -H "x-remove-me: value" --digest -u user:password http://192.168.4.1/middleware/auth-complex
|
||||
server.on("/middleware/auth-complex", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
String buffer = "Hello ";
|
||||
buffer.concat(request->getAttribute("user"));
|
||||
buffer.concat(" with role: ");
|
||||
buffer.concat(request->getAttribute("role"));
|
||||
request->send(200, "text/plain", buffer);
|
||||
})
|
||||
.addMiddlewares({&complexAuth, &authz});
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
|
@@ -288,10 +288,6 @@ void AsyncEventSource::onConnect(ArEventHandlerFunction cb) {
|
||||
_connectcb = cb;
|
||||
}
|
||||
|
||||
void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb) {
|
||||
_authorizeConnectHandler = cb;
|
||||
}
|
||||
|
||||
void AsyncEventSource::_addClient(AsyncEventSourceClient* client) {
|
||||
if (!client)
|
||||
return;
|
||||
@@ -378,14 +374,6 @@ bool AsyncEventSource::canHandle(AsyncWebServerRequest* request) {
|
||||
}
|
||||
|
||||
void AsyncEventSource::handleRequest(AsyncWebServerRequest* request) {
|
||||
if ((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) {
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
if (_authorizeConnectHandler != NULL) {
|
||||
if (!_authorizeConnectHandler(request)) {
|
||||
return request->send(401);
|
||||
}
|
||||
}
|
||||
request->send(new AsyncEventSourceResponse(this));
|
||||
}
|
||||
|
||||
|
@@ -53,7 +53,7 @@ class AsyncEventSource;
|
||||
class AsyncEventSourceResponse;
|
||||
class AsyncEventSourceClient;
|
||||
using ArEventHandlerFunction = std::function<void(AsyncEventSourceClient* client)>;
|
||||
using ArAuthorizeConnectHandler = std::function<bool(AsyncWebServerRequest* request)>;
|
||||
using ArAuthorizeConnectHandler = ArAuthorizeFunction;
|
||||
|
||||
class AsyncEventSourceMessage {
|
||||
private:
|
||||
@@ -116,7 +116,6 @@ class AsyncEventSource : public AsyncWebHandler {
|
||||
mutable std::mutex _client_queue_lock;
|
||||
#endif
|
||||
ArEventHandlerFunction _connectcb{nullptr};
|
||||
ArAuthorizeConnectHandler _authorizeConnectHandler;
|
||||
|
||||
public:
|
||||
AsyncEventSource(const String& url) : _url(url) {};
|
||||
@@ -125,7 +124,11 @@ class AsyncEventSource : public AsyncWebHandler {
|
||||
const char* url() const { return _url.c_str(); }
|
||||
void close();
|
||||
void onConnect(ArEventHandlerFunction cb);
|
||||
void authorizeConnect(ArAuthorizeConnectHandler cb);
|
||||
void authorizeConnect(ArAuthorizeConnectHandler cb) {
|
||||
AuthorizationMiddleware* m = new AuthorizationMiddleware(401, cb);
|
||||
m->_freeOnRemoval = true;
|
||||
addMiddleware(m);
|
||||
}
|
||||
void send(const String& message, const String& event, uint32_t id = 0, uint32_t reconnect = 0) { send(message.c_str(), event.c_str(), id, reconnect); }
|
||||
void send(const String& message, const char* event, uint32_t id = 0, uint32_t reconnect = 0) { send(message.c_str(), event, id, reconnect); }
|
||||
void send(const char* message, const char* event = NULL, uint32_t id = 0, uint32_t reconnect = 0);
|
||||
|
@@ -202,8 +202,6 @@ class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
|
||||
}
|
||||
|
||||
virtual void handleRequest(AsyncWebServerRequest* request) override final {
|
||||
if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
if (_onRequest) {
|
||||
if (request->method() == HTTP_GET) {
|
||||
JsonVariant json;
|
||||
|
@@ -101,9 +101,6 @@ class AsyncCallbackMessagePackWebHandler : public AsyncWebHandler {
|
||||
}
|
||||
|
||||
virtual void handleRequest(AsyncWebServerRequest* request) override final {
|
||||
if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
|
||||
if (_onRequest) {
|
||||
if (request->method() == HTTP_GET) {
|
||||
JsonVariant json;
|
||||
|
@@ -1086,9 +1086,6 @@ void AsyncWebSocket::handleRequest(AsyncWebServerRequest* request) {
|
||||
request->send(400);
|
||||
return;
|
||||
}
|
||||
if ((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) {
|
||||
return request->requestAuthentication();
|
||||
}
|
||||
if (_handshakeHandler != nullptr) {
|
||||
if (!_handshakeHandler(request)) {
|
||||
request->send(401);
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include "Arduino.h"
|
||||
|
||||
#include "FS.h"
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
@@ -68,6 +69,7 @@ class AsyncWebHandler;
|
||||
class AsyncStaticWebHandler;
|
||||
class AsyncCallbackWebHandler;
|
||||
class AsyncResponseStream;
|
||||
class AsyncMiddlewareChain;
|
||||
|
||||
#if defined(TARGET_RP2040)
|
||||
typedef enum http_method WebRequestMethod;
|
||||
@@ -541,6 +543,247 @@ bool ON_STA_FILTER(AsyncWebServerRequest* request);
|
||||
|
||||
bool ON_AP_FILTER(AsyncWebServerRequest* request);
|
||||
|
||||
/*
|
||||
* MIDDLEWARE :: Request interceptor, assigned to a AsyncWebHandler (or the server), which can be used:
|
||||
* 1. to run some code before the final handler is executed (e.g. check authentication)
|
||||
* 2. decide whether to proceed or not with the next handler
|
||||
* */
|
||||
|
||||
using ArMiddlewareNext = std::function<void(void)>;
|
||||
using ArMiddlewareCallback = std::function<void(AsyncWebServerRequest* request, ArMiddlewareNext next)>;
|
||||
|
||||
// Middleware is a base class for all middleware
|
||||
class AsyncMiddleware {
|
||||
public:
|
||||
virtual ~AsyncMiddleware() {}
|
||||
virtual void run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
return next();
|
||||
};
|
||||
|
||||
private:
|
||||
friend class AsyncWebHandler;
|
||||
friend class AsyncEventSource;
|
||||
friend class AsyncMiddlewareChain;
|
||||
bool _freeOnRemoval = false;
|
||||
};
|
||||
|
||||
// Create a custom middleware by providing an anonymous callback function
|
||||
class AsyncMiddlewareFunction : public AsyncMiddleware {
|
||||
public:
|
||||
AsyncMiddlewareFunction(ArMiddlewareCallback fn) : _fn(fn) {}
|
||||
void run(AsyncWebServerRequest* request, ArMiddlewareNext next) override { return _fn(request, next); };
|
||||
|
||||
private:
|
||||
ArMiddlewareCallback _fn;
|
||||
};
|
||||
|
||||
// For internal use only: super class to add/remove middleware to server or handlers
|
||||
class AsyncMiddlewareChain {
|
||||
public:
|
||||
virtual ~AsyncMiddlewareChain() {
|
||||
for (AsyncMiddleware* m : _middlewares)
|
||||
if (m->_freeOnRemoval)
|
||||
delete m;
|
||||
}
|
||||
void addMiddleware(ArMiddlewareCallback fn) {
|
||||
AsyncMiddlewareFunction* m = new AsyncMiddlewareFunction(fn);
|
||||
m->_freeOnRemoval = true;
|
||||
_middlewares.emplace_back(m);
|
||||
}
|
||||
void addMiddleware(AsyncMiddleware* middleware) { _middlewares.emplace_back(middleware); }
|
||||
void addMiddlewares(std::vector<AsyncMiddleware*> middlewares) {
|
||||
for (AsyncMiddleware* m : middlewares)
|
||||
addMiddleware(m);
|
||||
}
|
||||
bool removeMiddleware(AsyncMiddleware* middleware) {
|
||||
// remove all middlewares from _middlewares vector being equal to middleware, delete them having _freeOnRemoval flag to true and resize the vector.
|
||||
const size_t size = _middlewares.size();
|
||||
_middlewares.erase(std::remove_if(_middlewares.begin(), _middlewares.end(), [middleware](AsyncMiddleware* m) {
|
||||
if (m == middleware) {
|
||||
if (m->_freeOnRemoval)
|
||||
delete m;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
_middlewares.end());
|
||||
return size != _middlewares.size();
|
||||
}
|
||||
// For internal use only
|
||||
void _runChain(AsyncWebServerRequest* request, ArMiddlewareNext finalizer) {
|
||||
if (!_middlewares.size())
|
||||
return finalizer();
|
||||
ArMiddlewareNext next;
|
||||
std::list<AsyncMiddleware*>::iterator it = _middlewares.begin();
|
||||
next = [this, &next, &it, request, finalizer]() {
|
||||
if (it == _middlewares.end())
|
||||
return finalizer();
|
||||
AsyncMiddleware* m = *it;
|
||||
it++;
|
||||
return m->run(request, next);
|
||||
};
|
||||
return next();
|
||||
}
|
||||
|
||||
protected:
|
||||
std::list<AsyncMiddleware*> _middlewares;
|
||||
};
|
||||
|
||||
// AuthenticationMiddleware is a middleware that checks if the request is authenticated
|
||||
class AuthenticationMiddleware : public AsyncMiddleware {
|
||||
public:
|
||||
typedef enum {
|
||||
AUTH_NONE,
|
||||
AUTH_BASIC,
|
||||
AUTH_DIGEST
|
||||
} AuthType;
|
||||
|
||||
void setUsername(const char* username) { _username = username; }
|
||||
void setPassword(const char* password) { _password = password; }
|
||||
void setRealm(const char* realm) { _realm = realm; }
|
||||
void setPasswordIsHash(bool passwordIsHash) { _hash = passwordIsHash; }
|
||||
void setAuthType(AuthType authType) { _authType = authType; }
|
||||
|
||||
bool allowed(AsyncWebServerRequest* request) {
|
||||
return _authType == AUTH_NONE || !_username.length() || !_password.length() || request->authenticate(_username.c_str(), _password.c_str(), _realm, _hash);
|
||||
}
|
||||
|
||||
void run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
return allowed(request) ? next() : request->requestAuthentication(_realm, _authType == AUTH_DIGEST);
|
||||
}
|
||||
|
||||
private:
|
||||
String _username;
|
||||
String _password;
|
||||
const char* _realm = nullptr;
|
||||
bool _hash = false;
|
||||
AuthType _authType = AUTH_DIGEST;
|
||||
};
|
||||
|
||||
using ArAuthorizeFunction = std::function<bool(AsyncWebServerRequest* request)>;
|
||||
// AuthorizationMiddleware is a middleware that checks if the request is authorized
|
||||
class AuthorizationMiddleware : public AsyncMiddleware {
|
||||
public:
|
||||
AuthorizationMiddleware(ArAuthorizeFunction authorizeConnectHandler) : _code(403), _authz(authorizeConnectHandler) {}
|
||||
AuthorizationMiddleware(int code, ArAuthorizeFunction authorizeConnectHandler) : _code(code), _authz(authorizeConnectHandler) {}
|
||||
void run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
if (_authz && !_authz(request))
|
||||
return request->send(_code);
|
||||
return next();
|
||||
}
|
||||
|
||||
private:
|
||||
int _code;
|
||||
ArAuthorizeFunction _authz;
|
||||
};
|
||||
|
||||
// remove all headers from the incoming request except the ones provided in the constructor
|
||||
class HeaderFreeMiddleware : public AsyncMiddleware {
|
||||
public:
|
||||
void keep(const char* name) { _toKeep.push_back(name); }
|
||||
void unKeep(const char* name) { _toKeep.erase(std::remove(_toKeep.begin(), _toKeep.end(), name), _toKeep.end()); }
|
||||
void run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
std::vector<const char*> reqHeaders;
|
||||
request->getHeaderNames(reqHeaders);
|
||||
for (const char* h : reqHeaders) {
|
||||
bool keep = false;
|
||||
for (const char* k : _toKeep) {
|
||||
if (strcasecmp(h, k) == 0) {
|
||||
keep = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!keep) {
|
||||
request->removeHeader(h);
|
||||
}
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<const char*> _toKeep;
|
||||
};
|
||||
|
||||
// filter out specific headers from the incoming request
|
||||
class HeaderFilterMiddleware : public AsyncMiddleware {
|
||||
public:
|
||||
void filter(const char* name) { _toRemove.push_back(name); }
|
||||
void unFilter(const char* name) { _toRemove.erase(std::remove(_toRemove.begin(), _toRemove.end(), name), _toRemove.end()); }
|
||||
void run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
for (auto it = _toRemove.begin(); it != _toRemove.end(); ++it)
|
||||
request->removeHeader(*it);
|
||||
next();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<const char*> _toRemove;
|
||||
};
|
||||
|
||||
// curl-like logging of incoming requests
|
||||
class LoggingMiddleware : public AsyncMiddleware {
|
||||
public:
|
||||
void setOutput(Print& output) { _out = &output; }
|
||||
void setEnabled(bool enabled) { _enabled = enabled; }
|
||||
bool isEnabled() { return _enabled && _out; }
|
||||
|
||||
void run(AsyncWebServerRequest* request, ArMiddlewareNext next);
|
||||
|
||||
private:
|
||||
Print* _out = nullptr;
|
||||
bool _enabled = true;
|
||||
};
|
||||
|
||||
// CORS Middleware
|
||||
class CorsMiddleware : public AsyncMiddleware {
|
||||
public:
|
||||
void setOrigin(const char* origin) { _origin = origin; }
|
||||
void setMethods(const char* methods) { _methods = methods; }
|
||||
void setHeaders(const char* headers) { _headers = headers; }
|
||||
void setAllowCredentials(bool credentials) { _credentials = credentials; }
|
||||
void setMaxAge(uint32_t seconds) { _maxAge = seconds; }
|
||||
|
||||
void run(AsyncWebServerRequest* request, ArMiddlewareNext next);
|
||||
|
||||
private:
|
||||
String _origin = "*";
|
||||
String _methods = "*";
|
||||
String _headers = "*";
|
||||
bool _credentials = true;
|
||||
uint32_t _maxAge = 86400;
|
||||
};
|
||||
|
||||
// Rate limit Middleware
|
||||
class RateLimitMiddleware : public AsyncMiddleware {
|
||||
public:
|
||||
void setMaxRequests(size_t maxRequests) { _maxRequests = maxRequests; }
|
||||
void setWindowSize(uint32_t seconds) { _windowSizeMillis = seconds * 1000; }
|
||||
|
||||
bool isRequestAllowed(uint32_t& retryAfterSeconds) {
|
||||
uint32_t now = millis();
|
||||
|
||||
while (!_requestTimes.empty() && _requestTimes.front() <= now - _windowSizeMillis)
|
||||
_requestTimes.pop_front();
|
||||
|
||||
_requestTimes.push_back(now);
|
||||
|
||||
if (_requestTimes.size() > _maxRequests) {
|
||||
_requestTimes.pop_front();
|
||||
retryAfterSeconds = (_windowSizeMillis - (now - _requestTimes.front())) / 1000 + 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
retryAfterSeconds = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void run(AsyncWebServerRequest* request, ArMiddlewareNext next);
|
||||
|
||||
private:
|
||||
size_t _maxRequests = 0;
|
||||
uint32_t _windowSizeMillis = 0;
|
||||
std::list<uint32_t> _requestTimes;
|
||||
};
|
||||
|
||||
/*
|
||||
* REWRITE :: One instance can be handle any Request (done by the Server)
|
||||
* */
|
||||
@@ -576,11 +819,9 @@ class AsyncWebRewrite {
|
||||
* HANDLER :: One instance can be attached to any Request (done by the Server)
|
||||
* */
|
||||
|
||||
class AsyncWebHandler {
|
||||
class AsyncWebHandler : public AsyncMiddlewareChain {
|
||||
protected:
|
||||
ArRequestFilterFunction _filter{nullptr};
|
||||
String _username;
|
||||
String _password;
|
||||
|
||||
public:
|
||||
AsyncWebHandler() {}
|
||||
@@ -589,15 +830,16 @@ class AsyncWebHandler {
|
||||
return *this;
|
||||
}
|
||||
AsyncWebHandler& setAuthentication(const char* username, const char* password) {
|
||||
_username = username;
|
||||
_password = password;
|
||||
return *this;
|
||||
};
|
||||
AsyncWebHandler& setAuthentication(const String& username, const String& password) {
|
||||
_username = username;
|
||||
_password = password;
|
||||
if (username == nullptr || password == nullptr || strlen(username) == 0 || strlen(password) == 0)
|
||||
return *this;
|
||||
AuthenticationMiddleware* m = new AuthenticationMiddleware();
|
||||
m->setUsername(username);
|
||||
m->setPassword(password);
|
||||
m->_freeOnRemoval = true;
|
||||
addMiddleware(m);
|
||||
return *this;
|
||||
};
|
||||
AsyncWebHandler& setAuthentication(const String& username, const String& password) { return setAuthentication(username.c_str(), password.c_str()); };
|
||||
bool filter(AsyncWebServerRequest* request) { return _filter == NULL || _filter(request); }
|
||||
virtual ~AsyncWebHandler() {}
|
||||
virtual bool canHandle(AsyncWebServerRequest* request __attribute__((unused))) {
|
||||
@@ -685,7 +927,7 @@ typedef std::function<void(AsyncWebServerRequest* request)> ArRequestHandlerFunc
|
||||
typedef std::function<void(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final)> ArUploadHandlerFunction;
|
||||
typedef std::function<void(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total)> ArBodyHandlerFunction;
|
||||
|
||||
class AsyncWebServer {
|
||||
class AsyncWebServer : public AsyncMiddlewareChain {
|
||||
protected:
|
||||
AsyncServer _server;
|
||||
std::list<std::shared_ptr<AsyncWebRewrite>> _rewrites;
|
||||
|
84
src/Middlewares.cpp
Normal file
84
src/Middlewares.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#include "ESPAsyncWebServer.h"
|
||||
|
||||
void LoggingMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
if (!isEnabled()) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
_out->print(F("* Connection from "));
|
||||
_out->print(request->client()->remoteIP().toString());
|
||||
_out->print(':');
|
||||
_out->println(request->client()->remotePort());
|
||||
_out->print('>');
|
||||
_out->print(' ');
|
||||
_out->print(request->methodToString());
|
||||
_out->print(' ');
|
||||
_out->print(request->url().c_str());
|
||||
_out->print(F(" HTTP/1."));
|
||||
_out->println(request->version());
|
||||
for (auto& h : request->getHeaders()) {
|
||||
if (h.value().length()) {
|
||||
_out->print('>');
|
||||
_out->print(' ');
|
||||
_out->print(h.name());
|
||||
_out->print(':');
|
||||
_out->print(' ');
|
||||
_out->println(h.value());
|
||||
}
|
||||
}
|
||||
_out->println(F(">"));
|
||||
uint32_t elapsed = millis();
|
||||
next();
|
||||
elapsed = millis() - elapsed;
|
||||
AsyncWebServerResponse* response = request->getResponse();
|
||||
if (response) {
|
||||
_out->print(F("* Processed in "));
|
||||
_out->print(elapsed);
|
||||
_out->println(F(" ms"));
|
||||
_out->print('<');
|
||||
_out->print(F(" HTTP/1."));
|
||||
_out->print(request->version());
|
||||
_out->print(' ');
|
||||
_out->print(response->code());
|
||||
_out->print(' ');
|
||||
_out->println(AsyncWebServerResponse::responseCodeToString(response->code()));
|
||||
for (auto& h : response->getHeaders()) {
|
||||
if (h.value().length()) {
|
||||
_out->print('<');
|
||||
_out->print(' ');
|
||||
_out->print(h.name());
|
||||
_out->print(':');
|
||||
_out->print(' ');
|
||||
_out->println(h.value());
|
||||
}
|
||||
}
|
||||
_out->println('<');
|
||||
} else {
|
||||
_out->println(F("* Connection closed!"));
|
||||
}
|
||||
}
|
||||
|
||||
void CorsMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
if (request->method() == HTTP_OPTIONS && request->hasHeader(F("Origin"))) {
|
||||
AsyncWebServerResponse* response = request->beginResponse(200);
|
||||
response->addHeader(F("Access-Control-Allow-Origin"), _origin.c_str());
|
||||
response->addHeader(F("Access-Control-Allow-Methods"), _methods.c_str());
|
||||
response->addHeader(F("Access-Control-Allow-Headers"), _headers.c_str());
|
||||
response->addHeader(F("Access-Control-Allow-Credentials"), _credentials ? F("true") : F("false"));
|
||||
response->addHeader(F("Access-Control-Max-Age"), String(_maxAge).c_str());
|
||||
request->send(response);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
void RateLimitMiddleware::run(AsyncWebServerRequest* request, ArMiddlewareNext next) {
|
||||
uint32_t retryAfterSeconds;
|
||||
if (isRequestAllowed(retryAfterSeconds)) {
|
||||
next();
|
||||
} else {
|
||||
AsyncWebServerResponse* response = request->beginResponse(429);
|
||||
response->addHeader(F("Retry-After"), retryAfterSeconds);
|
||||
request->send(response);
|
||||
}
|
||||
}
|
@@ -129,22 +129,16 @@ class AsyncCallbackWebHandler : public AsyncWebHandler {
|
||||
}
|
||||
|
||||
virtual void handleRequest(AsyncWebServerRequest* request) override final {
|
||||
if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
if (_onRequest)
|
||||
_onRequest(request);
|
||||
else
|
||||
request->send(500);
|
||||
}
|
||||
virtual void handleUpload(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) override final {
|
||||
if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
if (_onUpload)
|
||||
_onUpload(request, filename, index, data, len, final);
|
||||
}
|
||||
virtual void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final {
|
||||
if ((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
if (_onBody)
|
||||
_onBody(request, data, len, index, total);
|
||||
}
|
||||
|
@@ -192,8 +192,6 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) {
|
||||
String filename = String((char*)request->_tempObject);
|
||||
free(request->_tempObject);
|
||||
request->_tempObject = NULL;
|
||||
if ((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
|
||||
if (request->_tempFile == true) {
|
||||
time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS)
|
||||
|
@@ -138,8 +138,7 @@ void AsyncWebServerRequest::_onData(void* buf, size_t len) {
|
||||
}
|
||||
if (_parsedLength == _contentLength) {
|
||||
_parseState = PARSE_REQ_END;
|
||||
if (_handler)
|
||||
_handler->handleRequest(this);
|
||||
_server->_runChain(this, [this]() { return _handler ? _handler->_runChain(this, [this]() { _handler->handleRequest(this); }) : send(501); });
|
||||
if (!_sent) {
|
||||
if (!_response)
|
||||
send(501, T_text_plain, "Handler did not handle the request");
|
||||
@@ -577,8 +576,7 @@ void AsyncWebServerRequest::_parseLine() {
|
||||
_parseState = PARSE_REQ_BODY;
|
||||
} else {
|
||||
_parseState = PARSE_REQ_END;
|
||||
if (_handler)
|
||||
_handler->handleRequest(this);
|
||||
_server->_runChain(this, [this]() { return _handler ? _handler->_runChain(this, [this]() { _handler->handleRequest(this); }) : send(501); });
|
||||
if (!_sent) {
|
||||
if (!_response)
|
||||
send(501, T_text_plain, "Handler did not handle the request");
|
||||
|
@@ -109,6 +109,8 @@ const char* AsyncWebServerResponse::responseCodeToString(int code) {
|
||||
return T_HTTP_CODE_416;
|
||||
case 417:
|
||||
return T_HTTP_CODE_417;
|
||||
case 429:
|
||||
return T_HTTP_CODE_429;
|
||||
case 500:
|
||||
return T_HTTP_CODE_500;
|
||||
case 501:
|
||||
@@ -196,6 +198,8 @@ const __FlashStringHelper* AsyncWebServerResponse::responseCodeToString(int code
|
||||
return FPSTR(T_HTTP_CODE_416);
|
||||
case 417:
|
||||
return FPSTR(T_HTTP_CODE_417);
|
||||
case 429:
|
||||
return FPSTR(T_HTTP_CODE_429);
|
||||
case 500:
|
||||
return FPSTR(T_HTTP_CODE_500);
|
||||
case 501:
|
||||
|
@@ -137,6 +137,7 @@ static constexpr const char* T_HTTP_CODE_414 = "Request-URI Too Large";
|
||||
static constexpr const char* T_HTTP_CODE_415 = "Unsupported Media Type";
|
||||
static constexpr const char* T_HTTP_CODE_416 = "Requested range not satisfiable";
|
||||
static constexpr const char* T_HTTP_CODE_417 = "Expectation Failed";
|
||||
static constexpr const char* T_HTTP_CODE_429 = "Too Many Requests";
|
||||
static constexpr const char* T_HTTP_CODE_500 = "Internal Server Error";
|
||||
static constexpr const char* T_HTTP_CODE_501 = "Not Implemented";
|
||||
static constexpr const char* T_HTTP_CODE_502 = "Bad Gateway";
|
||||
@@ -306,6 +307,7 @@ static const char T_HTTP_CODE_414[] PROGMEM = "Request-URI Too Large";
|
||||
static const char T_HTTP_CODE_415[] PROGMEM = "Unsupported Media Type";
|
||||
static const char T_HTTP_CODE_416[] PROGMEM = "Requested range not satisfiable";
|
||||
static const char T_HTTP_CODE_417[] PROGMEM = "Expectation Failed";
|
||||
static const char T_HTTP_CODE_429[] PROGMEM = "Too Many Requests";
|
||||
static const char T_HTTP_CODE_500[] PROGMEM = "Internal Server Error";
|
||||
static const char T_HTTP_CODE_501[] PROGMEM = "Not Implemented";
|
||||
static const char T_HTTP_CODE_502[] PROGMEM = "Bad Gateway";
|
||||
|
Reference in New Issue
Block a user