ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
http_request.cpp
Go to the documentation of this file.
1#include "http_request.h"
2
4#include "esphome/core/hal.h"
5#include "esphome/core/log.h"
6
7#include <cinttypes>
8
10
11ESPHOME_LOG_TAG(TAG, "http_request");
12
14 ESP_LOGCONFIG(TAG,
15 "HTTP Request:\n"
16 " Timeout: %" PRIu32 "ms\n"
17 " User-Agent: %s\n"
18 " Follow redirects: %s\n"
19 " Redirect limit: %d\n"
20 " Max concurrent requests: %u",
21 this->timeout_, this->useragent_, YESNO(this->follow_redirects_), this->redirect_limit_,
22 this->max_concurrent_);
23 if (this->watchdog_timeout_ > 0) {
24 ESP_LOGCONFIG(TAG, " Watchdog Timeout: %" PRIu32 "ms", this->watchdog_timeout_);
25 }
26}
27
28std::string HttpContainer::get_response_header(const std::string &header_name) {
29 auto lower = str_lower_case(header_name); // NOLINT
30 for (const auto &entry : this->response_headers_) {
31 if (entry.name == lower) {
32 ESP_LOGV(TAG, "Header with name %s found with value %s", lower.c_str(), entry.value.c_str());
33 return entry.value;
34 }
35 }
36 return "";
37}
38
40 if (this->buf != nullptr)
42 this->buf = nullptr;
43}
44
46 // An on_boot automation may already have queued a request
47 if (this->requests_ == nullptr)
48 this->disable_loop();
49}
50
52 if (!this->process_())
53 this->disable_loop();
54}
55
57 // Called before any on_shutdown(). Drop the requests already made, so that they do not hold up the reboot;
58 // only the ones that on_shutdown automations make are sent.
59 this->shutting_down_ = true;
60 this->busy_++;
61 unsigned dropped = 0;
62 for (PendingRequest *request = this->requests_; request != nullptr; request = request->next) {
63 if (request->finished || request->cancelled)
64 continue;
65 dropped++;
66 request->cancelled = true;
67 // As script.stop would, so that a script that was waiting for it can run again from on_shutdown
68 request->handler(request, RequestEvent::REQUEST_EVENT_ABANDON);
69 }
70 this->busy_--;
71 if (dropped != 0) {
72 ESP_LOGW(TAG, "Shutting down; %u requests dropped", dropped);
73 }
74 if (this->busy_ == 0)
75 this->remove_dead_();
76}
77
79 // Shutdown runs no loop(), so finish the requests here, blocking as they did before. A forced reboot calls
80 // only this, so it also finishes the requests made before it.
81 this->shutting_down_ = true;
82 this->drain_();
83}
84
86 this->busy_++;
87 uint8_t running = 0;
88 uint8_t waiting = 0;
89 PendingRequest **link = &this->requests_;
90 for (; *link != nullptr; link = &(*link)->next) {
91 const PendingRequest *other = *link;
92 if (other->finished || other->cancelled)
93 continue;
94 if (other->container == nullptr) {
95 waiting++;
96 } else {
97 running++;
98 }
99 }
100 *link = request;
101 this->enable_loop();
102 if (waiting >= MAX_QUEUED) {
103 ESP_LOGW(TAG, "Too many requests waiting; request dropped");
104 request->failed = true;
105 this->finish_(request);
106 } else if (waiting == 0 && running < this->max_concurrent_) {
107 // Start at once; one that cannot start is reported at once
108 this->begin_request_(request);
109 if (request->failed)
110 this->finish_(request);
111 }
112 this->busy_--;
113 if (this->busy_ != 0)
114 return;
115 if (this->shutting_down_) {
116 this->drain_();
117 } else {
118 this->remove_dead_();
119 }
120}
121
123 for (PendingRequest *request = this->requests_; request != nullptr; request = request->next) {
124 if (request->owner == owner)
125 request->cancelled = true;
126 }
127 if (this->busy_ == 0)
128 this->remove_dead_();
129}
130
132 this->busy_++;
133 // The first max_concurrent_ live requests run; a request finished here frees its slot on the next pass
134 uint8_t slots = this->max_concurrent_;
135 for (PendingRequest *request = this->requests_; request != nullptr && slots != 0; request = request->next) {
136 if (request->finished || request->cancelled)
137 continue;
138 slots--;
139 if (request->container == nullptr)
140 this->begin_request_(request);
141 if (!request->cancelled && (request->failed || this->advance_(request)))
142 this->finish_(request);
143 }
144 this->busy_--;
145 if (this->busy_ == 0)
146 this->remove_dead_();
147 return this->requests_ != nullptr;
148}
149
151 // A call further up holds request pointers, and runs the new requests itself
152 if (this->busy_ != 0)
153 return;
154 while (this->process_()) {
155 App.feed_wdt();
156 delay(1);
157 }
158}
159
161 request->start_time = millis();
163 // Stopped from the request's own lambdas: it is removed without being polled
164 if (!request->cancelled && request->container == nullptr)
165 request->failed = true;
166}
167
169 if (!request->reading) {
170 auto progress = request->container->poll();
172 return false;
174 request->failed = true;
175 return true;
176 }
177 if (!request->capture)
178 return true;
179 request->buf = RAMAllocator<uint8_t>().allocate(request->buf_size);
180 if (request->buf == nullptr) {
181 // As before, on_response still fires, with an empty body
182 ESP_LOGW(TAG, "Could not allocate %zu bytes for the response body", request->buf_size);
183 return true;
184 }
185 request->reading = true;
186 // millis(), not the loop time: poll() may have blocked earlier in this pass
187 request->last_data_time = millis();
188 }
189 // NOTE: HttpContainer::read() has non-BSD socket semantics - see http_request.h
190 // millis(), not the loop time: the budget is shorter than one pass of the loop
191 const uint32_t start = millis();
192 while (request->read_index < request->buf_size) {
193 int read_or_error =
194 request->container->read_available(request->buf + request->read_index, request->buf_size - request->read_index);
195 const uint32_t now = millis();
196 // As before, a body that is cut off still fires on_response with what was read
197 if (read_or_error < 0) {
198 ESP_LOGW(TAG, "Response body read failed after %zu bytes", request->read_index);
199 return true;
200 }
201 if (read_or_error == 0) {
202 if (request->container->is_read_complete())
203 return true;
204 if (now - request->last_data_time < request->timeout)
205 return false;
206 ESP_LOGW(TAG, "Response body timed out after %zu bytes", request->read_index);
207 return true;
208 }
209 request->read_index += read_or_error;
210 request->last_data_time = now;
211 if (now - start >= READ_BUDGET_MS)
212 return false;
213 }
214 // As before, on_response still fires, with the part that fitted
215 if (!request->container->is_read_complete()) {
216 ESP_LOGW(TAG, "Response body is larger than max_response_buffer_size; kept the first %zu bytes", request->buf_size);
217 }
218 return true;
219}
220
222 request->finished = true;
223 // The body was read over several passes, so time it from the start, as a blocking read would
224 if (request->reading && request->container != nullptr)
225 request->container->duration_ms = millis() - request->start_time;
227}
228
230 PendingRequest **link = &this->requests_;
231 while (PendingRequest *request = *link) {
232 if (!request->finished && !request->cancelled) {
233 link = &request->next;
234 continue;
235 }
236 *link = request->next;
237 if (request->container != nullptr)
238 request->container->end();
239 request->free_buffer();
240 request->handler(request, RequestEvent::REQUEST_EVENT_DESTROY);
241 }
242}
243
244} // namespace esphome::http_request
void feed_wdt()
Feed the task watchdog.
void enable_loop()
Enable this component's loop.
Definition component.h:248
void disable_loop()
Disable this component's loop.
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:2298
void deallocate(T *p, size_t n)
Definition helpers.h:2355
T * allocate(size_t n)
Definition helpers.h:2325
std::string get_response_header(const std::string &header_name)
Get the value of a response header by name.
std::vector< Header > response_headers_
bool advance_(PendingRequest *request)
void cancel(void *owner)
For HttpRequestSendAction: drop the requests of a stopped action without firing their triggers.
static constexpr uint32_t READ_BUDGET_MS
Longest time one loop() pass spends reading a response body.
void finish_(PendingRequest *request)
void begin_request_(PendingRequest *request)
void submit(PendingRequest *request)
For HttpRequestSendAction: queue a request and take ownership of it.
std::shared_ptr< HttpContainer > start(const char *url, const char *method, const std::string &body, const std::vector< Header > &request_headers)
static constexpr uint8_t MAX_QUEUED
Requests that wait for a free slot; more are dropped with on_error.
@ HTTP_REQUEST_PROGRESS_FAILED
No response; the container must still be ended.
@ HTTP_REQUEST_PROGRESS_PENDING
Still connecting, sending, or waiting for the response headers.
@ 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.
ESPHOME_LOG_TAG(TAG, "http_request")
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.
static void uint32_t
A request from an HttpRequestSendAction, run from HttpRequestComponent::loop().
std::shared_ptr< HttpContainer > container