api: rework plain and JSON implementations (#2405)
- match paths through a custom AsyncWebHandler instead of using generic not-found fallback handler
- allow MQTT-like patterns when registering paths (`simple/path`, `path/+/something`, `path/#`)
Replaces `relay/0`, `relay/1` etc. with `relay/+`. Magnitudes are plain paths, but using `/+` in case there's more than 1 magnitude of the same type.
- restore `std::function` as callback container (no more single-byte arg nonsense). Still, limit to 1 type per handler type
- adds JSON handlers which will receive JsonObject root as both input and output. Same logic as plain - GET returns resource data, PUT updates it.
- breaking change to `apiAuthenticate(request)`, it no longer will do `request->send(403)` and expect this to be handled externally.
- allow `Api-Key` header containing the key, works for both GET & PUT plain requests. The only way to set apikey for JSON.
- add `ApiRequest::param` to retrieve both GET and PUT params (aka args), remove ApiBuffer
- remove `API_BUFFER_SIZE`. Allow custom form-data key=value pairs for requests, allow to send basic `String`.
- add `API_JSON_BUFFER_SIZE` for the JSON buffer (both input and output)
- `/apis` replaced with `/api/list`, no longer uses custom handler and is an `apiRegister` callback
- `/api/rpc` custom handler replaced with an `apiRegister` callback
WIP further down:
- no more `webLog` for API requests, unless `webAccessLog` / `WEB_ACCESS_LOG` is set to `1`. This also needs to happen to the other handlers.
- migrate to ArduinoJson v6, since it become apparent it is actually a good upgrade :)
- actually make use of JSON endpoints more, right now it's just existing GET for sensors and relays
- fork ESPAsyncWebServer to cleanup path parsing and temporary objects attached to the request (also, fix things a lot of things based on PRs there...) 4 years ago |
|
- /*
-
- Part of the API MODULE
-
- Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
-
- */
-
- #pragma once
-
- #include <Arduino.h>
-
- #include <ESPAsyncWebServer.h>
-
- #include <algorithm>
- #include <memory>
- #include <vector>
-
- // -----------------------------------------------------------------------------
-
- struct PathPart {
- enum class Type {
- Unknown,
- Value,
- SingleWildcard,
- MultiWildcard
- };
-
- Type type;
- size_t offset;
- size_t length;
- };
-
- struct PathParts {
- using Parts = std::vector<PathPart>;
-
- PathParts() = delete;
-
- PathParts(const PathParts&) = default;
- PathParts(PathParts&&) noexcept = default;
-
- explicit PathParts(const String& path);
-
- explicit operator bool() const {
- return _ok;
- }
-
- void clear() {
- _parts.clear();
- }
-
- void reserve(size_t size) {
- _parts.reserve(size);
- }
-
- String operator[](size_t index) const {
- auto& part = _parts[index];
- return _path.substring(part.offset, part.offset + part.length);
- }
-
- const String& path() const {
- return _path;
- }
-
- const Parts& parts() const {
- return _parts;
- }
-
- size_t size() const {
- return _parts.size();
- }
-
- Parts::const_iterator begin() const {
- return _parts.begin();
- }
-
- Parts::const_iterator end() const {
- return _parts.end();
- }
-
- bool match(const PathParts& path) const;
- bool match(const String& path) const {
- return match(PathParts(path));
- }
-
- private:
- PathPart& emplace_back(PathPart::Type type, size_t offset, size_t length) {
- PathPart part { type, offset, length };
- _parts.push_back(std::move(part));
- return _parts.back();
- }
-
- const String& _path;
- Parts _parts;
- bool _ok { false };
- };
-
- // this is a purely temporary object, which we can only create while doing the API dispatch
-
- struct ApiRequest {
- ApiRequest() = delete;
-
- ApiRequest(const ApiRequest&) = default;
- ApiRequest(ApiRequest&&) noexcept = default;
-
- explicit ApiRequest(AsyncWebServerRequest& request, const PathParts& pattern, const PathParts& parts) :
- _request(request),
- _pattern(pattern),
- _parts(parts)
- {}
-
- template <typename T>
- void handle(T&& handler) {
- _done = true;
- handler(&_request);
- }
-
- template <typename T>
- void param_foreach(T&& handler) {
- const size_t params { _request.params() };
- for (size_t current = 0; current < params; ++current) {
- auto* param = _request.getParam(current);
- handler(param->name(), param->value());
- }
- }
-
- template <typename T>
- void param_foreach(const String& name, T&& handler) {
- param_foreach([&](const String& param_name, const String& param_value) {
- if (param_name == name) {
- handler(param_value);
- }
- });
- }
-
- const String& param(const String& name) {
- auto* result = _request.getParam(name, HTTP_PUT == _request.method());
- if (result) {
- return result->value();
- }
-
- return _empty_string();
- }
-
- void send(const String& payload) {
- if (payload.length()) {
- _request.send(200, "text/plain", payload);
- } else {
- _request.send(204);
- }
- _done = true;
- }
-
- bool done() const {
- return _done;
- }
-
- const PathParts& parts() const {
- return _parts;
- }
-
- String part(size_t index) const {
- return _parts[index];
- }
-
- // Only works when pattern cointains '+', retrieving the part at the same index from the real path
- // e.g. for the pair of `some/+/path` and `some/data/path`, calling `wildcard(0)` will return `data`
- String wildcard(int index) const;
- size_t wildcards() const;
-
- private:
- const String& _empty_string() const {
- static const String string;
- return string;
- }
-
- bool _done { false };
-
- AsyncWebServerRequest& _request;
- const PathParts& _pattern;
- const PathParts& _parts;
- };
-
- struct ApiRequestHelper {
- ApiRequestHelper(const ApiRequestHelper&) = delete;
- ApiRequestHelper(ApiRequestHelper&&) noexcept = default;
-
- // &path is expected to be request->url(), which is valid throughout the request's lifetime
- explicit ApiRequestHelper(AsyncWebServerRequest& request, const PathParts& pattern) :
- _request(request),
- _pattern(pattern),
- _path(request.url()),
- _match(_pattern.match(_path))
- {}
-
- ApiRequest request() const {
- return ApiRequest(_request, _pattern, _path);
- }
-
- const PathParts& parts() const {
- return _path;
- }
-
- bool match() const {
- return _match;
- }
-
- private:
- AsyncWebServerRequest& _request;
- const PathParts& _pattern;
- PathParts _path;
- bool _match;
- };
|