Fork of the espurna firmware for `mhsw` switches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

213 lines
4.8 KiB

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
  1. /*
  2. Part of the API MODULE
  3. Copyright (C) 2020 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
  4. */
  5. #pragma once
  6. #include <Arduino.h>
  7. #include <ESPAsyncWebServer.h>
  8. #include <algorithm>
  9. #include <memory>
  10. #include <vector>
  11. // -----------------------------------------------------------------------------
  12. struct PathPart {
  13. enum class Type {
  14. Unknown,
  15. Value,
  16. SingleWildcard,
  17. MultiWildcard
  18. };
  19. Type type;
  20. size_t offset;
  21. size_t length;
  22. };
  23. struct PathParts {
  24. using Parts = std::vector<PathPart>;
  25. PathParts() = delete;
  26. PathParts(const PathParts&) = default;
  27. PathParts(PathParts&&) noexcept = default;
  28. explicit PathParts(const String& path);
  29. explicit operator bool() const {
  30. return _ok;
  31. }
  32. void clear() {
  33. _parts.clear();
  34. }
  35. void reserve(size_t size) {
  36. _parts.reserve(size);
  37. }
  38. String operator[](size_t index) const {
  39. auto& part = _parts[index];
  40. return _path.substring(part.offset, part.offset + part.length);
  41. }
  42. const String& path() const {
  43. return _path;
  44. }
  45. const Parts& parts() const {
  46. return _parts;
  47. }
  48. size_t size() const {
  49. return _parts.size();
  50. }
  51. Parts::const_iterator begin() const {
  52. return _parts.begin();
  53. }
  54. Parts::const_iterator end() const {
  55. return _parts.end();
  56. }
  57. bool match(const PathParts& path) const;
  58. bool match(const String& path) const {
  59. return match(PathParts(path));
  60. }
  61. private:
  62. PathPart& emplace_back(PathPart::Type type, size_t offset, size_t length) {
  63. PathPart part { type, offset, length };
  64. _parts.push_back(std::move(part));
  65. return _parts.back();
  66. }
  67. const String& _path;
  68. Parts _parts;
  69. bool _ok { false };
  70. };
  71. // this is a purely temporary object, which we can only create while doing the API dispatch
  72. struct ApiRequest {
  73. ApiRequest() = delete;
  74. ApiRequest(const ApiRequest&) = default;
  75. ApiRequest(ApiRequest&&) noexcept = default;
  76. explicit ApiRequest(AsyncWebServerRequest& request, const PathParts& pattern, const PathParts& parts) :
  77. _request(request),
  78. _pattern(pattern),
  79. _parts(parts)
  80. {}
  81. template <typename T>
  82. void handle(T&& handler) {
  83. _done = true;
  84. handler(&_request);
  85. }
  86. template <typename T>
  87. void param_foreach(T&& handler) {
  88. const size_t params { _request.params() };
  89. for (size_t current = 0; current < params; ++current) {
  90. auto* param = _request.getParam(current);
  91. handler(param->name(), param->value());
  92. }
  93. }
  94. template <typename T>
  95. void param_foreach(const String& name, T&& handler) {
  96. param_foreach([&](const String& param_name, const String& param_value) {
  97. if (param_name == name) {
  98. handler(param_value);
  99. }
  100. });
  101. }
  102. const String& param(const String& name) {
  103. auto* result = _request.getParam(name, HTTP_PUT == _request.method());
  104. if (result) {
  105. return result->value();
  106. }
  107. return _empty_string();
  108. }
  109. void send(const String& payload) {
  110. if (payload.length()) {
  111. _request.send(200, "text/plain", payload);
  112. } else {
  113. _request.send(204);
  114. }
  115. _done = true;
  116. }
  117. bool done() const {
  118. return _done;
  119. }
  120. const PathParts& parts() const {
  121. return _parts;
  122. }
  123. String part(size_t index) const {
  124. return _parts[index];
  125. }
  126. // Only works when pattern cointains '+', retrieving the part at the same index from the real path
  127. // e.g. for the pair of `some/+/path` and `some/data/path`, calling `wildcard(0)` will return `data`
  128. String wildcard(int index) const;
  129. size_t wildcards() const;
  130. private:
  131. const String& _empty_string() const {
  132. static const String string;
  133. return string;
  134. }
  135. bool _done { false };
  136. AsyncWebServerRequest& _request;
  137. const PathParts& _pattern;
  138. const PathParts& _parts;
  139. };
  140. struct ApiRequestHelper {
  141. ApiRequestHelper(const ApiRequestHelper&) = delete;
  142. ApiRequestHelper(ApiRequestHelper&&) noexcept = default;
  143. // &path is expected to be request->url(), which is valid throughout the request's lifetime
  144. explicit ApiRequestHelper(AsyncWebServerRequest& request, const PathParts& pattern) :
  145. _request(request),
  146. _pattern(pattern),
  147. _path(request.url()),
  148. _match(_pattern.match(_path))
  149. {}
  150. ApiRequest request() const {
  151. return ApiRequest(_request, _pattern, _path);
  152. }
  153. const PathParts& parts() const {
  154. return _path;
  155. }
  156. bool match() const {
  157. return _match;
  158. }
  159. private:
  160. AsyncWebServerRequest& _request;
  161. const PathParts& _pattern;
  162. PathParts _path;
  163. bool _match;
  164. };