SensESP 3.4.1-alpha
Universal Signal K sensor toolkit ESP32
Loading...
Searching...
No Matches
config_handler.cpp
Go to the documentation of this file.
1#include "sensesp.h"
2
3#include "config_handler.h"
4
5#include <cstring>
6#include <memory>
7
9
10namespace sensesp {
11
12namespace {
13
14// Length of the path portion of a URI, excluding any query string. The query
15// may carry secrets and the captured log is served over HTTP, so only the path
16// is logged (see HTTPServer::dispatch_request).
17int uri_path_len(const char* uri) {
18 const char* query = strchr(uri, '?');
19 return query ? static_cast<int>(query - uri) : static_cast<int>(strlen(uri));
20}
21
22} // namespace
23
24bool get_item_data(JsonDocument& doc,
25 const std::shared_ptr<ConfigItemBase>& item) {
26 JsonObject obj = doc.to<JsonObject>();
27
28 String str;
29 serializeJson(doc, str);
30
31 obj["path"] = item->get_config_path();
32 obj["title"] = item->get_title();
33 obj["description"] = item->get_description();
34 obj["requires_restart"] = item->requires_restart();
35 obj["schema"] = serialized(item->get_config_schema());
36
37 item->refresh();
38
39 JsonObject config = obj["config"].to<JsonObject>();
40 bool result = item->to_json(config);
41
42 serializeJson(obj, str);
43
44 if (doc.overflowed()) {
45 ESP_LOGE("ConfigHandler", "JSON document overflowed");
46 return false;
47 }
48
49 return result;
50}
51
52esp_err_t handle_config_item_list(httpd_req_t* req) {
53 ESP_LOGI("ConfigHandler", "GET request to URL %.*s", uri_path_len(req->uri),
54 req->uri);
55 String url = String(req->uri);
56 String query = "";
57 if (url.indexOf('?') != -1) {
58 query = url.substring(url.indexOf('?') + 1);
59 }
60
61 bool cards_only = false;
62
63 // If query is "cards", return only the cards
64 if (query == "cards") {
65 cards_only = true;
66 } else if (query != "") {
67 httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid query");
68 return ESP_FAIL;
69 }
70
71 JsonDocument json_doc;
72 JsonArray arr = json_doc.to<JsonArray>();
73
74 auto config_items = ConfigItemBase::get_config_items();
75
76 for (auto it = config_items->begin(); it != config_items->end(); ++it) {
77 if (cards_only &&
78 ((*it)->get_config_schema() == "null" ||
79 (*it)->get_config_schema() == "{}" || (*it)->get_title() == "")) {
80 continue;
81 }
82 const String& path = (*it)->get_config_path();
83 if (path == "") {
84 continue;
85 }
86 auto obj = arr.add(path);
87 }
88
89 String response;
90 serializeJson(json_doc, response);
91 httpd_resp_set_type(req, "application/json");
92 httpd_resp_sendstr(req, response.c_str());
93 return ESP_OK;
94}
95
96void add_config_list_handler(std::shared_ptr<HTTPServer>& server) {
97 auto handler = std::make_shared<HTTPRequestHandler>(
98 1 << HTTP_GET, "/api/config", handle_config_item_list);
99 server->add_handler(handler);
100}
101
102void add_config_get_handler(std::shared_ptr<HTTPServer>& server) {
103 auto handler = std::make_shared<HTTPRequestHandler>(
104 1 << HTTP_GET, "/api/config/*", [](httpd_req_t* req) {
105 ESP_LOGD("ConfigHandler", "GET request to URL %.*s",
106 uri_path_len(req->uri), req->uri);
107 String url_tail = String(req->uri).substring(11);
108 String path;
109 String query = "";
110 if (url_tail.indexOf('?') != -1) {
111 path = url_tail.substring(0, url_tail.indexOf('?'));
112 query = url_tail.substring(url_tail.indexOf('?') + 1);
113 } else {
114 path = url_tail;
115 }
116 char path_cstr[path.length() + 1];
117 urldecode2(path_cstr, path.c_str());
118 url_tail = String(path_cstr);
119
120 if (path.length() == 0) {
121 // return a list of all ConfigItemT objects
122 return handle_config_item_list(req);
123 }
124
125 // find the config item object with the matching config_path
126 auto config_item = ConfigItemBase::get_config_item(url_tail);
127 if (config_item == nullptr) {
128 httpd_resp_send_err(req, HTTPD_404_NOT_FOUND,
129 "No ConfigItem found with that path");
130 return ESP_FAIL;
131 }
132
133 JsonDocument doc;
134
135 get_item_data(doc, config_item);
136
137 String response;
138 serializeJson(doc, response);
139 httpd_resp_set_type(req, "application/json");
140 httpd_resp_sendstr(req, response.c_str());
141 return ESP_OK;
142 });
143 server->add_handler(handler);
144}
145
146void add_config_put_handler(std::shared_ptr<HTTPServer>& server) {
147 auto handler = std::make_shared<HTTPRequestHandler>(
148 1 << HTTP_PUT, "/api/config/*",
149 [](httpd_req_t* req) { // check that the content type is JSON
150 ESP_LOGI(__FILENAME__, "PUT request to URL %.*s",
151 uri_path_len(req->uri), req->uri);
152 if (get_content_type(req) != "application/json") {
153 httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
154 "application/json content type expected");
155 return ESP_FAIL;
156 }
157
158 // get the URL tail after /api/config
159 String url_tail = String(req->uri).substring(11);
160 if (url_tail.length() == 0) {
161 httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
162 "No configuration path specified");
163 return ESP_FAIL;
164 }
165
166 // urldecode the URL tail
167 char url_tail_cstr[url_tail.length() + 1];
168 urldecode2(url_tail_cstr, url_tail.c_str());
169 url_tail = String(url_tail_cstr);
170
171 // find the ConfigItemT object with the matching config_path
172 auto config_item = ConfigItemBase::get_config_item(url_tail);
173 if (config_item == nullptr) {
174 httpd_resp_send_err(req, HTTPD_404_NOT_FOUND,
175 "No Configurable found with that path");
176 return ESP_FAIL;
177 }
178
179 // receive the payload
180 constexpr size_t kMaxConfigPayloadSize = 4096;
181 size_t payload_len = req->content_len;
182 if (payload_len > kMaxConfigPayloadSize) {
183 httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Payload too large");
184 return ESP_FAIL;
185 }
186 std::unique_ptr<char[]> payload(new char[payload_len + 1]);
187 int ret = httpd_req_recv(req, payload.get(), payload_len);
188 if (ret <= 0) {
189 httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR,
190 "Error receiving payload");
191 return ESP_FAIL;
192 }
193 payload[payload_len] = '\0';
194
195 ESP_LOGV("ConfigHandler", "Received payload: %s", payload.get());
196
197 // parse the content as JSON
198 JsonDocument doc;
199 DeserializationError error = deserializeJson(doc, payload.get());
200 if (error) {
201 ESP_LOGE("ConfigHandler", "Error parsing JSON payload: %s",
202 error.c_str());
203 httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
204 "Error parsing JSON payload");
205 return ESP_FAIL;
206 }
207
208 String response;
209 bool result = config_item->from_json(doc.as<JsonObject>());
210 if (!result) {
211 ESP_LOGE("ConfigHandler", "Error applying JSON payload");
212 httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
213 "Invalid JSON payload");
214 return ESP_FAIL;
215 }
216 config_item->save();
217 response = "{\"status\":\"ok\"}";
218
219 httpd_resp_set_type(req, "application/json");
220 httpd_resp_sendstr(req, response.c_str());
221 return ESP_OK;
222
223 });
224 server->add_handler(handler);
225}
226
227void add_config_handlers(std::shared_ptr<HTTPServer>& server) {
231}
232
233} // namespace sensesp
static std::shared_ptr< ConfigItemBase > get_config_item(const String key)
Get a single ConfigItemT by key.
static std::unique_ptr< std::vector< std::shared_ptr< ConfigItemBase > > > get_config_items()
Get all config items as a vector.
void add_config_handlers(std::shared_ptr< HTTPServer > &server)
Handle HTTP requests to /config.
void add_config_put_handler(std::shared_ptr< HTTPServer > &server)
void urldecode2(char *dst, const char *src)
void add_config_list_handler(std::shared_ptr< HTTPServer > &server)
bool get_item_data(JsonDocument &doc, const std::shared_ptr< ConfigItemBase > &item)
void add_config_get_handler(std::shared_ptr< HTTPServer > &server)
esp_err_t handle_config_item_list(httpd_req_t *req)
String get_content_type(httpd_req_t *req)