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