minor uodates to routing

This commit is contained in:
Proddy
2023-12-26 15:52:46 +01:00
parent 73a51ae4ad
commit 23d4f608c5
18 changed files with 231 additions and 742 deletions

View File

@@ -1,258 +0,0 @@
#ifndef ASYNC_JSON_H_
#define ASYNC_JSON_H_
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#define DYNAMIC_JSON_DOCUMENT_SIZE 1024
constexpr const char * JSON_MIMETYPE = "application/json";
class ChunkPrint : public Print {
private:
uint8_t * _destination;
size_t _to_skip;
size_t _to_write;
size_t _pos;
public:
ChunkPrint(uint8_t * destination, size_t from, size_t len)
: _destination(destination)
, _to_skip(from)
, _to_write(len)
, _pos{0} {
}
virtual ~ChunkPrint() {
}
size_t write(uint8_t c) {
if (_to_skip > 0) {
_to_skip--;
return 1;
} else if (_to_write > 0) {
_to_write--;
_destination[_pos++] = c;
return 1;
}
return 0;
}
size_t write(const uint8_t * buffer, size_t size) {
return this->Print::write(buffer, size);
}
};
class PrettyAsyncJsonResponse {
protected:
DynamicJsonDocument _jsonBuffer;
JsonVariant _root;
bool _isValid;
public:
PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
: _jsonBuffer(maxJsonBufferSize)
, _isValid{false} {
if (isArray)
_root = _jsonBuffer.createNestedArray();
else
_root = _jsonBuffer.createNestedObject();
}
~PrettyAsyncJsonResponse() {
}
JsonVariant & getRoot() {
return _root;
}
bool _sourceValid() const {
return _isValid;
}
size_t setLength() {
return 0;
}
void setContentType(const char * s) {
}
size_t getSize() {
return _jsonBuffer.size();
}
size_t _fillBuffer(uint8_t * data, size_t len) {
return len;
}
void setCode(uint16_t) {
}
};
class MsgpackAsyncJsonResponse {
protected:
DynamicJsonDocument _jsonBuffer;
JsonVariant _root;
bool _isValid;
public:
MsgpackAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
: _jsonBuffer(maxJsonBufferSize)
, _isValid{false} {
if (isArray)
_root = _jsonBuffer.createNestedArray();
else
_root = _jsonBuffer.createNestedObject();
}
~MsgpackAsyncJsonResponse() {
}
JsonVariant & getRoot() {
return _root;
}
bool _sourceValid() const {
return _isValid;
}
size_t setLength() {
return 0;
}
void setContentType(const char * s) {
}
size_t getSize() {
return _jsonBuffer.size();
}
size_t _fillBuffer(uint8_t * data, size_t len) {
return len;
}
void setCode(uint16_t) {
}
};
class AsyncJsonResponse {
protected:
DynamicJsonDocument _jsonBuffer;
JsonVariant _root;
bool _isValid;
public:
AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
: _jsonBuffer(maxJsonBufferSize)
, _isValid{false} {
if (isArray)
_root = _jsonBuffer.createNestedArray();
else
_root = _jsonBuffer.createNestedObject();
}
~AsyncJsonResponse() {
}
JsonVariant & getRoot() {
return _root;
}
bool _sourceValid() const {
return _isValid;
}
size_t setLength() {
return 0;
}
size_t getSize() {
return _jsonBuffer.size();
}
size_t _fillBuffer(uint8_t * data, size_t len) {
return len;
}
void setCode(uint16_t) {
}
};
typedef std::function<void(AsyncWebServerRequest * request, JsonVariant & json)> ArJsonRequestHandlerFunction;
class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
private:
protected:
const String _uri;
WebRequestMethodComposite _method;
ArJsonRequestHandlerFunction _onRequest;
size_t _contentLength;
size_t _maxContentLength;
size_t _maxJsonBufferSize;
public:
AsyncCallbackJsonWebHandler(const String & uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE)
: _uri(uri)
, _method(HTTP_POST | HTTP_PUT | HTTP_PATCH)
, _onRequest(onRequest)
, _maxContentLength(16384) {
}
void setMethod(WebRequestMethodComposite method) {
_method = method;
}
void setMaxContentLength(int maxContentLength) {
_maxContentLength = maxContentLength;
}
void setMaxJsonBufferSize(int maxJsonBufferSize) {
_maxJsonBufferSize = maxJsonBufferSize;
}
void onRequest(ArJsonRequestHandlerFunction fn) {
_onRequest = fn;
}
virtual bool canHandle(AsyncWebServerRequest * request) override final {
if (!_onRequest)
return false;
if (!(_method & request->method()))
return false;
request->addInterestingHeader("ANY");
return true;
}
virtual void handleRequest(AsyncWebServerRequest * request) override final {
if (_onRequest) {
if (request->_tempObject != NULL) {
DynamicJsonDocument jsonBuffer(1024);
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject));
if (!error) {
JsonVariant json = jsonBuffer.as<JsonVariant>();
_onRequest(request, json);
return;
}
}
request->send(_contentLength > _maxContentLength ? 413 : 400);
} 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 {
}
virtual void handleBody(AsyncWebServerRequest * request, uint8_t * data, size_t len, size_t index, size_t total) override final {
if (_onRequest) {
_contentLength = total;
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
request->_tempObject = malloc(total);
}
if (request->_tempObject != NULL) {
memcpy((uint8_t *)(request->_tempObject) + index, data, len);
}
}
}
virtual bool isRequestHandlerTrivial() override final {
return _onRequest ? false : true;
}
};
#endif

