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.

216 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...)
3 years ago
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...)
3 years ago
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...)
3 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. if (_done) return;
  84. _done = true;
  85. handler(&_request);
  86. }
  87. template <typename T>
  88. void param_foreach(T&& handler) {
  89. const size_t params { _request.params() };
  90. for (size_t current = 0; current < params; ++current) {
  91. auto* param = _request.getParam(current);
  92. handler(param->name(), param->value());
  93. }
  94. }
  95. template <typename T>
  96. void param_foreach(const String& name, T&& handler) {
  97. param_foreach([&](const String& param_name, const String& param_value) {
  98. if (param_name == name) {
  99. handler(param_value);
  100. }
  101. });
  102. }
  103. const String& param(const String& name) {
  104. auto* result = _request.getParam(name, HTTP_PUT == _request.method());
  105. if (result) {
  106. return result->value();
  107. }
  108. return _empty_string();
  109. }
  110. void send(const String& payload) {
  111. if (_done) return;
  112. _done = true;
  113. if (payload.length()) {
  114. _request.send(200, "text/plain", payload);
  115. } else {
  116. _request.send(204);
  117. }
  118. }
  119. bool done() const {
  120. return _done;
  121. }
  122. const PathParts& parts() const {
  123. return _parts;
  124. }
  125. String part(size_t index) const {
  126. return _parts[index];
  127. }
  128. // Only works when pattern cointains '+', retrieving the part at the same index from the real path
  129. // e.g. for the pair of `some/+/path` and `some/data/path`, calling `wildcard(0)` will return `data`
  130. String wildcard(int index) const;
  131. size_t wildcards() const;
  132. private:
  133. const String& _empty_string() const {
  134. static const String string;
  135. return string;
  136. }
  137. bool _done { false };
  138. AsyncWebServerRequest& _request;
  139. const PathParts& _pattern;
  140. const PathParts& _parts;
  141. };
  142. struct ApiRequestHelper {
  143. ApiRequestHelper(const ApiRequestHelper&) = delete;
  144. ApiRequestHelper(ApiRequestHelper&&) noexcept = default;
  145. // &path is expected to be request->url(), which is valid throughout the request's lifetime
  146. explicit ApiRequestHelper(AsyncWebServerRequest& request, const PathParts& pattern) :
  147. _request(request),
  148. _pattern(pattern),
  149. _path(request.url()),
  150. _match(_pattern.match(_path))
  151. {}
  152. ApiRequest request() const {
  153. return ApiRequest(_request, _pattern, _path);
  154. }
  155. const PathParts& parts() const {
  156. return _path;
  157. }
  158. bool match() const {
  159. return _match;
  160. }
  161. private:
  162. AsyncWebServerRequest& _request;
  163. const PathParts& _pattern;
  164. PathParts _path;
  165. bool _match;
  166. };