ESPHome 2025.10.3
Loading...
Searching...
No Matches
web_server_idf.cpp
Go to the documentation of this file.
1#ifdef USE_ESP32
2
3#include <cstdarg>
4#include <memory>
5#include <cstring>
6#include <cctype>
7
9#include "esphome/core/log.h"
10
11#include "esp_tls_crypto.h"
12#include <freertos/FreeRTOS.h>
13#include <freertos/task.h>
14
15#include "utils.h"
16#include "web_server_idf.h"
17
18#ifdef USE_WEBSERVER_OTA
19#include <multipart_parser.h>
20#include "multipart.h" // For parse_multipart_boundary and other utils
21#endif
22
23#ifdef USE_WEBSERVER
26#endif // USE_WEBSERVER
27
28// Include socket headers after Arduino headers to avoid IPADDR_NONE/INADDR_NONE macro conflicts
29#include <cerrno>
30#include <sys/socket.h>
31
32namespace esphome {
33namespace web_server_idf {
34
35#ifndef HTTPD_409
36#define HTTPD_409 "409 Conflict"
37#endif
38
39#define CRLF_STR "\r\n"
40#define CRLF_LEN (sizeof(CRLF_STR) - 1)
41
42static const char *const TAG = "web_server_idf";
43
44// Global instance to avoid guard variable (saves 8 bytes)
45// This is initialized at program startup before any threads
46namespace {
47// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
48DefaultHeaders default_headers_instance;
49} // namespace
50
51DefaultHeaders &DefaultHeaders::Instance() { return default_headers_instance; }
52
53namespace {
54// Non-blocking send function to prevent watchdog timeouts when TCP buffers are full
69int nonblocking_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_len, int flags) {
70 if (buf == nullptr) {
71 return HTTPD_SOCK_ERR_INVALID;
72 }
73
74 // Use MSG_DONTWAIT to prevent blocking when TCP send buffer is full
75 int ret = send(sockfd, buf, buf_len, flags | MSG_DONTWAIT);
76 if (ret < 0) {
77 if (errno == EAGAIN || errno == EWOULDBLOCK) {
78 // Buffer full - retry later
79 return HTTPD_SOCK_ERR_TIMEOUT;
80 }
81 // Real error
82 ESP_LOGD(TAG, "send error: errno %d", errno);
83 return HTTPD_SOCK_ERR_FAIL;
84 }
85 return ret;
86}
87} // namespace
88
90 if (this->server_) {
91 httpd_stop(this->server_);
92 this->server_ = nullptr;
93 }
94}
95
97 if (this->server_) {
98 this->end();
99 }
100 httpd_config_t config = HTTPD_DEFAULT_CONFIG();
101 config.server_port = this->port_;
102 config.uri_match_fn = [](const char * /*unused*/, const char * /*unused*/, size_t /*unused*/) { return true; };
103 if (httpd_start(&this->server_, &config) == ESP_OK) {
104 const httpd_uri_t handler_get = {
105 .uri = "",
106 .method = HTTP_GET,
108 .user_ctx = this,
109 };
110 httpd_register_uri_handler(this->server_, &handler_get);
111
112 const httpd_uri_t handler_post = {
113 .uri = "",
114 .method = HTTP_POST,
116 .user_ctx = this,
117 };
118 httpd_register_uri_handler(this->server_, &handler_post);
119
120 const httpd_uri_t handler_options = {
121 .uri = "",
122 .method = HTTP_OPTIONS,
124 .user_ctx = this,
125 };
126 httpd_register_uri_handler(this->server_, &handler_options);
127 }
128}
129
130esp_err_t AsyncWebServer::request_post_handler(httpd_req_t *r) {
131 ESP_LOGVV(TAG, "Enter AsyncWebServer::request_post_handler. uri=%s", r->uri);
132 auto content_type = request_get_header(r, "Content-Type");
133
134 if (!request_has_header(r, "Content-Length")) {
135 ESP_LOGW(TAG, "Content length is required for post: %s", r->uri);
136 httpd_resp_send_err(r, HTTPD_411_LENGTH_REQUIRED, nullptr);
137 return ESP_OK;
138 }
139
140 if (content_type.has_value()) {
141 const char *content_type_char = content_type.value().c_str();
142
143 // Check most common case first
144 if (stristr(content_type_char, "application/x-www-form-urlencoded") != nullptr) {
145 // Normal form data - proceed with regular handling
146#ifdef USE_WEBSERVER_OTA
147 } else if (stristr(content_type_char, "multipart/form-data") != nullptr) {
148 auto *server = static_cast<AsyncWebServer *>(r->user_ctx);
149 return server->handle_multipart_upload_(r, content_type_char);
150#endif
151 } else {
152 ESP_LOGW(TAG, "Unsupported content type for POST: %s", content_type_char);
153 // fallback to get handler to support backward compatibility
155 }
156 }
157
158 // Handle regular form data
159 if (r->content_len > CONFIG_HTTPD_MAX_REQ_HDR_LEN) {
160 ESP_LOGW(TAG, "Request size is to big: %zu", r->content_len);
161 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr);
162 return ESP_FAIL;
163 }
164
165 std::string post_query;
166 if (r->content_len > 0) {
167 post_query.resize(r->content_len);
168 const int ret = httpd_req_recv(r, &post_query[0], r->content_len + 1);
169 if (ret <= 0) { // 0 return value indicates connection closed
170 if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
171 httpd_resp_send_err(r, HTTPD_408_REQ_TIMEOUT, nullptr);
172 return ESP_ERR_TIMEOUT;
173 }
174 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr);
175 return ESP_FAIL;
176 }
177 }
178
179 AsyncWebServerRequest req(r, std::move(post_query));
180 return static_cast<AsyncWebServer *>(r->user_ctx)->request_handler_(&req);
181}
182
183esp_err_t AsyncWebServer::request_handler(httpd_req_t *r) {
184 ESP_LOGVV(TAG, "Enter AsyncWebServer::request_handler. method=%u, uri=%s", r->method, r->uri);
186 return static_cast<AsyncWebServer *>(r->user_ctx)->request_handler_(&req);
187}
188
190 for (auto *handler : this->handlers_) {
191 if (handler->canHandle(request)) {
192 // At now process only basic requests.
193 // OTA requires multipart request support and handleUpload for it
194 handler->handleRequest(request);
195 return ESP_OK;
196 }
197 }
198 if (this->on_not_found_) {
199 this->on_not_found_(request);
200 return ESP_OK;
201 }
202 return ESP_ERR_NOT_FOUND;
203}
204
206 delete this->rsp_;
207 for (auto *param : this->params_) {
208 delete param; // NOLINT(cppcoreguidelines-owning-memory)
209 }
210}
211
212bool AsyncWebServerRequest::hasHeader(const char *name) const { return request_has_header(*this, name); }
213
215 return request_get_header(*this, name);
216}
217
218std::string AsyncWebServerRequest::url() const {
219 auto *str = strchr(this->req_->uri, '?');
220 if (str == nullptr) {
221 return this->req_->uri;
222 }
223 return std::string(this->req_->uri, str - this->req_->uri);
224}
225
226std::string AsyncWebServerRequest::host() const { return this->get_header("Host").value(); }
227
229 httpd_resp_send(*this, response->get_content_data(), response->get_content_size());
230}
231
232void AsyncWebServerRequest::send(int code, const char *content_type, const char *content) {
233 this->init_response_(nullptr, code, content_type);
234 if (content) {
235 httpd_resp_send(*this, content, HTTPD_RESP_USE_STRLEN);
236 } else {
237 httpd_resp_send(*this, nullptr, 0);
238 }
239}
240
241void AsyncWebServerRequest::redirect(const std::string &url) {
242 httpd_resp_set_status(*this, "302 Found");
243 httpd_resp_set_hdr(*this, "Location", url.c_str());
244 httpd_resp_send(*this, nullptr, 0);
245}
246
247void AsyncWebServerRequest::init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type) {
248 // Set status code - use constants for common codes to avoid string allocation
249 const char *status = nullptr;
250 switch (code) {
251 case 200:
252 status = HTTPD_200;
253 break;
254 case 404:
255 status = HTTPD_404;
256 break;
257 case 409:
258 status = HTTPD_409;
259 break;
260 default:
261 break;
262 }
263 httpd_resp_set_status(*this, status == nullptr ? to_string(code).c_str() : status);
264
265 if (content_type && *content_type) {
266 httpd_resp_set_type(*this, content_type);
267 }
268 httpd_resp_set_hdr(*this, "Accept-Ranges", "none");
269
270 for (const auto &pair : DefaultHeaders::Instance().headers_) {
271 httpd_resp_set_hdr(*this, pair.first.c_str(), pair.second.c_str());
272 }
273
274 delete this->rsp_;
275 this->rsp_ = rsp;
276}
277
278#ifdef USE_WEBSERVER_AUTH
279bool AsyncWebServerRequest::authenticate(const char *username, const char *password) const {
280 if (username == nullptr || password == nullptr || *username == 0) {
281 return true;
282 }
283 auto auth = this->get_header("Authorization");
284 if (!auth.has_value()) {
285 return false;
286 }
287
288 auto *auth_str = auth.value().c_str();
289
290 const auto auth_prefix_len = sizeof("Basic ") - 1;
291 if (strncmp("Basic ", auth_str, auth_prefix_len) != 0) {
292 ESP_LOGW(TAG, "Only Basic authorization supported yet");
293 return false;
294 }
295
296 std::string user_info;
297 user_info += username;
298 user_info += ':';
299 user_info += password;
300
301 size_t n = 0, out;
302 esp_crypto_base64_encode(nullptr, 0, &n, reinterpret_cast<const uint8_t *>(user_info.c_str()), user_info.size());
303
304 auto digest = std::unique_ptr<char[]>(new char[n + 1]);
305 esp_crypto_base64_encode(reinterpret_cast<uint8_t *>(digest.get()), n, &out,
306 reinterpret_cast<const uint8_t *>(user_info.c_str()), user_info.size());
307
308 return strcmp(digest.get(), auth_str + auth_prefix_len) == 0;
309}
310
311void AsyncWebServerRequest::requestAuthentication(const char *realm) const {
312 httpd_resp_set_hdr(*this, "Connection", "keep-alive");
313 auto auth_val = str_sprintf("Basic realm=\"%s\"", realm ? realm : "Login Required");
314 httpd_resp_set_hdr(*this, "WWW-Authenticate", auth_val.c_str());
315 httpd_resp_send_err(*this, HTTPD_401_UNAUTHORIZED, nullptr);
316}
317#endif
318
320 // Check cache first - only successful lookups are cached
321 for (auto *param : this->params_) {
322 if (param->name() == name) {
323 return param;
324 }
325 }
326
327 // Look up value from query strings
329 if (!val.has_value()) {
330 auto url_query = request_get_url_query(*this);
331 if (url_query.has_value()) {
332 val = query_key_value(url_query.value(), name);
333 }
334 }
335
336 // Don't cache misses to avoid wasting memory when handlers check for
337 // optional parameters that don't exist in the request
338 if (!val.has_value()) {
339 return nullptr;
340 }
341
342 auto *param = new AsyncWebParameter(name, val.value()); // NOLINT(cppcoreguidelines-owning-memory)
343 this->params_.push_back(param);
344 return param;
345}
346
347void AsyncWebServerResponse::addHeader(const char *name, const char *value) {
348 httpd_resp_set_hdr(*this->req_, name, value);
349}
350
351void AsyncResponseStream::print(float value) { this->print(to_string(value)); }
352
353void AsyncResponseStream::printf(const char *fmt, ...) {
354 va_list args;
355
356 va_start(args, fmt);
357 const int length = vsnprintf(nullptr, 0, fmt, args);
358 va_end(args);
359
360 std::string str;
361 str.resize(length);
362
363 va_start(args, fmt);
364 vsnprintf(&str[0], length + 1, fmt, args);
365 va_end(args);
366
367 this->print(str);
368}
369
370#ifdef USE_WEBSERVER
372 for (auto *ses : this->sessions_) {
373 delete ses; // NOLINT(cppcoreguidelines-owning-memory)
374 }
375}
376
378 // NOLINTNEXTLINE(cppcoreguidelines-owning-memory,clang-analyzer-cplusplus.NewDeleteLeaks)
379 auto *rsp = new AsyncEventSourceResponse(request, this, this->web_server_);
380 if (this->on_connect_) {
381 this->on_connect_(rsp);
382 }
383 this->sessions_.insert(rsp);
384}
385
387 // Clean up dead sessions safely
388 // This follows the ESP-IDF pattern where free_ctx marks resources as dead
389 // and the main loop handles the actual cleanup to avoid race conditions
390 auto it = this->sessions_.begin();
391 while (it != this->sessions_.end()) {
392 auto *ses = *it;
393 // If the session has a dead socket (marked by destroy callback)
394 if (ses->fd_.load() == 0) {
395 ESP_LOGD(TAG, "Removing dead event source session");
396 it = this->sessions_.erase(it);
397 delete ses; // NOLINT(cppcoreguidelines-owning-memory)
398 } else {
399 ses->loop();
400 ++it;
401 }
402 }
403}
404
405void AsyncEventSource::try_send_nodefer(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
406 for (auto *ses : this->sessions_) {
407 if (ses->fd_.load() != 0) { // Skip dead sessions
408 ses->try_send_nodefer(message, event, id, reconnect);
409 }
410 }
411}
412
413void AsyncEventSource::deferrable_send_state(void *source, const char *event_type,
414 message_generator_t *message_generator) {
415 for (auto *ses : this->sessions_) {
416 if (ses->fd_.load() != 0) { // Skip dead sessions
417 ses->deferrable_send_state(source, event_type, message_generator);
418 }
419 }
420}
421
425 : server_(server), web_server_(ws), entities_iterator_(new esphome::web_server::ListEntitiesIterator(ws, server)) {
426 httpd_req_t *req = *request;
427
428 httpd_resp_set_status(req, HTTPD_200);
429 httpd_resp_set_type(req, "text/event-stream");
430 httpd_resp_set_hdr(req, "Cache-Control", "no-cache");
431 httpd_resp_set_hdr(req, "Connection", "keep-alive");
432
433 for (const auto &pair : DefaultHeaders::Instance().headers_) {
434 httpd_resp_set_hdr(req, pair.first.c_str(), pair.second.c_str());
435 }
436
437 httpd_resp_send_chunk(req, CRLF_STR, CRLF_LEN);
438
439 req->sess_ctx = this;
440 req->free_ctx = AsyncEventSourceResponse::destroy;
441
442 this->hd_ = req->handle;
443 this->fd_.store(httpd_req_to_sockfd(req));
444
445 // Use non-blocking send to prevent watchdog timeouts when TCP buffers are full
446 httpd_sess_set_send_override(this->hd_, this->fd_.load(), nonblocking_send);
447
448 // Configure reconnect timeout and send config
449 // this should always go through since the tcp send buffer is empty on connect
450 std::string message = ws->get_config_json();
451 this->try_send_nodefer(message.c_str(), "ping", millis(), 30000);
452
453#ifdef USE_WEBSERVER_SORTING
454 for (auto &group : ws->sorting_groups_) {
455 // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
456 json::JsonBuilder builder;
457 JsonObject root = builder.root();
458 root["name"] = group.second.name;
459 root["sorting_weight"] = group.second.weight;
460 message = builder.serialize();
461 // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
462
463 // a (very) large number of these should be able to be queued initially without defer
464 // since the only thing in the send buffer at this point is the initial ping/config
465 this->try_send_nodefer(message.c_str(), "sorting_group");
466 }
467#endif
468
470
471 // just dump them all up-front and take advantage of the deferred queue
472 // on second thought that takes too long, but leaving the commented code here for debug purposes
473 // while(!this->entities_iterator_->completed()) {
474 // this->entities_iterator_->advance();
475 //}
476}
477
479 auto *rsp = static_cast<AsyncEventSourceResponse *>(ptr);
480 ESP_LOGD(TAG, "Event source connection closed (fd: %d)", rsp->fd_.load());
481 // Mark as dead by setting fd to 0 - will be cleaned up in the main loop
482 rsp->fd_.store(0);
483 // Note: We don't delete or remove from set here to avoid race conditions
484}
485
486// helper for allowing only unique entries in the queue
488 DeferredEvent item(source, message_generator);
489
490 // Use range-based for loop instead of std::find_if to reduce template instantiation overhead and binary size
491 for (auto &event : this->deferred_queue_) {
492 if (event == item) {
493 event = item;
494 return;
495 }
496 }
497 this->deferred_queue_.push_back(item);
498}
499
501 while (!deferred_queue_.empty()) {
502 DeferredEvent &de = deferred_queue_.front();
503 std::string message = de.message_generator_(web_server_, de.source_);
504 if (this->try_send_nodefer(message.c_str(), "state")) {
505 // O(n) but memory efficiency is more important than speed here which is why std::vector was chosen
506 deferred_queue_.erase(deferred_queue_.begin());
507 } else {
508 break;
509 }
510 }
511}
512
514 if (event_buffer_.empty()) {
515 return;
516 }
517 if (event_bytes_sent_ == event_buffer_.size()) {
518 event_buffer_.resize(0);
520 return;
521 }
522
523 size_t remaining = event_buffer_.size() - event_bytes_sent_;
524 int bytes_sent =
525 httpd_socket_send(this->hd_, this->fd_.load(), event_buffer_.c_str() + event_bytes_sent_, remaining, 0);
526 if (bytes_sent == HTTPD_SOCK_ERR_TIMEOUT) {
527 // EAGAIN/EWOULDBLOCK - socket buffer full, try again later
528 // NOTE: Similar logic exists in web_server/web_server.cpp in DeferredUpdateEventSource::process_deferred_queue_()
529 // The implementations differ due to platform-specific APIs (HTTPD_SOCK_ERR_TIMEOUT vs DISCARDED, fd_.store(0) vs
530 // close()), but the failure counting and timeout logic should be kept in sync. If you change this logic, also
531 // update the Arduino implementation.
534 // Too many failures, connection is likely dead
535 ESP_LOGW(TAG, "Closing stuck EventSource connection after %" PRIu16 " failed sends",
537 this->fd_.store(0); // Mark for cleanup
538 this->deferred_queue_.clear();
539 }
540 return;
541 }
542 if (bytes_sent == HTTPD_SOCK_ERR_FAIL) {
543 // Real socket error - connection will be closed by httpd and destroy callback will be called
544 return;
545 }
546 if (bytes_sent <= 0) {
547 // Unexpected error or zero bytes sent
548 ESP_LOGW(TAG, "Unexpected send result: %d", bytes_sent);
549 return;
550 }
551
552 // Successful send - reset failure counter
554 event_bytes_sent_ += bytes_sent;
555
556 // Log partial sends for debugging
557 if (event_bytes_sent_ < event_buffer_.size()) {
558 ESP_LOGV(TAG, "Partial send: %d/%zu bytes (total: %zu/%zu)", bytes_sent, remaining, event_bytes_sent_,
559 event_buffer_.size());
560 }
561
562 if (event_bytes_sent_ == event_buffer_.size()) {
563 event_buffer_.resize(0);
565 }
566}
567
574
575bool AsyncEventSourceResponse::try_send_nodefer(const char *message, const char *event, uint32_t id,
576 uint32_t reconnect) {
577 if (this->fd_.load() == 0) {
578 return false;
579 }
580
582 if (!event_buffer_.empty()) {
583 // there is still pending event data to send first
584 return false;
585 }
586
587 // 8 spaces are standing in for the hexidecimal chunk length to print later
588 const char chunk_len_header[] = " " CRLF_STR;
589 const int chunk_len_header_len = sizeof(chunk_len_header) - 1;
590
591 event_buffer_.append(chunk_len_header);
592
593 if (reconnect) {
594 event_buffer_.append("retry: ", sizeof("retry: ") - 1);
595 event_buffer_.append(to_string(reconnect));
596 event_buffer_.append(CRLF_STR, CRLF_LEN);
597 }
598
599 if (id) {
600 event_buffer_.append("id: ", sizeof("id: ") - 1);
601 event_buffer_.append(to_string(id));
602 event_buffer_.append(CRLF_STR, CRLF_LEN);
603 }
604
605 if (event && *event) {
606 event_buffer_.append("event: ", sizeof("event: ") - 1);
607 event_buffer_.append(event);
608 event_buffer_.append(CRLF_STR, CRLF_LEN);
609 }
610
611 if (message && *message) {
612 event_buffer_.append("data: ", sizeof("data: ") - 1);
613 event_buffer_.append(message);
614 event_buffer_.append(CRLF_STR, CRLF_LEN);
615 }
616
617 if (event_buffer_.empty()) {
618 return true;
619 }
620
621 event_buffer_.append(CRLF_STR, CRLF_LEN);
622 event_buffer_.append(CRLF_STR, CRLF_LEN);
623
624 // chunk length header itself and the final chunk terminating CRLF are not counted as part of the chunk
625 int chunk_len = event_buffer_.size() - CRLF_LEN - chunk_len_header_len;
626 char chunk_len_str[9];
627 snprintf(chunk_len_str, 9, "%08x", chunk_len);
628 std::memcpy(&event_buffer_[0], chunk_len_str, 8);
629
632
633 return true;
634}
635
636void AsyncEventSourceResponse::deferrable_send_state(void *source, const char *event_type,
637 message_generator_t *message_generator) {
638 // allow all json "details_all" to go through before publishing bare state events, this avoids unnamed entries showing
639 // up in the web GUI and reduces event load during initial connect
640 if (!entities_iterator_->completed() && 0 != strcmp(event_type, "state_detail_all"))
641 return;
642
643 if (source == nullptr)
644 return;
645 if (event_type == nullptr)
646 return;
647 if (message_generator == nullptr)
648 return;
649
650 if (0 != strcmp(event_type, "state_detail_all") && 0 != strcmp(event_type, "state")) {
651 ESP_LOGE(TAG, "Can't defer non-state event");
652 }
653
656
657 if (!event_buffer_.empty() || !deferred_queue_.empty()) {
658 // outgoing event buffer or deferred queue still not empty which means downstream tcp send buffer full, no point
659 // trying to send first
660 deq_push_back_with_dedup_(source, message_generator);
661 } else {
662 std::string message = message_generator(web_server_, source);
663 if (!this->try_send_nodefer(message.c_str(), "state")) {
664 deq_push_back_with_dedup_(source, message_generator);
665 }
666 }
667}
668#endif
669
670#ifdef USE_WEBSERVER_OTA
671esp_err_t AsyncWebServer::handle_multipart_upload_(httpd_req_t *r, const char *content_type) {
672 static constexpr size_t MULTIPART_CHUNK_SIZE = 1460; // Match Arduino AsyncWebServer buffer size
673 static constexpr size_t YIELD_INTERVAL_BYTES = 16 * 1024; // Yield every 16KB to prevent watchdog
674
675 // Parse boundary and create reader
676 const char *boundary_start;
677 size_t boundary_len;
678 if (!parse_multipart_boundary(content_type, &boundary_start, &boundary_len)) {
679 ESP_LOGE(TAG, "Failed to parse multipart boundary");
680 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr);
681 return ESP_FAIL;
682 }
683
685 AsyncWebHandler *handler = nullptr;
686 for (auto *h : this->handlers_) {
687 if (h->canHandle(&req)) {
688 handler = h;
689 break;
690 }
691 }
692
693 if (!handler) {
694 ESP_LOGW(TAG, "No handler found for OTA request");
695 httpd_resp_send_err(r, HTTPD_404_NOT_FOUND, nullptr);
696 return ESP_OK;
697 }
698
699 // Upload state
700 std::string filename;
701 size_t index = 0;
702 // Create reader on heap to reduce stack usage
703 auto reader = std::make_unique<MultipartReader>("--" + std::string(boundary_start, boundary_len));
704
705 // Configure callbacks
706 reader->set_data_callback([&](const uint8_t *data, size_t len) {
707 if (!reader->has_file() || !len)
708 return;
709
710 if (filename.empty()) {
711 filename = reader->get_current_part().filename;
712 ESP_LOGV(TAG, "Processing file: '%s'", filename.c_str());
713 handler->handleUpload(&req, filename, 0, nullptr, 0, false); // Start
714 }
715
716 handler->handleUpload(&req, filename, index, const_cast<uint8_t *>(data), len, false);
717 index += len;
718 });
719
720 reader->set_part_complete_callback([&]() {
721 if (index > 0) {
722 handler->handleUpload(&req, filename, index, nullptr, 0, true); // End
723 filename.clear();
724 index = 0;
725 }
726 });
727
728 // Process data
729 std::unique_ptr<char[]> buffer(new char[MULTIPART_CHUNK_SIZE]);
730 size_t bytes_since_yield = 0;
731
732 for (size_t remaining = r->content_len; remaining > 0;) {
733 int recv_len = httpd_req_recv(r, buffer.get(), std::min(remaining, MULTIPART_CHUNK_SIZE));
734
735 if (recv_len <= 0) {
736 httpd_resp_send_err(r, recv_len == HTTPD_SOCK_ERR_TIMEOUT ? HTTPD_408_REQ_TIMEOUT : HTTPD_400_BAD_REQUEST,
737 nullptr);
738 return recv_len == HTTPD_SOCK_ERR_TIMEOUT ? ESP_ERR_TIMEOUT : ESP_FAIL;
739 }
740
741 if (reader->parse(buffer.get(), recv_len) != static_cast<size_t>(recv_len)) {
742 ESP_LOGW(TAG, "Multipart parser error");
743 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr);
744 return ESP_FAIL;
745 }
746
747 remaining -= recv_len;
748 bytes_since_yield += recv_len;
749
750 if (bytes_since_yield > YIELD_INTERVAL_BYTES) {
751 vTaskDelay(1);
752 bytes_since_yield = 0;
753 }
754 }
755
756 handler->handleRequest(&req);
757 return ESP_OK;
758}
759#endif // USE_WEBSERVER_OTA
760
761} // namespace web_server_idf
762} // namespace esphome
763
764#endif // !defined(USE_ESP32)
uint8_t h
Definition bl0906.h:2
uint8_t status
Definition bl0942.h:8
void begin(bool include_internal=false)
Builder class for creating JSON documents without lambdas.
Definition json_util.h:62
value_type const & value() const
Definition optional.h:94
This class allows users to create a web server with their ESP nodes.
Definition web_server.h:166
std::string get_config_json()
Return the webserver configuration as JSON.
std::map< uint64_t, SortingGroup > sorting_groups_
Definition web_server.h:494
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
virtual void handleRequest(AsyncWebServerRequest *request)
virtual void handleUpload(AsyncWebServerRequest *request, const std::string &filename, size_t index, uint8_t *data, size_t len, bool final)
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
esp_err_t handle_multipart_upload_(httpd_req_t *r, const char *content_type)
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)
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
std::vector< AsyncWebParameter * > params_
virtual const char * get_content_data() const =0
void addHeader(const char *name, const char *value)
const char * message
Definition component.cpp:38
uint16_t flags
mopeka_std_values val[4]
const char *const TAG
Definition spi.cpp:8
optional< std::string > request_get_url_query(httpd_req_t *req)
Definition utils.cpp:56
optional< std::string > request_get_header(httpd_req_t *req, const char *name)
Definition utils.cpp:39
bool parse_multipart_boundary(const char *content_type, const char **boundary_start, size_t *boundary_len)
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:74
const char * stristr(const char *haystack, const char *needle)
Definition utils.cpp:104
bool request_has_header(httpd_req_t *req, const char *name)
Definition utils.cpp:37
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:304
std::string str_sprintf(const char *fmt,...)
Definition helpers.cpp:222
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:29
std::string print()
uint16_t length
Definition tt21100.cpp:0