View File

@@ -1,13 +1,10 @@
#ifndef ESP8266React_h
#define ESP8266React_h
#include <Arduino.h>
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <espMqttClient.h>
#include <ESPAsyncWebServer.h>
#include <list>
@@ -146,7 +143,7 @@ class ESP8266React {
class EMSESPSettingsService {
public:
EMSESPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
EMSESPSettingsService(PsychicHttpServer * server, FS * fs, SecurityManager * securityManager);
void begin();
};

View File

@@ -1,251 +0,0 @@
#ifndef _ESPAsyncWebServer_H_
#define _ESPAsyncWebServer_H_
#include "Arduino.h"
#include <functional>
#include <AsyncTCP.h>
#include <ArduinoJson.h>
class AsyncWebServer;
class AsyncWebServerRequest;
class AsyncWebServerResponse;
class AsyncJsonResponse;
class PrettyAsyncJsonResponse;
class MsgpackAsyncJsonResponse;
class AsyncEventSource;
class AsyncWebParameter {
private:
String _name;
String _value;
size_t _size;
bool _isForm;
bool _isFile;
public:
AsyncWebParameter(const String & name, const String & value, bool form = false, bool file = false, size_t size = 0)
: _name(name)
, _value(value)
, _size(size)
, _isForm(form)
, _isFile(file) {
}
const String & name() const {
return _name;
}
const String & value() const {
return _value;
}
size_t size() const {
return _size;
}
bool isPost() const {
return _isForm;
}
bool isFile() const {
return _isFile;
}
};
typedef enum {
HTTP_GET = 0b00000001,
HTTP_POST = 0b00000010,
HTTP_DELETE = 0b00000100,
HTTP_PUT = 0b00001000,
HTTP_PATCH = 0b00010000,
HTTP_HEAD = 0b00100000,
HTTP_OPTIONS = 0b01000000,
HTTP_ANY = 0b01111111,
} WebRequestMethod;
typedef uint8_t WebRequestMethodComposite;
typedef std::function<void(void)> ArDisconnectHandler;
class AsyncWebServerRequest {
friend class AsyncWebServer;
friend class AsyncCallbackWebHandler;
private:
AsyncClient * _client;
AsyncWebServer * _server;
WebRequestMethodComposite _method;
String _url;
public:
void * _tempObject;
AsyncWebServerRequest(AsyncWebServer *, AsyncClient *){};
AsyncWebServerRequest(){};
~AsyncWebServerRequest(){};
AsyncClient * client() {
return _client;
}
WebRequestMethodComposite method() const {
return _method;
}
void method(WebRequestMethodComposite method_s) {
_method = method_s;
}
void addInterestingHeader(const String & name){};
size_t args() const {
return 0;
}
void send(AsyncWebServerResponse * response){};
void send(AsyncJsonResponse * response){};
void send(PrettyAsyncJsonResponse * response){};
void send(MsgpackAsyncJsonResponse * response){};
void send(int code, const String & contentType = String(), const String & content = String()){};
void send(int code, const String & contentType, const __FlashStringHelper *){};
const String & url() const {
return _url;
}
void url(const String & url_s) {
_url = url_s;
}
bool hasParam(const String & name, bool post, bool file) const {
return false;
}
bool hasParam(const char * name, bool post, bool file) const {
return false;
}
bool hasParam(const char * name) const {
return false;
}
bool hasParam(const __FlashStringHelper * data) const {
return false;
}
bool hasParam(const __FlashStringHelper * data, bool post, bool file) const {
return false;
}
AsyncWebParameter * getParam(const String & name, bool post, bool file) const {
return nullptr;
}
AsyncWebParameter * getParam(const __FlashStringHelper * data, bool post, bool file) const {
return nullptr;
}
AsyncWebParameter * getParam(const __FlashStringHelper * data) const {
return nullptr;
}
AsyncWebParameter * getParam(const char * name) const {
return nullptr;
}
AsyncWebParameter * getParam(size_t num) const {
return nullptr;
}
AsyncWebServerResponse * beginResponse(int code, const String & contentType = String(), const String & content = String()) {
return nullptr;
}
size_t headers() const; // get header count
size_t params() const; // get arguments count
};
typedef std::function<bool(AsyncWebServerRequest * request)> ArRequestFilterFunction;
class AsyncWebHandler {
protected:
String _username;
String _password;
public:
AsyncWebHandler()
: _username("")
, _password("") {
}
virtual ~AsyncWebHandler() {
}
virtual bool canHandle(AsyncWebServerRequest * request __attribute__((unused))) {
return false;
}
virtual void handleRequest(AsyncWebServerRequest * request __attribute__((unused))) {
}
virtual void handleUpload(AsyncWebServerRequest * request __attribute__((unused)),
const String & filename __attribute__((unused)),
size_t index __attribute__((unused)),
uint8_t * data __attribute__((unused)),
size_t len __attribute__((unused)),
bool final __attribute__((unused))) {
}
virtual void handleBody(AsyncWebServerRequest * request __attribute__((unused)),
uint8_t * data __attribute__((unused)),
size_t len __attribute__((unused)),
size_t index __attribute__((unused)),
size_t total __attribute__((unused))) {
}
virtual bool isRequestHandlerTrivial() {
return true;
}
AsyncWebHandler & setFilter(ArRequestFilterFunction fn) {
return *this;
}
};
class AsyncWebServerResponse {
public:
AsyncWebServerResponse();
virtual ~AsyncWebServerResponse();
};
typedef std::function<void(AsyncWebServerRequest * request)> ArRequestHandlerFunction;
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 {
protected:
AsyncServer _server;
public:
AsyncWebServer(uint16_t port)
: _server(port){};
~AsyncWebServer(){};
void begin(){};
void end();
AsyncWebHandler & addHandler(AsyncWebHandler * handler) {
return *handler;
}
void on(const char * uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){};
};
class AsyncEventSource : public AsyncWebHandler {
public:
AsyncEventSource(const String & url){};
~AsyncEventSource(){};
size_t count() const {
return 1;
}
void send(const char * message, const char * event = NULL, uint32_t id = 0, uint32_t reconnect = 0){};
};
#endif

View File

@@ -3,45 +3,55 @@
#include <functional>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <PsychicHttp.h>
#include <SecurityManager.h>
#include <StatefulService.h>
#define HTTP_ENDPOINT_ORIGIN_ID "http"
#define HTTPS_ENDPOINT_ORIGIN_ID "https"
using namespace std::placeholders; // for `_1` etc
template <class T>
class HttpGetEndpoint {
public:
HttpGetEndpoint(JsonStateReader<T> stateReader,
StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
PsychicHttpServer * server,
const char * servicePath,
SecurityManager * securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: _stateReader(stateReader)
, _statefulService(statefulService)
, _bufferSize(bufferSize) {
}
HttpGetEndpoint(JsonStateReader<T> stateReader,
StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: _stateReader(stateReader)
, _statefulService(statefulService)
, _bufferSize(bufferSize) {
, _bufferSize(bufferSize)
, _server(server)
, _servicePath(servicePath)
, _securityManager(securityManager)
, _authenticationPredicate(authenticationPredicate) {
}
protected:
JsonStateReader<T> _stateReader;
StatefulService<T> * _statefulService;
size_t _bufferSize;
JsonStateReader<T> _stateReader;
StatefulService<T> * _statefulService;
size_t _bufferSize;
PsychicHttpServer * _server;
const char * _servicePath;
SecurityManager * _securityManager;
AuthenticationPredicate _authenticationPredicate;
void fetchSettings(AsyncWebServerRequest * request) {
void registerURI() {
_server->on(_servicePath,
HTTP_GET,
_securityManager->wrapRequest(
[this](PsychicRequest * request) {
PsychicJsonResponse response = PsychicJsonResponse(request, false, _bufferSize);
JsonObject jsonObject = response.getRoot();
_statefulService->read(jsonObject, _stateReader);
return response.send();
},
_authenticationPredicate));
}
};
@@ -51,46 +61,64 @@ class HttpPostEndpoint {
HttpPostEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
PsychicHttpServer * server,
const char * servicePath,
SecurityManager * securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: _stateReader(stateReader)
, _stateUpdater(stateUpdater)
, _statefulService(statefulService)
, _server(server)
, _servicePath(servicePath)
, _securityManager(securityManager)
, _authenticationPredicate(authenticationPredicate)
, _bufferSize(bufferSize) {
}
HttpPostEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: _stateReader(stateReader)
, _stateUpdater(stateUpdater)
, _statefulService(statefulService)
, _bufferSize(bufferSize) {
}
protected:
JsonStateReader<T> _stateReader;
JsonStateUpdater<T> _stateUpdater;
StatefulService<T> * _statefulService;
size_t _bufferSize;
JsonStateReader<T> _stateReader;
JsonStateUpdater<T> _stateUpdater;
StatefulService<T> * _statefulService;
size_t _bufferSize;
SecurityManager * _securityManager;
AuthenticationPredicate _authenticationPredicate;
PsychicHttpServer * _server;
const char * _servicePath;
void updateSettings(AsyncWebServerRequest * request, JsonVariant & json) {
if (!json.is<JsonObject>()) {
return;
}
JsonObject jsonObject = json.as<JsonObject>();
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
if (outcome == StateUpdateResult::ERROR) {
return;
}
if (outcome == StateUpdateResult::CHANGED) {
}
void registerURI() {
_server->on(_servicePath,
HTTP_POST,
_securityManager->wrapCallback(
[this](PsychicRequest * request, JsonVariant & json) {
if (!json.is<JsonObject>()) {
return request->reply(400);
}
JsonObject jsonObject = json.as<JsonObject>();
StateUpdateResult outcome = _statefulService->updateWithoutPropagation(jsonObject, _stateUpdater);
if (outcome == StateUpdateResult::ERROR) {
return request->reply(400);
} else if ((outcome == StateUpdateResult::CHANGED) || (outcome == StateUpdateResult::CHANGED_RESTART)) {
// TODO see if this works as intended. Before the stat was updated on an onDisconnect
// request->onDisconnect([this]() { _statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); });
// TODO add support for https
_statefulService->callUpdateHandlers(HTTP_ENDPOINT_ORIGIN_ID); // persist the changes to the FS
}
PsychicJsonResponse response = PsychicJsonResponse(request, false, _bufferSize);
jsonObject = response.getRoot();
_statefulService->read(jsonObject, _stateReader);
if (outcome == StateUpdateResult::CHANGED_RESTART) {
return request->reply(205); // reboot required
}
return response.send();
},
_authenticationPredicate));
}
};
@@ -100,8 +128,8 @@ class HttpEndpoint : public HttpGetEndpoint<T>, public HttpPostEndpoint<T> {
HttpEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
PsychicHttpServer * server,
const char * servicePath,
SecurityManager * securityManager,
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
@@ -109,14 +137,10 @@ class HttpEndpoint : public HttpGetEndpoint<T>, public HttpPostEndpoint<T> {
, HttpPostEndpoint<T>(stateReader, stateUpdater, statefulService, server, servicePath, securityManager, authenticationPredicate, bufferSize) {
}
HttpEndpoint(JsonStateReader<T> stateReader,
JsonStateUpdater<T> stateUpdater,
StatefulService<T> * statefulService,
AsyncWebServer * server,
const String & servicePath,
size_t bufferSize = DEFAULT_BUFFER_SIZE)
: HttpGetEndpoint<T>(stateReader, statefulService, server, servicePath, bufferSize)
, HttpPostEndpoint<T>(stateReader, stateUpdater, statefulService, server, servicePath, bufferSize) {
// register the web server on() endpoints
void registerURI() {
HttpGetEndpoint<T>::registerURI();
HttpPostEndpoint<T>::registerURI();
}
};

View File

@@ -3,7 +3,6 @@
#include <Arduino.h>
#include <Features.h>
#include <ESPAsyncWebServer.h>
#include <list>
#ifndef FACTORY_JWT_SECRET
@@ -72,11 +71,6 @@ class SecurityManager {
virtual Authentication authenticate(const String & username, const String & password) = 0;
virtual String generateJWT(User * user) = 0;
#endif
virtual Authentication authenticateRequest(AsyncWebServerRequest * request) = 0;
virtual ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0;
virtual ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0;
virtual ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction onRequest, AuthenticationPredicate predicate) = 0;
};
#endif