ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
http_request.h
Go to the documentation of this file.
1#pragma once
2
3#include <list>
4#include <memory>
5#include <set>
6#include <utility>
7#include <vector>
8
15#include "esphome/core/hal.h"
17#include "esphome/core/log.h"
18
19namespace esphome::http_request {
20
21struct Header {
22 std::string name;
23 std::string value;
24};
25
27template<typename... Ts> struct RequestHeader {
28 const char *name;
29 const char *(*value)(Ts...);
30};
31
32// Some common HTTP status codes
60
67inline bool is_redirect(int const status) {
68 switch (status) {
74 return true;
75 default:
76 return false;
77 }
78}
79
88inline bool is_success(int const status) { return status >= HTTP_STATUS_OK && status < HTTP_STATUS_MULTIPLE_CHOICES; }
89
91inline bool should_collect_header(const std::vector<std::string> &lower_case_collect_headers,
92 const std::string &lower_header_name) {
93 for (const auto &h : lower_case_collect_headers) {
94 if (h == lower_header_name)
95 return true;
96 }
97 return false;
98}
99
100/*
101 * HTTP Container Read Semantics
102 * =============================
103 *
104 * IMPORTANT: These semantics differ from standard BSD sockets!
105 *
106 * BSD socket read() returns:
107 * > 0: bytes read
108 * == 0: connection closed (EOF)
109 * < 0: error (check errno)
110 *
111 * HttpContainer::read() returns:
112 * > 0: bytes read successfully
113 * == 0: no data available yet OR all content read
114 * (caller should check bytes_read vs content_length)
115 * < 0: error or connection closed (caller should EXIT)
116 * HTTP_ERROR_CONNECTION_CLOSED (-1) = connection closed prematurely
117 * other negative values = platform-specific errors
118 *
119 * Platform behaviors:
120 * - ESP-IDF: blocking reads, 0 only returned when all content read
121 * - Arduino: non-blocking, 0 means "no data yet" or "all content read"
122 *
123 * Chunked responses that complete in a reasonable time work correctly on both
124 * platforms. The limitation below applies only to *streaming* chunked
125 * responses where data arrives slowly over a long period.
126 *
127 * Streaming chunked responses are NOT supported (all platforms):
128 * The read helpers (http_read_loop_result, http_read_fully) block the main
129 * event loop until all response data is received. For streaming responses
130 * where data trickles in slowly (e.g., TTS streaming via ffmpeg proxy),
131 * this starves the event loop on both ESP-IDF and Arduino. If data arrives
132 * just often enough to avoid the caller's timeout, the loop runs
133 * indefinitely. If data stops entirely, ESP-IDF fails with
134 * -ESP_ERR_HTTP_EAGAIN (transport timeout) while Arduino spins with
135 * delay(1) until the caller's timeout fires. Supporting streaming requires
136 * a non-blocking incremental read pattern that yields back to the event
137 * loop between chunks. Components that need streaming should use
138 * esp_http_client directly on a separate FreeRTOS task with
139 * esp_http_client_is_complete_data_received() for completion detection
140 * (see audio_reader.cpp for an example).
141 *
142 * Chunked transfer encoding - platform differences:
143 * - ESP-IDF HttpContainer:
144 * HttpContainerIDF overrides is_read_complete() to call
145 * esp_http_client_is_complete_data_received(), which is the
146 * authoritative completion check for both chunked and non-chunked
147 * transfers. When esp_http_client_read() returns 0 for a completed
148 * chunked response, read() returns 0 and is_read_complete() returns
149 * true, so callers get COMPLETE from http_read_loop_result().
150 *
151 * - Arduino HttpContainer:
152 * Chunked responses are decoded internally (see
153 * HttpContainerArduino::read_chunked_()). When the final chunk arrives,
154 * is_chunked_ is cleared and content_length is set to bytes_read_.
155 * Completion is then detected via is_read_complete(), and a subsequent
156 * read() returns 0 to indicate "all content read" (not
157 * HTTP_ERROR_CONNECTION_CLOSED).
158 *
159 * Use the helper functions below instead of checking return values directly:
160 * - http_read_loop_result(): for manual loops with per-chunk processing
161 * - http_read_fully(): for simple "read N bytes into buffer" operations
162 */
163
166static constexpr int HTTP_ERROR_CONNECTION_CLOSED = -1;
167
169enum class HttpReadStatus : uint8_t {
170 OK,
171 ERROR,
172 TIMEOUT,
173};
174
180
182enum class HttpReadLoopResult : uint8_t {
183 DATA,
184 COMPLETE,
185 RETRY,
186 ERROR,
187 TIMEOUT,
188};
189
196
203inline HttpReadLoopResult http_read_loop_result(int bytes_read_or_error, uint32_t &last_data_time, uint32_t timeout_ms,
204 bool is_read_complete) {
205 if (bytes_read_or_error > 0) {
206 last_data_time = millis();
208 }
209 if (bytes_read_or_error < 0) {
211 }
212 // bytes_read_or_error == 0: either "no data yet" or "all content read"
213 if (is_read_complete) {
215 }
216 if (millis() - last_data_time >= timeout_ms) {
218 }
219 delay(1); // Small delay to prevent tight spinning
221}
222
223class HttpRequestComponent;
224
225class HttpContainer : public Parented<HttpRequestComponent> {
226 public:
227 virtual ~HttpContainer() = default;
228 size_t content_length{0};
229 int status_code{-1};
231
259 virtual int read(uint8_t *buf, size_t max_len) = 0;
261 virtual int read_available(uint8_t *buf, size_t max_len) { return this->read(buf, max_len); }
262 virtual void end() = 0;
263
268
269 void set_secure(bool secure) { this->secure_ = secure; }
270 void set_watchdog_timeout(uint32_t watchdog_timeout) { this->watchdog_timeout_ = watchdog_timeout; }
272 void set_chunked(bool chunked) { this->is_chunked_ = chunked; }
273
274 size_t get_bytes_read() const { return this->bytes_read_; }
275
282 virtual bool is_read_complete() const {
283 // Per RFC 9112, these responses have no body:
284 // - 1xx (Informational), 204 No Content, 205 Reset Content, 304 Not Modified
285 if ((this->status_code >= 100 && this->status_code < 200) || this->status_code == HTTP_STATUS_NO_CONTENT ||
287 return true;
288 }
289 // For non-chunked responses, complete when bytes_read >= content_length
290 // This handles both Content-Length: 0 and Content-Length: N cases
291 return !this->is_chunked_ && this->bytes_read_ >= this->content_length;
292 }
293
299 std::string get_response_header(const std::string &header_name);
300
301 protected:
302 size_t bytes_read_{0};
304 bool secure_{false};
305 bool is_chunked_{false};
306 std::vector<Header> response_headers_{};
307};
308
317inline HttpReadResult http_read_fully(HttpContainer *container, uint8_t *buffer, size_t total_size, size_t chunk_size,
318 uint32_t timeout_ms) {
319 size_t read_index = 0;
320 uint32_t last_data_time = millis();
321
322 while (read_index < total_size) {
323 int read_bytes_or_error = container->read(buffer + read_index, std::min(chunk_size, total_size - read_index));
324
325 App.feed_wdt();
326 yield();
327
328 auto result = http_read_loop_result(read_bytes_or_error, last_data_time, timeout_ms, container->is_read_complete());
329 if (result == HttpReadLoopResult::RETRY)
330 continue;
331 if (result == HttpReadLoopResult::COMPLETE)
332 break; // Server sent less data than requested, but transfer is complete
333 if (result == HttpReadLoopResult::ERROR)
334 return {HttpReadStatus::ERROR, read_bytes_or_error};
335 if (result == HttpReadLoopResult::TIMEOUT)
336 return {HttpReadStatus::TIMEOUT, 0};
337
338 read_index += read_bytes_or_error;
339 }
340 return {HttpReadStatus::OK, 0};
341}
342
343class HttpRequestResponseTrigger final : public Trigger<std::shared_ptr<HttpContainer>, std::string &> {
344 public:
345 void process(const std::shared_ptr<HttpContainer> &container, std::string &response_body) {
346 this->trigger(container, response_body);
347 }
348};
349
357
361 using Handler = void (*)(PendingRequest *request, RequestEvent event);
365 void free_buffer();
366
369 // The action, so that a stop of it can find its requests
370 void *owner;
371 // nullptr until the request has started
372 std::shared_ptr<HttpContainer> container;
373 uint8_t *buf{nullptr};
374 size_t buf_size{0};
375 size_t read_index{0};
379 bool capture{false};
380 bool reading{false};
381 bool failed{false};
382 bool finished{false};
383 bool cancelled{false};
384};
385
387 public:
388 void dump_config() override;
389 float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
390 void setup() override;
391 void loop() override;
392 void on_safe_shutdown() override;
393 void on_shutdown() override;
394
397 void submit(PendingRequest *request);
399 void cancel(void *owner);
400
401 void set_useragent(const char *useragent) { this->useragent_ = useragent; }
402 void set_timeout(uint32_t timeout) { this->timeout_ = timeout; }
403 uint32_t get_timeout() const { return this->timeout_; }
404 void set_watchdog_timeout(uint32_t watchdog_timeout) { this->watchdog_timeout_ = watchdog_timeout; }
407 if (this->watchdog_timeout_ == 0 || timeout_ms <= this->timeout_)
408 return this->watchdog_timeout_;
409 return this->watchdog_timeout_ + 3 * (timeout_ms - this->timeout_);
410 }
411 void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; }
412 void set_redirect_limit(uint16_t limit) { this->redirect_limit_ = limit; }
413 void set_max_concurrent_requests(uint8_t max_concurrent) { this->max_concurrent_ = max_concurrent; }
414
415 std::shared_ptr<HttpContainer> get(const char *url) { return this->start(url, "GET", "", std::vector<Header>{}); }
416 std::shared_ptr<HttpContainer> get(const char *url, const std::vector<Header> &request_headers) {
417 return this->start(url, "GET", "", request_headers);
418 }
419 std::shared_ptr<HttpContainer> get(const char *url, const std::vector<Header> &request_headers,
420 const std::vector<std::string> &lower_case_collect_headers) {
421 return this->start(url, "GET", "", request_headers, lower_case_collect_headers);
422 }
423 std::shared_ptr<HttpContainer> post(const char *url, const std::string &body) {
424 return this->start(url, "POST", body, std::vector<Header>{});
425 }
426 std::shared_ptr<HttpContainer> post(const char *url, const std::string &body,
427 const std::vector<Header> &request_headers) {
428 return this->start(url, "POST", body, request_headers);
429 }
430 std::shared_ptr<HttpContainer> post(const char *url, const std::string &body,
431 const std::vector<Header> &request_headers,
432 const std::vector<std::string> &lower_case_collect_headers) {
433 return this->start(url, "POST", body, request_headers, lower_case_collect_headers);
434 }
435
436 std::shared_ptr<HttpContainer> get(const std::string &url) { return this->get(url.c_str()); }
437 std::shared_ptr<HttpContainer> get(const std::string &url, const std::vector<Header> &request_headers) {
438 return this->get(url.c_str(), request_headers);
439 }
440 std::shared_ptr<HttpContainer> get(const std::string &url, const std::vector<Header> &request_headers,
441 const std::vector<std::string> &lower_case_collect_headers) {
442 return this->get(url.c_str(), request_headers, lower_case_collect_headers);
443 }
444 std::shared_ptr<HttpContainer> post(const std::string &url, const std::string &body) {
445 return this->post(url.c_str(), body);
446 }
447 std::shared_ptr<HttpContainer> post(const std::string &url, const std::string &body,
448 const std::vector<Header> &request_headers) {
449 return this->post(url.c_str(), body, request_headers);
450 }
451 std::shared_ptr<HttpContainer> post(const std::string &url, const std::string &body,
452 const std::vector<Header> &request_headers,
453 const std::vector<std::string> &lower_case_collect_headers) {
454 return this->post(url.c_str(), body, request_headers, lower_case_collect_headers);
455 }
456
457 // Remove before 2027.1.0
458 ESPDEPRECATED("Pass request_headers as std::vector<Header> instead of std::list. Removed in 2027.1.0.", "2026.7.0")
459 std::shared_ptr<HttpContainer> get(const std::string &url, const std::list<Header> &request_headers) {
460 return this->get(url, std::vector<Header>(request_headers.begin(), request_headers.end()));
461 }
462 // Remove before 2027.1.0
463 ESPDEPRECATED("Pass request_headers as std::vector<Header> instead of std::list. Removed in 2027.1.0.", "2026.7.0")
464 std::shared_ptr<HttpContainer> get(const std::string &url, const std::list<Header> &request_headers,
465 const std::vector<std::string> &collect_headers) {
466 return this->get(url, std::vector<Header>(request_headers.begin(), request_headers.end()), collect_headers);
467 }
468 // Remove before 2027.1.0
469 ESPDEPRECATED("Pass request_headers as std::vector<Header> instead of std::list. Removed in 2027.1.0.", "2026.7.0")
470 std::shared_ptr<HttpContainer> post(const std::string &url, const std::string &body,
471 const std::list<Header> &request_headers) {
472 return this->post(url, body, std::vector<Header>(request_headers.begin(), request_headers.end()));
473 }
474 // Remove before 2027.1.0
475 ESPDEPRECATED("Pass request_headers as std::vector<Header> instead of std::list. Removed in 2027.1.0.", "2026.7.0")
476 std::shared_ptr<HttpContainer> post(const std::string &url, const std::string &body,
477 const std::list<Header> &request_headers,
478 const std::vector<std::string> &collect_headers) {
479 return this->post(url, body, std::vector<Header>(request_headers.begin(), request_headers.end()), collect_headers);
480 }
481
482 std::shared_ptr<HttpContainer> start(const char *url, const char *method, const std::string &body,
483 const std::vector<Header> &request_headers) {
484 // Call perform() directly to avoid ambiguity with the deprecated overloads
485 return this->perform(url, method, body, request_headers, {}, this->timeout_);
486 }
487 std::shared_ptr<HttpContainer> start(const std::string &url, const std::string &method, const std::string &body,
488 const std::vector<Header> &request_headers) {
489 return this->start(url.c_str(), method.c_str(), body, request_headers);
490 }
491
492 // Remove before 2027.1.0
493 ESPDEPRECATED("Pass request_headers as std::vector<Header> instead of std::list. Removed in 2027.1.0.", "2026.7.0")
494 std::shared_ptr<HttpContainer> start(const std::string &url, const std::string &method, const std::string &body,
495 const std::list<Header> &request_headers) {
496 return this->start(url, method, body, std::vector<Header>(request_headers.begin(), request_headers.end()));
497 }
498
499 // Remove before 2027.1.0
500 ESPDEPRECATED("Pass collect_headers as std::vector<std::string> instead of std::set. Removed in 2027.1.0.",
501 "2026.7.0")
502 std::shared_ptr<HttpContainer> start(const std::string &url, const std::string &method, const std::string &body,
503 const std::vector<Header> &request_headers,
504 const std::set<std::string> &collect_headers) {
505 std::vector<std::string> lower;
506 lower.reserve(collect_headers.size());
507 for (const auto &h : collect_headers) {
508 lower.push_back(str_lower_case(h)); // NOLINT
509 }
510 return this->perform(url.c_str(), method.c_str(), body, request_headers, lower, this->timeout_);
511 }
512
513 // Remove before 2027.1.0
514 ESPDEPRECATED("Pass request_headers as std::vector<Header> instead of std::list, and collect_headers as "
515 "std::vector<std::string> instead of std::set. Removed in 2027.1.0.",
516 "2026.7.0")
517 std::shared_ptr<HttpContainer> start(const std::string &url, const std::string &method, const std::string &body,
518 const std::list<Header> &request_headers,
519 const std::set<std::string> &collect_headers) {
520 std::vector<std::string> lower;
521 lower.reserve(collect_headers.size());
522 for (const auto &h : collect_headers) {
523 lower.push_back(str_lower_case(h)); // NOLINT
524 }
525 return this->perform(url.c_str(), method.c_str(), body,
526 std::vector<Header>(request_headers.begin(), request_headers.end()), lower, this->timeout_);
527 }
528
529 // Remove before 2027.1.0
530 ESPDEPRECATED("Pass request_headers as std::vector<Header> instead of std::list. Removed in 2027.1.0.", "2026.7.0")
531 std::shared_ptr<HttpContainer> start(const std::string &url, const std::string &method, const std::string &body,
532 const std::list<Header> &request_headers,
533 const std::vector<std::string> &lower_case_collect_headers) {
534 return this->perform(url.c_str(), method.c_str(), body,
535 std::vector<Header>(request_headers.begin(), request_headers.end()),
536 lower_case_collect_headers, this->timeout_);
537 }
538
539 std::shared_ptr<HttpContainer> start(const char *url, const char *method, const std::string &body,
540 const std::vector<Header> &request_headers,
541 const std::vector<std::string> &lower_case_collect_headers) {
542 return this->perform(url, method, body, request_headers, lower_case_collect_headers, this->timeout_);
543 }
544 std::shared_ptr<HttpContainer> start(const char *url, const char *method, const std::string &body,
545 const std::vector<Header> &request_headers,
546 const std::vector<std::string> &lower_case_collect_headers,
547 uint32_t timeout_ms) {
548 return this->perform(url, method, body, request_headers, lower_case_collect_headers, timeout_ms);
549 }
550 std::shared_ptr<HttpContainer> start(const std::string &url, const std::string &method, const std::string &body,
551 const std::vector<Header> &request_headers,
552 const std::vector<std::string> &lower_case_collect_headers) {
553 return this->start(url.c_str(), method.c_str(), body, request_headers, lower_case_collect_headers);
554 }
555
559 virtual std::shared_ptr<HttpContainer> begin(const char *url, const char *method, std::string &&body,
560 const std::vector<Header> &request_headers,
561 const std::vector<std::string> &lower_case_collect_headers,
562 uint32_t timeout_ms) {
563 return this->perform(url, method, body, request_headers, lower_case_collect_headers, timeout_ms);
564 }
565
566 protected:
567 virtual std::shared_ptr<HttpContainer> perform(const char *url, const char *method, const std::string &body,
568 const std::vector<Header> &request_headers,
569 const std::vector<std::string> &lower_case_collect_headers,
570 uint32_t timeout_ms) = 0;
572 static constexpr uint32_t READ_BUDGET_MS = 10;
574 static constexpr uint8_t MAX_QUEUED = 4;
575
576 bool process_();
577 void drain_();
578 void begin_request_(PendingRequest *request);
579 bool advance_(PendingRequest *request);
580 void finish_(PendingRequest *request);
581 void remove_dead_();
582
583 const char *useragent_{nullptr};
585 uint16_t redirect_limit_{};
588 // Oldest first; the first max_concurrent_ live requests run
590 // Depth of calls that hold request pointers; finished and cancelled requests are freed when it returns to 0
591 uint8_t busy_{0};
592 uint8_t max_concurrent_{1};
593 bool shutting_down_{false};
594};
595
596template<typename... Ts> class HttpRequestSendAction final : public Action<Ts...> {
597 public:
599 TEMPLATABLE_VALUE(std::string, url)
600 TEMPLATABLE_VALUE(const char *, method)
601 TEMPLATABLE_VALUE(std::string, body)
603#ifdef USE_HTTP_REQUEST_RESPONSE
604 TEMPLATABLE_VALUE(bool, capture_response)
605#endif
606
607 static_assert(std::is_trivially_copyable_v<RequestHeader<Ts...>> &&
608 sizeof(RequestHeader<Ts...>) % sizeof(uint32_t) == 0,
609 "ESP8266 reads request headers from flash in whole words");
611 void set_request_headers(const RequestHeader<Ts...> *headers, size_t count) {
612 this->request_headers_ = ConstVector<RequestHeader<Ts...>>(headers, count);
613 }
614
615 void add_collect_header(const char *value) { this->lower_case_collect_headers_.emplace_back(value); }
616
617 void init_json(size_t count) { this->json_.init(count); }
618 void add_json(const char *key, TemplatableValue<std::string, Ts...> value) { this->json_.push_back({key, value}); }
619
620 void set_json(std::function<void(Ts..., JsonObject)> json_func) { this->json_func_ = json_func; }
621
622#ifdef USE_HTTP_REQUEST_RESPONSE
626#endif
628
629 Trigger<Ts...> *get_error_trigger() { return &this->error_trigger_; }
630
631 void set_max_response_buffer_size(size_t max_response_buffer_size) {
632 this->max_response_buffer_size_ = max_response_buffer_size;
633 }
634
635 void play_complex(const Ts &...x) override {
636 this->num_running_++;
637 this->parent_->submit(new Request(this, x...)); // NOLINT(cppcoreguidelines-owning-memory)
638 }
639
640 void play(const Ts &...x) override { /* see play_complex */ }
641
642 protected:
643 struct Request final : PendingRequest {
644 Request(HttpRequestSendAction *action, const Ts &...x)
646 std::tuple<stored_trigger_arg_t<Ts>...> args;
647 };
648
649 static void handle_event(PendingRequest *request, RequestEvent event) {
650 auto *self = static_cast<Request *>(request);
651 auto *action = static_cast<HttpRequestSendAction *>(request->owner);
652 switch (event) {
654 std::apply([action, self](auto &...x) { action->start_request_(*self, x...); }, self->args);
655 break;
657 action->finish_(*self);
658 break;
660 delete self; // NOLINT(cppcoreguidelines-owning-memory)
661 break;
663 action->stop_complex();
664 break;
665 }
666 }
667
668 void stop() override { this->parent_->cancel(this); }
669
670 void start_request_(Request &request, const Ts &...x) {
671 std::string body;
672 if (this->body_.has_value()) {
673 body = this->body_.value(x...);
674 }
675 if (!this->json_.empty()) {
676 body = json::build_json([this, x...](JsonObject root) mutable { this->encode_json_(x..., root); });
677 }
678 if (this->json_func_ != nullptr) {
679 body = json::build_json([this, x...](JsonObject root) mutable { this->json_func_(x..., root); });
680 }
681 std::vector<Header> request_headers;
682 request_headers.reserve(this->request_headers_.size());
683 for (const auto &header : this->request_headers_) {
684 request_headers.push_back({header.name, header.value(x...)});
685 }
686
687 request.timeout = this->timeout_.has_value() ? this->timeout_.value(x...) : this->parent_->get_timeout();
688#ifdef USE_HTTP_REQUEST_RESPONSE
689 request.capture = this->capture_response_.value(x...);
690#endif
691 request.buf_size = this->max_response_buffer_size_;
692 request.container = this->parent_->begin(this->url_.value(x...).c_str(), this->method_.value(x...), std::move(body),
693 request_headers, this->lower_case_collect_headers_, request.timeout);
694 }
695
696 void finish_(Request &request) {
697 auto &container = request.container;
698 if (request.failed) {
699 std::apply([this](auto &...args) { this->error_trigger_.trigger(args...); }, request.args);
700 }
701#ifdef USE_HTTP_REQUEST_RESPONSE
702 else if (request.capture) {
703 std::string response_body;
704 if (request.buf != nullptr)
705 response_body.assign(reinterpret_cast<char *>(request.buf), request.read_index);
706 request.free_buffer();
707 std::apply(
708 [this, &container, &response_body](auto &...args) {
709 this->success_trigger_with_response_.trigger(container, response_body, args...);
710 },
711 request.args);
712 }
713#endif
714 else {
715 std::apply([this, &container](auto &...args) { this->success_trigger_.trigger(container, args...); },
716 request.args);
717 }
718 // End it before the chain can start another request, so that one client is held at a time
719 if (container != nullptr) {
720 container->end();
721 container.reset();
722 }
723 // A stop from the triggers ended this run; a run started after it continues the chain instead
724 if (!request.cancelled)
725 std::apply([this](auto &...args) { this->play_next_(args...); }, request.args);
726 }
727
728 void encode_json_(Ts... x, JsonObject root) {
729 for (const auto &item : this->json_) {
730 auto val = item.second;
731 root[item.first] = val.value(x...);
732 }
733 }
736 std::vector<std::string> lower_case_collect_headers_{"content-type", "content-length"};
737 FixedVector<std::pair<const char *, TemplatableValue<std::string, Ts...>>> json_{};
738 std::function<void(Ts..., JsonObject)> json_func_{nullptr};
739#ifdef USE_HTTP_REQUEST_RESPONSE
741#endif
744
746};
747
748} // namespace esphome::http_request
Heap-allocating helper functions.
uint8_t h
Definition bl0906.h:2
uint8_t status
Definition bl0942.h:8
void play_next_(const Ts &...x)
Definition automation.h:613
void feed_wdt()
Feed the task watchdog.
Lightweight read-only view over a const array stored in RODATA (will typically be in flash memory).
Definition helpers.h:130
Fixed-capacity vector - sized once through init() or try_init(); push_back never reallocates This avo...
Definition helpers.h:624
Helper class to easily give an object a parent of type T.
Definition helpers.h:2073
Primary TemplatableValue: stores either a constant value or a function pointer.
Definition automation.h:168
std::string get_response_header(const std::string &header_name)
Get the value of a response header by name.
virtual int read(uint8_t *buf, size_t max_len)=0
Read data from the HTTP response body.
virtual bool is_read_complete() const
Check if all expected content has been read.
std::vector< Header > response_headers_
int status_code
-1 indicates no response received yet
virtual HttpRequestProgress poll()
Advance a request started with HttpRequestComponent::begin(); call from the main loop until it is no ...
virtual int read_available(uint8_t *buf, size_t max_len)
Like read(), but returns 0 at once when no data has arrived yet, where read() may wait for it.
void set_watchdog_timeout(uint32_t watchdog_timeout)
bool is_chunked_
True if response uses chunked transfer encoding.
bool advance_(PendingRequest *request)
void cancel(void *owner)
For HttpRequestSendAction: drop the requests of a stopped action without firing their triggers.
void set_max_concurrent_requests(uint8_t max_concurrent)
static constexpr uint32_t READ_BUDGET_MS
Longest time one loop() pass spends reading a response body.
ESPDEPRECATED("Pass request_headers as std::vector<Header> instead of std::list. Removed in 2027.1.0.", "2026.7.0") std
std::shared_ptr< HttpContainer > post(const std::string &url, const std::string &body)
void set_useragent(const char *useragent)
std::shared_ptr< HttpContainer > get(const char *url)
void finish_(PendingRequest *request)
std::shared_ptr< HttpContainer > start(const char *url, const char *method, const std::string &body, const std::vector< Header > &request_headers, const std::vector< std::string > &lower_case_collect_headers)
virtual std::shared_ptr< HttpContainer > perform(const char *url, const char *method, const std::string &body, const std::vector< Header > &request_headers, const std::vector< std::string > &lower_case_collect_headers, uint32_t timeout_ms)=0
std::shared_ptr< HttpContainer > post(const char *url, const std::string &body, const std::vector< Header > &request_headers, const std::vector< std::string > &lower_case_collect_headers)
ESPDEPRECATED("Pass request_headers as std::vector<Header> instead of std::list, and collect_headers as " "std::vector<std::string> instead of std::set. Removed in 2027.1.0.", "2026.7.0") std
std::shared_ptr< HttpContainer > start(const std::string &url, const std::string &method, const std::string &body, const std::vector< Header > &request_headers)
std::shared_ptr< HttpContainer > post(const std::string &url, const std::string &body, const std::vector< Header > &request_headers, const std::vector< std::string > &lower_case_collect_headers)
void set_follow_redirects(bool follow_redirects)
std::shared_ptr< HttpContainer > post(const char *url, const std::string &body, const std::vector< Header > &request_headers)
std::shared_ptr< HttpContainer > get(const std::string &url, const std::vector< Header > &request_headers, const std::vector< std::string > &lower_case_collect_headers)
void begin_request_(PendingRequest *request)
uint32_t get_watchdog_timeout(uint32_t timeout_ms) const
void submit(PendingRequest *request)
For HttpRequestSendAction: queue a request and take ownership of it.
void set_watchdog_timeout(uint32_t watchdog_timeout)
std::shared_ptr< HttpContainer > start(const char *url, const char *method, const std::string &body, const std::vector< Header > &request_headers, const std::vector< std::string > &lower_case_collect_headers, uint32_t timeout_ms)
std::shared_ptr< HttpContainer > get(const char *url, const std::vector< Header > &request_headers)
std::shared_ptr< HttpContainer > post(const char *url, const std::string &body)
std::shared_ptr< HttpContainer > start(const char *url, const char *method, const std::string &body, const std::vector< Header > &request_headers)
ESPDEPRECATED("Pass collect_headers as std::vector<std::string> instead of std::set. Removed in 2027.1.0.", "2026.7.0") std
std::shared_ptr< HttpContainer > post(const std::string &url, const std::string &body, const std::vector< Header > &request_headers)
std::shared_ptr< HttpContainer > start(const std::string &url, const std::string &method, const std::string &body, const std::vector< Header > &request_headers, const std::vector< std::string > &lower_case_collect_headers)
std::shared_ptr< HttpContainer > get(const std::string &url)
static constexpr uint8_t MAX_QUEUED
Requests that wait for a free slot; more are dropped with on_error.
virtual std::shared_ptr< HttpContainer > begin(const char *url, const char *method, std::string &&body, const std::vector< Header > &request_headers, const std::vector< std::string > &lower_case_collect_headers, uint32_t timeout_ms)
Start a request without waiting for the response, then poll() the container until it is not pending.
std::shared_ptr< HttpContainer > get(const std::string &url, const std::vector< Header > &request_headers)
std::shared_ptr< HttpContainer > get(const char *url, const std::vector< Header > &request_headers, const std::vector< std::string > &lower_case_collect_headers)
void process(const std::shared_ptr< HttpContainer > &container, std::string &response_body)
void set_max_response_buffer_size(size_t max_response_buffer_size)
TEMPLATABLE_VALUE(std::string, url) TEMPLATABLE_VALUE(const char *
Trigger< std::shared_ptr< HttpContainer >, std::string &, Ts... > success_trigger_with_response_
void start_request_(Request &request, const Ts &...x)
void play_complex(const Ts &...x) override
std::function< void(Ts..., JsonObject)> json_func_
ConstVector< RequestHeader< Ts... > > request_headers_
Trigger< std::shared_ptr< HttpContainer >, Ts... > success_trigger_
method timeout TEMPLATABLE_VALUE(bool, capture_response) static_assert(std void set_request_headers(const RequestHeader< Ts... > *headers, size_t count)
Codegen only: the table must outlive the action.
std::vector< std::string > lower_case_collect_headers_
void set_json(std::function< void(Ts..., JsonObject)> json_func)
void add_json(const char *key, TemplatableValue< std::string, Ts... > value)
Trigger< std::shared_ptr< HttpContainer >, std::string &, Ts... > * get_success_trigger_with_response()
void encode_json_(Ts... x, JsonObject root)
static void handle_event(PendingRequest *request, RequestEvent event)
Trigger< std::shared_ptr< HttpContainer >, Ts... > * get_success_trigger()
HttpRequestSendAction(HttpRequestComponent *parent)
FixedVector< std::pair< const char *, TemplatableValue< std::string, Ts... > > > json_
void yield(void)
const char * key
unsigned count
Definition gif.cpp:51
uint32_t buffer
Definition gif.cpp:50
mopeka_std_values val[3]
bool should_collect_header(const std::vector< std::string > &lower_case_collect_headers, const std::string &lower_header_name)
Check if a header name should be collected (linear scan, fine for small lists)
HttpRequestProgress
Progress of a request started with HttpRequestComponent::begin()
@ HTTP_REQUEST_PROGRESS_FAILED
No response; the container must still be ended.
@ HTTP_REQUEST_PROGRESS_DONE
Response headers received; status_code is set and the body can be read.
@ HTTP_REQUEST_PROGRESS_PENDING
Still connecting, sending, or waiting for the response headers.
RequestEvent
What HttpRequestComponent asks of the action that owns a request.
@ REQUEST_EVENT_FINISH
Fire the triggers and continue the action chain.
@ REQUEST_EVENT_START
Build the request and call begin()
@ REQUEST_EVENT_DESTROY
Free the request.
@ REQUEST_EVENT_ABANDON
Dropped at shutdown: stop the action, so that it can run again.
HttpReadLoopResult
Result of processing a non-blocking read with timeout (for manual loops)
@ TIMEOUT
Timeout waiting for data, caller should exit loop.
@ COMPLETE
All content has been read, caller should exit loop.
@ ERROR
Read error, caller should exit loop.
@ RETRY
No data yet, already delayed, caller should continue loop.
@ DATA
Data was read, process it.
bool is_success(int const status)
Checks if the given HTTP status code indicates a successful request.
HttpReadLoopResult http_read_loop_result(int bytes_read_or_error, uint32_t &last_data_time, uint32_t timeout_ms, bool is_read_complete)
Process a read result with timeout tracking and delay handling.
bool is_redirect(int const status)
Returns true if the HTTP status code is a redirect.
HttpReadResult http_read_fully(HttpContainer *container, uint8_t *buffer, size_t total_size, size_t chunk_size, uint32_t timeout_ms)
Read data from HTTP container into buffer with timeout handling Handles feed_wdt, yield,...
HttpReadStatus
Status of a read operation.
@ TIMEOUT
Timeout waiting for data.
@ OK
Read completed successfully.
SerializationBuffer build_json(const json_build_t &f)
Build a JSON string with the provided json build function.
Definition json_util.cpp:19
constexpr float AFTER_WIFI
For components that should be initialized after WiFi is connected.
Definition component.h:55
std::string str_lower_case(const std::string &str)
Convert the string to lower case.
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
Application App
Global storage of Application pointer - only one Application can exist.
STL namespace.
for(uint32_t *p=scan_start;p< stack_top &&p< scan_start+64 &&bt_count< MAX_BACKTRACE;p++)
static void uint32_t
Result of an HTTP read operation.
HttpReadStatus status
Status of the read operation.
int error_code
Error code from read() on failure, 0 on success.
std::tuple< stored_trigger_arg_t< Ts >... > args
Request(HttpRequestSendAction *action, const Ts &...x)
A request from an HttpRequestSendAction, run from HttpRequestComponent::loop().
std::shared_ptr< HttpContainer > container
PendingRequest(Handler handler, void *owner)
PendingRequest & operator=(const PendingRequest &)=delete
void(*)(PendingRequest *request, RequestEvent event) Handler
PendingRequest(const PendingRequest &)=delete
One request header of an action; codegen puts these in a shared flash table.
uint16_t x
Definition tt21100.cpp:5