From e68b7627f2751f5366ab3870709a0bdffe0ad7de Mon Sep 17 00:00:00 2001 From: rjwats Date: Sun, 14 Apr 2019 08:52:40 +0100 Subject: [PATCH] Arduinojson6 (#8) * Remove redundant AuthSettingsService. Will re-implement properly soon using JWT. * Support ArduinoJson >= 6.0.0 * Fix ArduinoJson version to 6.x.x --- platformio.ini | 2 +- src/APStatus.cpp | 4 +- src/APStatus.h | 3 +- src/AsyncArduinoJson6.h | 167 +++++++++++++++++++++++++++++++ src/AsyncJsonCallbackResponse.h | 31 ------ src/AsyncJsonRequestWebHandler.h | 10 +- src/AuthSettingsService.cpp | 47 --------- src/AuthSettingsService.h | 56 ----------- src/NTPStatus.cpp | 4 +- src/NTPStatus.h | 3 +- src/SettingsPersistence.h | 22 ++-- src/SettingsService.h | 21 ++-- src/SimpleService.h | 23 +++-- src/WiFiScanner.cpp | 8 +- src/WiFiScanner.h | 4 +- src/WiFiStatus.cpp | 4 +- src/WiFiStatus.h | 3 +- 17 files changed, 226 insertions(+), 186 deletions(-) create mode 100644 src/AsyncArduinoJson6.h delete mode 100644 src/AsyncJsonCallbackResponse.h delete mode 100644 src/AuthSettingsService.cpp delete mode 100644 src/AuthSettingsService.h diff --git a/platformio.ini b/platformio.ini index 1e427fe..ffe5b99 100644 --- a/platformio.ini +++ b/platformio.ini @@ -23,6 +23,6 @@ build_flags= lib_deps = https://github.com/PaulStoffregen/Time https://github.com/gmag11/NtpClient - ArduinoJson@>=5.0.0,<6.0.0 + ArduinoJson@>=6.0.0,<7.0.0 https://github.com/me-no-dev/ESPAsyncWebServer https://github.com/me-no-dev/AsyncTCP diff --git a/src/APStatus.cpp b/src/APStatus.cpp index ef65402..35a7b4f 100644 --- a/src/APStatus.cpp +++ b/src/APStatus.cpp @@ -5,8 +5,8 @@ APStatus::APStatus(AsyncWebServer *server) : _server(server) { } void APStatus::apStatus(AsyncWebServerRequest *request) { - AsyncJsonResponse * response = new AsyncJsonResponse(); - JsonObject& root = response->getRoot(); + AsyncJsonResponse * response = new AsyncJsonResponse(MAX_AP_STATUS_SIZE); + JsonObject root = response->getRoot(); WiFiMode_t currentWiFiMode = WiFi.getMode(); root["active"] = (currentWiFiMode == WIFI_AP || currentWiFiMode == WIFI_AP_STA); diff --git a/src/APStatus.h b/src/APStatus.h index 044173d..8c32fc8 100644 --- a/src/APStatus.h +++ b/src/APStatus.h @@ -11,9 +11,10 @@ #include #include -#include +#include #include +#define MAX_AP_STATUS_SIZE 1024 #define AP_STATUS_SERVICE_PATH "/rest/apStatus" class APStatus { diff --git a/src/AsyncArduinoJson6.h b/src/AsyncArduinoJson6.h new file mode 100644 index 0000000..f075d46 --- /dev/null +++ b/src/AsyncArduinoJson6.h @@ -0,0 +1,167 @@ + +/** +* A copy of AsyncJson.h from ESPAsyncWebServer, updated for ArduinoJson6. +*/ + +#ifndef ASYNC_ARDUINO_JSON_6_H +#define ASYNC_ARDUINO_JSON_6_H + +#include +#include +#include + +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) { + size_t written = 0; + while (written < size && write(buffer[written])) { + written++; + } + return written; + } +}; + +class AsyncJsonResponse: public AsyncAbstractResponse { + private: + DynamicJsonDocument _jsonDocument; + bool _isValid; + JsonObject _root; + + public: + AsyncJsonResponse(int maxSize): _jsonDocument(maxSize), _isValid{false} { + _code = 200; + _contentType = JSON_MIMETYPE; + _root = _jsonDocument.to(); + } + ~AsyncJsonResponse() {} + JsonObject getRoot() { + return _root; + } + bool _sourceValid() const { + return _isValid; + } + size_t setLength() { + _contentLength = measureJson(_jsonDocument); + if (_contentLength) { _isValid = true; } + return _contentLength; + } + size_t getSize() { + return _jsonDocument.size(); + } + size_t _fillBuffer(uint8_t *data, size_t len){ + ChunkPrint dest(data, _sentLength, len); + serializeJson(_jsonDocument, dest); + return len; + } +}; + +typedef std::function ArJsonRequestHandlerFunction; + +class AsyncCallbackJsonWebHandler: public AsyncWebHandler { +private: +protected: + const String _uri; + WebRequestMethodComposite _method; + ArJsonRequestHandlerFunction _onRequest; + size_t _contentLength; + size_t _maxContentLength; +public: + AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest) : _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 onRequest(ArJsonRequestHandlerFunction fn){ _onRequest = fn; } + virtual bool canHandle(AsyncWebServerRequest *request) override final{ + if(!_onRequest) + return false; + + if(!(_method & request->method())) + return false; + + if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/"))) + return false; + + if (!request->contentType().equalsIgnoreCase(JSON_MIMETYPE)) + return false; + + request->addInterestingHeader("ANY"); + return true; + } + virtual void handleRequest(AsyncWebServerRequest *request) override final { + if(_onRequest) { + if (request->_tempObject != NULL) { + DynamicJsonDocument _jsonDocument(_maxContentLength); + DeserializationError err = deserializeJson(_jsonDocument, (uint8_t*)(request->_tempObject)); + if (err == DeserializationError::Ok) { + _onRequest(request, _jsonDocument.as()); + 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;} +}; + + +/* +* Listens for a response being destroyed and calls a callback during said distruction. +* +* Used so we can take action after the response has been rendered to the client. +* +* Avoids having to fork ESPAsyncWebServer with a callback feature - still not a nice use of a destructor! +*/ + +typedef std::function AsyncJsonCallback; + +class AsyncJsonCallbackResponse: public AsyncJsonResponse { + + private: + + AsyncJsonCallback _callback; + + public: + + AsyncJsonCallbackResponse(AsyncJsonCallback callback, int maxSize) : AsyncJsonResponse(maxSize), _callback{callback} {} + ~AsyncJsonCallbackResponse() { + _callback(); + } + +}; + +#endif diff --git a/src/AsyncJsonCallbackResponse.h b/src/AsyncJsonCallbackResponse.h deleted file mode 100644 index b384f69..0000000 --- a/src/AsyncJsonCallbackResponse.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef _AsyncJsonCallbackResponse_H_ -#define _AsyncJsonCallbackResponse_H_ - -#include -#include - -/* -* Listens for a response being destroyed and calls a callback during said distruction. -* used so we can take action after the response has been rendered to the client. -* -* Avoids having to fork ESPAsyncWebServer with a callback feature, but not nice! -*/ - -typedef std::function AsyncJsonCallback; - -class AsyncJsonCallbackResponse: public AsyncJsonResponse { - - private: - - AsyncJsonCallback _callback; - - public: - - AsyncJsonCallbackResponse(AsyncJsonCallback callback, bool isArray=false) : AsyncJsonResponse(isArray), _callback{callback} {} - ~AsyncJsonCallbackResponse() { - _callback(); - } - -}; - -#endif // end _AsyncJsonCallbackResponse_H_ diff --git a/src/AsyncJsonRequestWebHandler.h b/src/AsyncJsonRequestWebHandler.h index ddfdba7..aa8498a 100644 --- a/src/AsyncJsonRequestWebHandler.h +++ b/src/AsyncJsonRequestWebHandler.h @@ -14,7 +14,7 @@ * Really only of use where there is a determinate payload size. */ -typedef std::function JsonRequestCallback; +typedef std::function JsonRequestCallback; class AsyncJsonRequestWebHandler: public AsyncWebHandler { @@ -72,10 +72,10 @@ class AsyncJsonRequestWebHandler: public AsyncWebHandler { // parse JSON and if possible handle the request if (request->_tempObject) { - DynamicJsonBuffer jsonBuffer; - JsonVariant json = jsonBuffer.parse((uint8_t *) request->_tempObject); - if (json.success()) { - _onRequest(request, json); + DynamicJsonDocument jsonDocument(_maxContentLength); + DeserializationError error = deserializeJson(jsonDocument, (uint8_t *) request->_tempObject); + if (error == DeserializationError::Ok) { + _onRequest(request, jsonDocument); }else{ request->send(400); } diff --git a/src/AuthSettingsService.cpp b/src/AuthSettingsService.cpp deleted file mode 100644 index 0b8645c..0000000 --- a/src/AuthSettingsService.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include - -AuthSettingsService::AuthSettingsService(AsyncWebServer* server, FS* fs) : SettingsService(server, fs, AUTH_SETTINGS_SERVICE_PATH, AUTH_SETTINGS_FILE) { - _server->on(AUTH_LOGOUT_SERVICE_PATH, HTTP_GET, std::bind(&AuthSettingsService::logout, this, std::placeholders::_1)); - - // configure authentication handler - _authenticationHandler.setUri(AUTH_AUTHENTICATE_SERVICE_PATH); - _authenticationHandler.setMethod(HTTP_POST); - _authenticationHandler.onRequest(std::bind(&AuthSettingsService::authenticate, this, std::placeholders::_1, std::placeholders::_2)); - _server->addHandler(&_authenticationHandler); -} - -AuthSettingsService::~AuthSettingsService() {} - -// checks the session is authenticated, refreshes the sessions timeout if so -bool AuthSettingsService::authenticated(AsyncWebServerRequest *request){ - request->send(400); - return false; -} - -void AuthSettingsService::readFromJsonObject(JsonObject& root){ - _username = root["username"] | AUTH_DEFAULT_USERNAME; - _password = root["password"] | AUTH_DEFAULT_PASSWORD; - _sessionTimeout= root["session_timeout"] | AUTH_DEFAULT_SESSION_TIMEOUT; -} - -void AuthSettingsService::writeToJsonObject(JsonObject& root){ - root["username"] = _username; - root["password"] = _password; - root["session_timeout"] = _sessionTimeout; -} - -void AuthSettingsService::logout(AsyncWebServerRequest *request){ - // revoke the current requests session -} - -void AuthSettingsService::authenticate(AsyncWebServerRequest *request, JsonVariant &json){ - if (json.is()){ - JsonObject& credentials = json.as(); - if (credentials["username"] == _username && credentials["password"] == _password){ - // store cookie and write to response - } - request->send(401); - } else{ - request->send(400); - } -} diff --git a/src/AuthSettingsService.h b/src/AuthSettingsService.h deleted file mode 100644 index cdbea15..0000000 --- a/src/AuthSettingsService.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef AuthSettingsService_h -#define AuthSettingsService_h - -#include - -#define AUTH_DEFAULT_USERNAME "admin" -#define AUTH_DEFAULT_PASSWORD "admin" -#define AUTH_DEFAULT_SESSION_TIMEOUT 3600 - -#define AUTH_SETTINGS_FILE "/config/authSettings.json" - -#define AUTH_SETTINGS_SERVICE_PATH "/rest/authSettings" -#define AUTH_LOGOUT_SERVICE_PATH "/rest/logout" -#define AUTH_AUTHENTICATE_SERVICE_PATH "/rest/authenticate" - -// max number of concurrently authenticated clients -#define AUTH_MAX_CLIENTS 10 - -/* -* TODO: Will protect services with a cookie based authentication service. -*/ - -class AuthSettingsService : public SettingsService { - - public: - - AuthSettingsService(AsyncWebServer* server, FS* fs); - ~AuthSettingsService(); - - // checks the session is authenticated, - // refreshes the sessions timeout if found - bool authenticated(AsyncWebServerRequest *request); - - protected: - - void readFromJsonObject(JsonObject& root); - void writeToJsonObject(JsonObject& root); - - private: - - // callback handler for authentication endpoint - AsyncJsonRequestWebHandler _authenticationHandler; - - // only supporting one username at the moment - String _username; - String _password; - - // session timeout in seconds - unsigned int _sessionTimeout; - - void logout(AsyncWebServerRequest *request); - void authenticate(AsyncWebServerRequest *request, JsonVariant &json); - -}; - -#endif // end AuthSettingsService_h diff --git a/src/NTPStatus.cpp b/src/NTPStatus.cpp index f4162a9..50dbc37 100644 --- a/src/NTPStatus.cpp +++ b/src/NTPStatus.cpp @@ -5,8 +5,8 @@ NTPStatus::NTPStatus(AsyncWebServer *server) : _server(server) { } void NTPStatus::ntpStatus(AsyncWebServerRequest *request) { - AsyncJsonResponse * response = new AsyncJsonResponse(); - JsonObject& root = response->getRoot(); + AsyncJsonResponse * response = new AsyncJsonResponse(MAX_NTP_STATUS_SIZE); + JsonObject root = response->getRoot(); // request time now first, this can sometimes force a sync time_t timeNow = now(); diff --git a/src/NTPStatus.h b/src/NTPStatus.h index 5451efd..086407f 100644 --- a/src/NTPStatus.h +++ b/src/NTPStatus.h @@ -11,10 +11,11 @@ #include #include -#include +#include #include #include +#define MAX_NTP_STATUS_SIZE 1024 #define NTP_STATUS_SERVICE_PATH "/rest/ntpStatus" class NTPStatus { diff --git a/src/SettingsPersistence.h b/src/SettingsPersistence.h index 0dd1604..a0f0ec4 100644 --- a/src/SettingsPersistence.h +++ b/src/SettingsPersistence.h @@ -3,10 +3,9 @@ #include #include -#include #include #include -#include +#include /** * At the moment, not expecting settings service to have to deal with large JSON @@ -30,8 +29,8 @@ protected: bool writeToFS() { // create and populate a new json object - DynamicJsonBuffer jsonBuffer; - JsonObject& root = jsonBuffer.createObject(); + DynamicJsonDocument jsonDocument = DynamicJsonDocument(MAX_SETTINGS_SIZE); + JsonObject root = jsonDocument.to(); writeToJsonObject(root); // serialize it to filesystem @@ -42,7 +41,7 @@ protected: return false; } - root.printTo(configFile); + serializeJson(jsonDocument, configFile); configFile.close(); return true; @@ -57,12 +56,13 @@ protected: // We never expect the config file to get very large, so cap it. size_t size = configFile.size(); if (size <= MAX_SETTINGS_SIZE) { - DynamicJsonBuffer jsonBuffer; - JsonObject& root = jsonBuffer.parseObject(configFile); - if (root.success()) { + DynamicJsonDocument jsonDocument = DynamicJsonDocument(MAX_SETTINGS_SIZE); + DeserializationError error = deserializeJson(jsonDocument, configFile); + if (error == DeserializationError::Ok && jsonDocument.is()){ + JsonObject root = jsonDocument.as(); readFromJsonObject(root); configFile.close(); - return; + return; } } configFile.close(); @@ -81,8 +81,8 @@ protected: // We assume the readFromJsonObject supplies sensible defaults if an empty object // is supplied, this virtual function allows that to be changed. virtual void applyDefaultConfig(){ - DynamicJsonBuffer jsonBuffer; - JsonObject& root = jsonBuffer.createObject(); + DynamicJsonDocument jsonDocument = DynamicJsonDocument(MAX_SETTINGS_SIZE); + JsonObject root = jsonDocument.to(); readFromJsonObject(root); } diff --git a/src/SettingsService.h b/src/SettingsService.h index 7c6e5a0..409c6d3 100644 --- a/src/SettingsService.h +++ b/src/SettingsService.h @@ -11,10 +11,9 @@ #include #include -#include #include #include -#include +#include /* * Abstraction of a service which stores it's settings as JSON in a file system. @@ -26,24 +25,26 @@ private: AsyncJsonRequestWebHandler _updateHandler; void fetchConfig(AsyncWebServerRequest *request){ - AsyncJsonResponse * response = new AsyncJsonResponse(); - writeToJsonObject(response->getRoot()); + AsyncJsonResponse * response = new AsyncJsonResponse(MAX_SETTINGS_SIZE); + JsonObject jsonObject = response->getRoot(); + writeToJsonObject(jsonObject); response->setLength(); request->send(response); } - void updateConfig(AsyncWebServerRequest *request, JsonVariant &json){ - if (json.is()){ - JsonObject& newConfig = json.as(); + void updateConfig(AsyncWebServerRequest *request, JsonDocument &jsonDocument){ + if (jsonDocument.is()){ + JsonObject newConfig = jsonDocument.as(); readFromJsonObject(newConfig); writeToFS(); // write settings back with a callback to reconfigure the wifi - AsyncJsonCallbackResponse * response = new AsyncJsonCallbackResponse([this] () {onConfigUpdated();}); - writeToJsonObject(response->getRoot()); + AsyncJsonCallbackResponse * response = new AsyncJsonCallbackResponse([this] () {onConfigUpdated();}, MAX_SETTINGS_SIZE); + JsonObject jsonObject = response->getRoot(); + writeToJsonObject(jsonObject); response->setLength(); request->send(response); - } else{ + } else { request->send(400); } } diff --git a/src/SimpleService.h b/src/SimpleService.h index 4f2daec..9144dfc 100644 --- a/src/SimpleService.h +++ b/src/SimpleService.h @@ -10,10 +10,9 @@ #endif #include -#include #include +#include #include -#include /** * At the moment, not expecting services to have to deal with large JSON @@ -35,23 +34,25 @@ private: AsyncJsonRequestWebHandler _updateHandler; void fetchConfig(AsyncWebServerRequest *request){ - AsyncJsonResponse * response = new AsyncJsonResponse(); - writeToJsonObject(response->getRoot()); + AsyncJsonResponse * response = new AsyncJsonResponse(MAX_SETTINGS_SIZE); + JsonObject jsonObject = response->getRoot(); + writeToJsonObject(jsonObject); response->setLength(); request->send(response); } - void updateConfig(AsyncWebServerRequest *request, JsonVariant &json){ - if (json.is()){ - JsonObject& newConfig = json.as(); + void updateConfig(AsyncWebServerRequest *request, JsonDocument &jsonDocument){ + if (jsonDocument.is()){ + JsonObject newConfig = jsonDocument.as(); readFromJsonObject(newConfig); - + // write settings back with a callback to reconfigure the wifi - AsyncJsonCallbackResponse * response = new AsyncJsonCallbackResponse([this] () {onConfigUpdated();}); - writeToJsonObject(response->getRoot()); + AsyncJsonCallbackResponse * response = new AsyncJsonCallbackResponse([this] () {onConfigUpdated();}, MAX_SETTINGS_SIZE); + JsonObject jsonObject = response->getRoot(); + writeToJsonObject(jsonObject); response->setLength(); request->send(response); - } else{ + } else { request->send(400); } } diff --git a/src/WiFiScanner.cpp b/src/WiFiScanner.cpp index dd15f64..7bf1591 100644 --- a/src/WiFiScanner.cpp +++ b/src/WiFiScanner.cpp @@ -16,11 +16,11 @@ void WiFiScanner::scanNetworks(AsyncWebServerRequest *request) { void WiFiScanner::listNetworks(AsyncWebServerRequest *request) { int numNetworks = WiFi.scanComplete(); if (numNetworks > -1){ - AsyncJsonResponse * response = new AsyncJsonResponse(); - JsonObject& root = response->getRoot(); - JsonArray& networks = root.createNestedArray("networks"); + AsyncJsonResponse * response = new AsyncJsonResponse(MAX_WIFI_SCANNER_SIZE); + JsonObject root = response->getRoot(); + JsonArray networks = root.createNestedArray("networks"); for (int i=0; i #include -#include +#include #include #define SCAN_NETWORKS_SERVICE_PATH "/rest/scanNetworks" #define LIST_NETWORKS_SERVICE_PATH "/rest/listNetworks" +#define MAX_WIFI_SCANNER_SIZE 1024 + class WiFiScanner { public: diff --git a/src/WiFiStatus.cpp b/src/WiFiStatus.cpp index 9e8574a..4b32d7d 100644 --- a/src/WiFiStatus.cpp +++ b/src/WiFiStatus.cpp @@ -49,8 +49,8 @@ void WiFiStatus::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) { #endif void WiFiStatus::wifiStatus(AsyncWebServerRequest *request) { - AsyncJsonResponse * response = new AsyncJsonResponse(); - JsonObject& root = response->getRoot(); + AsyncJsonResponse * response = new AsyncJsonResponse(MAX_WIFI_STATUS_SIZE); + JsonObject root = response->getRoot(); wl_status_t status = WiFi.status(); root["status"] = (uint8_t) status; if (status == WL_CONNECTED){ diff --git a/src/WiFiStatus.h b/src/WiFiStatus.h index 194f996..dca96d8 100644 --- a/src/WiFiStatus.h +++ b/src/WiFiStatus.h @@ -11,9 +11,10 @@ #include #include -#include +#include #include +#define MAX_WIFI_STATUS_SIZE 1024 #define WIFI_STATUS_SERVICE_PATH "/rest/wifiStatus" class WiFiStatus {