Terminal: change command-line parser (#2247)
Change the underlying command line handling:
- switch to a custom parser, inspired by redis / sds
- update terminalRegisterCommand signature, pass only bare minimum
- clean-up `help` & `commands`. update settings `set`, `get` and `del`
- allow our custom test suite to run command-line tests
- clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`)
- send parsing errors to the debug log
As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT`
- MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API.
- Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS.
Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :)
Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case. 4 years ago Terminal: change command-line parser (#2247)
Change the underlying command line handling:
- switch to a custom parser, inspired by redis / sds
- update terminalRegisterCommand signature, pass only bare minimum
- clean-up `help` & `commands`. update settings `set`, `get` and `del`
- allow our custom test suite to run command-line tests
- clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`)
- send parsing errors to the debug log
As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT`
- MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API.
- Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS.
Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :)
Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case. 4 years ago Terminal: change command-line parser (#2247)
Change the underlying command line handling:
- switch to a custom parser, inspired by redis / sds
- update terminalRegisterCommand signature, pass only bare minimum
- clean-up `help` & `commands`. update settings `set`, `get` and `del`
- allow our custom test suite to run command-line tests
- clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`)
- send parsing errors to the debug log
As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT`
- MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API.
- Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS.
Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :)
Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case. 4 years ago Terminal: change command-line parser (#2247)
Change the underlying command line handling:
- switch to a custom parser, inspired by redis / sds
- update terminalRegisterCommand signature, pass only bare minimum
- clean-up `help` & `commands`. update settings `set`, `get` and `del`
- allow our custom test suite to run command-line tests
- clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`)
- send parsing errors to the debug log
As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT`
- MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API.
- Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS.
Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :)
Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case. 4 years ago Terminal: change command-line parser (#2247)
Change the underlying command line handling:
- switch to a custom parser, inspired by redis / sds
- update terminalRegisterCommand signature, pass only bare minimum
- clean-up `help` & `commands`. update settings `set`, `get` and `del`
- allow our custom test suite to run command-line tests
- clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`)
- send parsing errors to the debug log
As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT`
- MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API.
- Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS.
Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :)
Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case. 4 years ago Terminal: change command-line parser (#2247)
Change the underlying command line handling:
- switch to a custom parser, inspired by redis / sds
- update terminalRegisterCommand signature, pass only bare minimum
- clean-up `help` & `commands`. update settings `set`, `get` and `del`
- allow our custom test suite to run command-line tests
- clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`)
- send parsing errors to the debug log
As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT`
- MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API.
- Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS.
Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :)
Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case. 4 years ago |
|
- /*
-
- API MODULE
-
- Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
-
- */
-
- #include "api.h"
-
- // -----------------------------------------------------------------------------
-
- #if API_SUPPORT
-
- #include <vector>
-
- #include "system.h"
- #include "web.h"
- #include "rpc.h"
-
- #include <ESPAsyncTCP.h>
- #include <ArduinoJson.h>
-
- constexpr size_t ApiPathSizeMax { 64ul };
- std::vector<Api> _apis;
-
- // -----------------------------------------------------------------------------
- // API
- // -----------------------------------------------------------------------------
-
- bool _asJson(AsyncWebServerRequest *request) {
- bool asJson = false;
- if (request->hasHeader("Accept")) {
- AsyncWebHeader* h = request->getHeader("Accept");
- asJson = h->value().equals("application/json");
- }
- return asJson;
- }
-
- void _onAPIsText(AsyncWebServerRequest *request) {
- AsyncResponseStream *response = request->beginResponseStream("text/plain");
- char buffer[ApiPathSizeMax] = {0};
- for (auto& api : _apis) {
- sprintf_P(buffer, PSTR("/api/%s\n"), api.path.c_str());
- response->write(buffer);
- }
- request->send(response);
- }
-
- constexpr size_t ApiJsonBufferSize = 1024;
-
- void _onAPIsJson(AsyncWebServerRequest *request) {
-
- DynamicJsonBuffer jsonBuffer(ApiJsonBufferSize);
- JsonArray& root = jsonBuffer.createArray();
-
- char buffer[ApiPathSizeMax] = {0};
- for (auto& api : _apis) {
- sprintf(buffer, "/api/%s", api.path.c_str());
- root.add(buffer);
- }
-
- AsyncResponseStream *response = request->beginResponseStream("application/json");
- root.printTo(*response);
- request->send(response);
-
- }
-
- void _onAPIs(AsyncWebServerRequest *request) {
-
- webLog(request);
- if (!apiAuthenticate(request)) return;
-
- bool asJson = _asJson(request);
-
- String output;
- if (asJson) {
- _onAPIsJson(request);
- } else {
- _onAPIsText(request);
- }
-
- }
-
- void _onRPC(AsyncWebServerRequest *request) {
-
- webLog(request);
- if (!apiAuthenticate(request)) return;
-
- //bool asJson = _asJson(request);
- int response = 404;
-
- if (request->hasParam("action")) {
-
- AsyncWebParameter* p = request->getParam("action");
-
- const auto action = p->value();
- DEBUG_MSG_P(PSTR("[RPC] Action: %s\n"), action.c_str());
-
- if (rpcHandleAction(action)) {
- response = 204;
- }
-
- }
-
- request->send(response);
-
- }
-
- struct ApiMatch {
- Api* api { nullptr };
- Api::Type type { Api::Type::Basic };
- };
-
- ApiMatch _apiMatch(const String& url, AsyncWebServerRequest* request) {
-
- ApiMatch result;
- char buffer[ApiPathSizeMax] = {0};
-
- for (auto& api : _apis) {
- sprintf_P(buffer, PSTR("/api/%s"), api.path.c_str());
- if (url != buffer) {
- continue;
- }
-
- auto type = _asJson(request)
- ? Api::Type::Json
- : Api::Type::Basic;
-
- result.api = &api;
- result.type = type;
- break;
- }
-
- return result;
- }
-
- bool _apiDispatchRequest(const String& url, AsyncWebServerRequest* request) {
-
- auto match = _apiMatch(url, request);
- if (!match.api) {
- return false;
- }
-
- if (match.type != match.api->type) {
- DEBUG_MSG_P(PSTR("[API] Cannot handle the request type\n"));
- request->send(404);
- return true;
- }
-
- const bool is_put = (
- (!apiRestFul() || (request->method() == HTTP_PUT))
- && request->hasParam("value", request->method() == HTTP_PUT)
- );
-
- ApiBuffer buffer;
-
- switch (match.api->type) {
-
- case Api::Type::Basic: {
- if (!match.api->get.basic) {
- break;
- }
-
- if (is_put) {
- if (!match.api->put.basic) {
- break;
- }
- auto value = request->getParam("value", request->method() == HTTP_PUT)->value();
- if (buffer.size < (value.length() + 1ul)) {
- break;
- }
- std::copy(value.c_str(), value.c_str() + value.length() + 1, buffer.data);
- match.api->put.basic(*match.api, buffer);
- buffer.erase();
- }
-
- match.api->get.basic(*match.api, buffer);
- request->send(200, "text/plain", buffer.data);
-
- return true;
- }
-
- // TODO: pass the body instead of `value` param
- // TODO: handle HTTP_PUT
- case Api::Type::Json: {
- if (!match.api->get.json || is_put) {
- break;
- }
-
- DynamicJsonBuffer jsonBuffer(API_BUFFER_SIZE);
- JsonObject& root = jsonBuffer.createObject();
-
- match.api->get.json(*match.api, root);
-
- AsyncResponseStream *response = request->beginResponseStream("application/json", root.measureLength() + 1);
- root.printTo(*response);
- request->send(response);
-
- return true;
- }
-
- }
-
- DEBUG_MSG_P(PSTR("[API] Method not supported\n"));
- request->send(405);
-
- return true;
-
- }
-
- bool _apiRequestCallback(AsyncWebServerRequest* request) {
-
- String url = request->url();
-
- if (url.equals("/rpc")) {
- _onRPC(request);
- return true;
- }
-
- if (url.equals("/api") || url.equals("/apis")) {
- _onAPIs(request);
- return true;
- }
-
- if (!url.startsWith("/api/")) return false;
-
- // [alexa] don't call the http api -> response for alexa is done by fauxmoesp lib
- #if ALEXA_SUPPORT
- if (url.indexOf("/lights") > 14 ) return false;
- #endif
-
- if (!apiAuthenticate(request)) return false;
-
- return _apiDispatchRequest(url, request);
-
- }
-
- // -----------------------------------------------------------------------------
-
- void apiReserve(size_t size) {
- _apis.reserve(_apis.size() + size);
- }
-
- void apiRegister(const Api& api) {
- if (api.path.length() >= (ApiPathSizeMax - strlen("/api/") - 1ul)) {
- return;
- }
- _apis.push_back(api);
- }
-
- void apiSetup() {
- webRequestRegister(_apiRequestCallback);
- }
-
- void apiOk(const Api&, ApiBuffer& buffer) {
- buffer.data[0] = 'O';
- buffer.data[1] = 'K';
- buffer.data[2] = '\0';
- }
-
- void apiError(const Api&, ApiBuffer& buffer) {
- buffer.data[0] = '-';
- buffer.data[1] = 'E';
- buffer.data[2] = 'R';
- buffer.data[3] = 'R';
- buffer.data[4] = 'O';
- buffer.data[5] = 'R';
- buffer.data[6] = '\0';
- }
-
- #endif // API_SUPPORT
-
|