ESPHome 2025.5.0
Loading...
Searching...
No Matches
web_server_idf.cpp
Go to the documentation of this file.
1#ifdef USE_ESP_IDF
2
3#include <cstdarg>
4
5#include "esphome/core/log.h"
7
8#include "esp_tls_crypto.h"
9
10#include "utils.h"
11
14
15#include "web_server_idf.h"
16
17namespace esphome {
18namespace web_server_idf {
19
20#ifndef HTTPD_409
21#define HTTPD_409 "409 Conflict"
22#endif
23
24#define CRLF_STR "\r\n"
25#define CRLF_LEN (sizeof(CRLF_STR) - 1)
26
27static const char *const TAG = "web_server_idf";
28
30 if (this->server_) {
31 httpd_stop(this->server_);
32 this->server_ = nullptr;
33 }
34}
35
37 if (this->server_) {
38 this->end();
39 }
40 httpd_config_t config = HTTPD_DEFAULT_CONFIG();
41 config.server_port = this->port_;
42 config.uri_match_fn = [](const char * /*unused*/, const char * /*unused*/, size_t /*unused*/) { return true; };
43 if (httpd_start(&this->server_, &config) == ESP_OK) {
44 const httpd_uri_t handler_get = {
45 .uri = "",
46 .method = HTTP_GET,
48 .user_ctx = this,
49 };
50 httpd_register_uri_handler(this->server_, &handler_get);
51
52 const httpd_uri_t handler_post = {
53 .uri = "",
54 .method = HTTP_POST,
56 .user_ctx = this,
57 };
58 httpd_register_uri_handler(this->server_, &handler_post);
59
60 const httpd_uri_t handler_options = {
61 .uri = "",
62 .method = HTTP_OPTIONS,
64 .user_ctx = this,
65 };
66 httpd_register_uri_handler(this->server_, &handler_options);
67 }
68}
69
70esp_err_t AsyncWebServer::request_post_handler(httpd_req_t *r) {
71 ESP_LOGVV(TAG, "Enter AsyncWebServer::request_post_handler. uri=%s", r->uri);
72 auto content_type = request_get_header(r, "Content-Type");
73 if (content_type.has_value() && *content_type != "application/x-www-form-urlencoded") {
74 ESP_LOGW(TAG, "Only application/x-www-form-urlencoded supported for POST request");
75 // fallback to get handler to support backward compatibility
77 }
78
79 if (!request_has_header(r, "Content-Length")) {
80 ESP_LOGW(TAG, "Content length is requred for post: %s", r->uri);
81 httpd_resp_send_err(r, HTTPD_411_LENGTH_REQUIRED, nullptr);
82 return ESP_OK;
83 }
84
85 if (r->content_len > HTTPD_MAX_REQ_HDR_LEN) {
86 ESP_LOGW(TAG, "Request size is to big: %zu", r->content_len);
87 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr);
88 return ESP_FAIL;
89 }
90
91 std::string post_query;
92 if (r->content_len > 0) {
93 post_query.resize(r->content_len);
94 const int ret = httpd_req_recv(r, &post_query[0], r->content_len + 1);
95 if (ret <= 0) { // 0 return value indicates connection closed
96 if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
97 httpd_resp_send_err(r, HTTPD_408_REQ_TIMEOUT, nullptr);
98 return ESP_ERR_TIMEOUT;
99 }
100 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr);
101 return ESP_FAIL;
102 }
103 }
104
105 AsyncWebServerRequest req(r, std::move(post_query));
106 return static_cast<AsyncWebServer *>(r->user_ctx)->request_handler_(&req);
107}
108
109esp_err_t AsyncWebServer::request_handler(httpd_req_t *r) {
110 ESP_LOGVV(TAG, "Enter AsyncWebServer::request_handler. method=%u, uri=%s", r->method, r->uri);
112 return static_cast<AsyncWebServer *>(r->user_ctx)->request_handler_(&req);
113}
114
116 for (auto *handler : this->handlers_) {
117 if (handler->canHandle(request)) {
118 // At now process only basic requests.
119 // OTA requires multipart request support and handleUpload for it
120 handler->handleRequest(request);
121 return ESP_OK;
122 }
123 }
124 if (this->on_not_found_) {
125 this->on_not_found_(request);
126 return ESP_OK;
127 }
128 return ESP_ERR_NOT_FOUND;
129}
130
132 delete this->rsp_;
133 for (const auto &pair : this->params_) {
134 delete pair.second; // NOLINT(cppcoreguidelines-owning-memory)
135 }
136}
137
138bool AsyncWebServerRequest::hasHeader(const char *name) const { return request_has_header(*this, name); }
139
141 return request_get_header(*this, name);
142}
143
144std::string AsyncWebServerRequest::url() const {
145 auto *str = strchr(this->req_->uri, '?');
146 if (str == nullptr) {
147 return this->req_->uri;
148 }
149 return std::string(this->req_->uri, str - this->req_->uri);
150}
151
152std::string AsyncWebServerRequest::host() const { return this->get_header("Host").value(); }
153
155 httpd_resp_send(*this, response->get_content_data(), response->get_content_size());
156}
157
158void AsyncWebServerRequest::send(int code, const char *content_type, const char *content) {
159 this->init_response_(nullptr, code, content_type);
160 if (content) {
161 httpd_resp_send(*this, content, HTTPD_RESP_USE_STRLEN);
162 } else {
163 httpd_resp_send(*this, nullptr, 0);
164 }
165}
166
167void AsyncWebServerRequest::redirect(const std::string &url) {
168 httpd_resp_set_status(*this, "302 Found");
169 httpd_resp_set_hdr(*this, "Location", url.c_str());
170 httpd_resp_send(*this, nullptr, 0);
171}
172
173void AsyncWebServerRequest::init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type) {
174 httpd_resp_set_status(*this, code == 200 ? HTTPD_200
175 : code == 404 ? HTTPD_404
176 : code == 409 ? HTTPD_409
177 : to_string(code).c_str());
178
179 if (content_type && *content_type) {
180 httpd_resp_set_type(*this, content_type);
181 }
182 httpd_resp_set_hdr(*this, "Accept-Ranges", "none");
183
184 for (const auto &pair : DefaultHeaders::Instance().headers_) {
185 httpd_resp_set_hdr(*this, pair.first.c_str(), pair.second.c_str());
186 }
187
188 delete this->rsp_;
189 this->rsp_ = rsp;
190}
191
192bool AsyncWebServerRequest::authenticate(const char *username, const char *password) const {
193 if (username == nullptr || password == nullptr || *username == 0) {
194 return true;
195 }
196 auto auth = this->get_header("Authorization");
197 if (!auth.has_value()) {
198 return false;
199 }
200
201 auto *auth_str = auth.value().c_str();
202
203 const auto auth_prefix_len = sizeof("Basic ") - 1;
204 if (strncmp("Basic ", auth_str, auth_prefix_len) != 0) {
205 ESP_LOGW(TAG, "Only Basic authorization supported yet");
206 return false;
207 }
208
209 std::string user_info;
210 user_info += username;
211 user_info += ':';
212 user_info += password;
213
214 size_t n = 0, out;
215 esp_crypto_base64_encode(nullptr, 0, &n, reinterpret_cast<const uint8_t *>(user_info.c_str()), user_info.size());
216
217 auto digest = std::unique_ptr<char[]>(new char[n + 1]);
218 esp_crypto_base64_encode(reinterpret_cast<uint8_t *>(digest.get()), n, &out,
219 reinterpret_cast<const uint8_t *>(user_info.c_str()), user_info.size());
220
221 return strncmp(digest.get(), auth_str + auth_prefix_len, auth.value().size() - auth_prefix_len) == 0;
222}
223
224void AsyncWebServerRequest::requestAuthentication(const char *realm) const {
225 httpd_resp_set_hdr(*this, "Connection", "keep-alive");
226 auto auth_val = str_sprintf("Basic realm=\"%s\"", realm ? realm : "Login Required");
227 httpd_resp_set_hdr(*this, "WWW-Authenticate", auth_val.c_str());
228 httpd_resp_send_err(*this, HTTPD_401_UNAUTHORIZED, nullptr);
229}
230
232 auto find = this->params_.find(name);
233 if (find != this->params_.end()) {
234 return find->second;
235 }
236
238 if (!val.has_value()) {
239 auto url_query = request_get_url_query(*this);
240 if (url_query.has_value()) {
241 val = query_key_value(url_query.value(), name);
242 }
243 }
244
245 AsyncWebParameter *param = nullptr;
246 if (val.has_value()) {
247 param = new AsyncWebParameter(val.value()); // NOLINT(cppcoreguidelines-owning-memory)
248 }
249 this->params_.insert({name, param});
250 return param;
251}
252
253void AsyncWebServerResponse::addHeader(const char *name, const char *value) {
254 httpd_resp_set_hdr(*this->req_, name, value);
255}
256
257void AsyncResponseStream::print(float value) { this->print(to_string(value)); }
258
259void AsyncResponseStream::printf(const char *fmt, ...) {
260 va_list args;
261
262 va_start(args, fmt);
263 const int length = vsnprintf(nullptr, 0, fmt, args);
264 va_end(args);
265
266 std::string str;
267 str.resize(length);
268
269 va_start(args, fmt);
270 vsnprintf(&str[0], length + 1, fmt, args);
271 va_end(args);
272
273 this->print(str);
274}
275
277 for (auto *ses : this->sessions_) {
278 delete ses; // NOLINT(cppcoreguidelines-owning-memory)
279 }
280}
281
283 auto *rsp = // NOLINT(cppcoreguidelines-owning-memory)
284 new AsyncEventSourceResponse(request, this, this->web_server_);
285 if (this->on_connect_) {
286 this->on_connect_(rsp);
287 }
288 this->sessions_.insert(rsp);
289}
290
292 for (auto *ses : this->sessions_) {
293 ses->loop();
294 }
295}
296
297void AsyncEventSource::try_send_nodefer(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
298 for (auto *ses : this->sessions_) {
299 ses->try_send_nodefer(message, event, id, reconnect);
300 }
301}
302
303void AsyncEventSource::deferrable_send_state(void *source, const char *event_type,
304 message_generator_t *message_generator) {
305 for (auto *ses : this->sessions_) {
306 ses->deferrable_send_state(source, event_type, message_generator);
307 }
308}
309
313 : server_(server), web_server_(ws), entities_iterator_(new esphome::web_server::ListEntitiesIterator(ws, server)) {
314 httpd_req_t *req = *request;
315
316 httpd_resp_set_status(req, HTTPD_200);
317 httpd_resp_set_type(req, "text/event-stream");
318 httpd_resp_set_hdr(req, "Cache-Control", "no-cache");
319 httpd_resp_set_hdr(req, "Connection", "keep-alive");
320
321 for (const auto &pair : DefaultHeaders::Instance().headers_) {
322 httpd_resp_set_hdr(req, pair.first.c_str(), pair.second.c_str());
323 }
324
325 httpd_resp_send_chunk(req, CRLF_STR, CRLF_LEN);
326
327 req->sess_ctx = this;
328 req->free_ctx = AsyncEventSourceResponse::destroy;
329
330 this->hd_ = req->handle;
331 this->fd_ = httpd_req_to_sockfd(req);
332
333 // Configure reconnect timeout and send config
334 // this should always go through since the tcp send buffer is empty on connect
335 std::string message = ws->get_config_json();
336 this->try_send_nodefer(message.c_str(), "ping", millis(), 30000);
337
338 for (auto &group : ws->sorting_groups_) {
339 message = json::build_json([group](JsonObject root) {
340 root["name"] = group.second.name;
341 root["sorting_weight"] = group.second.weight;
342 });
343
344 // a (very) large number of these should be able to be queued initially without defer
345 // since the only thing in the send buffer at this point is the initial ping/config
346 this->try_send_nodefer(message.c_str(), "sorting_group");
347 }
348
350
351 // just dump them all up-front and take advantage of the deferred queue
352 // on second thought that takes too long, but leaving the commented code here for debug purposes
353 // while(!this->entities_iterator_->completed()) {
354 // this->entities_iterator_->advance();
355 //}
356}
357
359 auto *rsp = static_cast<AsyncEventSourceResponse *>(ptr);
360 rsp->server_->sessions_.erase(rsp);
361 delete rsp; // NOLINT(cppcoreguidelines-owning-memory)
362}
363
364// helper for allowing only unique entries in the queue
366 DeferredEvent item(source, message_generator);
367
368 auto iter = std::find_if(this->deferred_queue_.begin(), this->deferred_queue_.end(),
369 [&item](const DeferredEvent &test) -> bool { return test == item; });
370
371 if (iter != this->deferred_queue_.end()) {
372 (*iter) = item;
373 } else {
374 this->deferred_queue_.push_back(item);
375 }
376}
377
379 while (!deferred_queue_.empty()) {
380 DeferredEvent &de = deferred_queue_.front();
381 std::string message = de.message_generator_(web_server_, de.source_);
382 if (this->try_send_nodefer(message.c_str(), "state")) {
383 // O(n) but memory efficiency is more important than speed here which is why std::vector was chosen
384 deferred_queue_.erase(deferred_queue_.begin());
385 } else {
386 break;
387 }
388 }
389}
390
392 if (event_buffer_.empty()) {
393 return;
394 }
395 if (event_bytes_sent_ == event_buffer_.size()) {
396 event_buffer_.resize(0);
398 return;
399 }
400
401 int bytes_sent = httpd_socket_send(this->hd_, this->fd_, event_buffer_.c_str() + event_bytes_sent_,
402 event_buffer_.size() - event_bytes_sent_, 0);
403 if (bytes_sent == HTTPD_SOCK_ERR_TIMEOUT || bytes_sent == HTTPD_SOCK_ERR_FAIL) {
404 return;
405 }
406 event_bytes_sent_ += bytes_sent;
407
408 if (event_bytes_sent_ == event_buffer_.size()) {
409 event_buffer_.resize(0);
411 }
412}
413
420
421bool AsyncEventSourceResponse::try_send_nodefer(const char *message, const char *event, uint32_t id,
422 uint32_t reconnect) {
423 if (this->fd_ == 0) {
424 return false;
425 }
426
428 if (!event_buffer_.empty()) {
429 // there is still pending event data to send first
430 return false;
431 }
432
433 // 8 spaces are standing in for the hexidecimal chunk length to print later
434 const char chunk_len_header[] = " " CRLF_STR;
435 const int chunk_len_header_len = sizeof(chunk_len_header) - 1;
436
437 event_buffer_.append(chunk_len_header);
438
439 if (reconnect) {
440 event_buffer_.append("retry: ", sizeof("retry: ") - 1);
441 event_buffer_.append(to_string(reconnect));
442 event_buffer_.append(CRLF_STR, CRLF_LEN);
443 }
444
445 if (id) {
446 event_buffer_.append("id: ", sizeof("id: ") - 1);
447 event_buffer_.append(to_string(id));
448 event_buffer_.append(CRLF_STR, CRLF_LEN);
449 }
450
451 if (event && *event) {
452 event_buffer_.append("event: ", sizeof("event: ") - 1);
453 event_buffer_.append(event);
454 event_buffer_.append(CRLF_STR, CRLF_LEN);
455 }
456
457 if (message && *message) {
458 event_buffer_.append("data: ", sizeof("data: ") - 1);
459 event_buffer_.append(message);
460 event_buffer_.append(CRLF_STR, CRLF_LEN);
461 }
462
463 if (event_buffer_.empty()) {
464 return true;
465 }
466
467 event_buffer_.append(CRLF_STR, CRLF_LEN);
468 event_buffer_.append(CRLF_STR, CRLF_LEN);
469
470 // chunk length header itself and the final chunk terminating CRLF are not counted as part of the chunk
471 int chunk_len = event_buffer_.size() - CRLF_LEN - chunk_len_header_len;
472 char chunk_len_str[9];
473 snprintf(chunk_len_str, 9, "%08x", chunk_len);
474 std::memcpy(&event_buffer_[0], chunk_len_str, 8);
475
478
479 return true;
480}
481
482void AsyncEventSourceResponse::deferrable_send_state(void *source, const char *event_type,
483 message_generator_t *message_generator) {
484 // allow all json "details_all" to go through before publishing bare state events, this avoids unnamed entries showing
485 // up in the web GUI and reduces event load during initial connect
486 if (!entities_iterator_->completed() && 0 != strcmp(event_type, "state_detail_all"))
487 return;
488
489 if (source == nullptr)
490 return;
491 if (event_type == nullptr)
492 return;
493 if (message_generator == nullptr)
494 return;
495
496 if (0 != strcmp(event_type, "state_detail_all") && 0 != strcmp(event_type, "state")) {
497 ESP_LOGE(TAG, "Can't defer non-state event");
498 }
499
502
503 if (!event_buffer_.empty() || !deferred_queue_.empty()) {
504 // outgoing event buffer or deferred queue still not empty which means downstream tcp send buffer full, no point
505 // trying to send first
506 deq_push_back_with_dedup_(source, message_generator);
507 } else {
508 std::string message = message_generator(web_server_, source);
509 if (!this->try_send_nodefer(message.c_str(), "state")) {
510 deq_push_back_with_dedup_(source, message_generator);
511 }
512 }
513}
514
515} // namespace web_server_idf
516} // namespace esphome
517
518#endif // !defined(USE_ESP_IDF)
void begin(bool include_internal=false)
value_type const & value() const
Definition optional.h:89
This class allows users to create a web server with their ESP nodes.
Definition web_server.h:149
std::string get_config_json()
Return the webserver configuration as JSON.
std::map< uint64_t, SortingGroup > sorting_groups_
Definition web_server.h:481
std::set< AsyncEventSourceResponse * > sessions_
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator)
esphome::web_server::WebServer * web_server_
void try_send_nodefer(const char *message, const char *event=nullptr, uint32_t id=0, uint32_t reconnect=0)
void handleRequest(AsyncWebServerRequest *request) override
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator)
esphome::web_server::WebServer * web_server_
void deq_push_back_with_dedup_(void *source, message_generator_t *message_generator)
AsyncEventSourceResponse(const AsyncWebServerRequest *request, esphome::web_server_idf::AsyncEventSource *server, esphome::web_server::WebServer *ws)
std::unique_ptr< esphome::web_server::ListEntitiesIterator > entities_iterator_
bool try_send_nodefer(const char *message, const char *event=nullptr, uint32_t id=0, uint32_t reconnect=0)
void printf(const char *fmt,...) __attribute__((format(printf
std::function< void(AsyncWebServerRequest *request)> on_not_found_
static esp_err_t request_post_handler(httpd_req_t *r)
std::vector< AsyncWebHandler * > handlers_
esp_err_t request_handler_(AsyncWebServerRequest *request) const
static esp_err_t request_handler(httpd_req_t *r)
AsyncWebParameter * getParam(const std::string &name)
optional< std::string > get_header(const char *name) const
void send(AsyncWebServerResponse *response)
std::map< std::string, AsyncWebParameter * > params_
void init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type)
void requestAuthentication(const char *realm=nullptr) const
bool authenticate(const char *username, const char *password) const
virtual const char * get_content_data() const =0
void addHeader(const char *name, const char *value)
mopeka_std_values val[4]
std::string build_json(const json_build_t &f)
Build a JSON string with the provided json build function.
Definition json_util.cpp:12
const char *const TAG
Definition spi.cpp:8
optional< std::string > request_get_url_query(httpd_req_t *req)
Definition utils.cpp:54
optional< std::string > request_get_header(httpd_req_t *req, const char *name)
Definition utils.cpp:37
std::string(esphome::web_server::WebServer *, void *) message_generator_t
optional< std::string > query_key_value(const std::string &query_url, const std::string &key)
Definition utils.cpp:72
bool request_has_header(httpd_req_t *req, const char *name)
Definition utils.cpp:35
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string to_string(int value)
Definition helpers.cpp:82
std::string str_sprintf(const char *fmt,...)
Definition helpers.cpp:323
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:27
std::string print()
uint16_t length
Definition tt21100.cpp:0