10#include <sys/select.h>
19#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
20#include "esp_crt_bundle.h"
23#include "esp_task_wdt.h"
30static constexpr size_t SEND_CHUNK_SIZE = 1024;
31static constexpr uint32_t SEND_BUDGET_MS = 10;
32static constexpr uint32_t ERROR_DURATION_MS = 1000;
37 " Buffer Size RX: %u\n"
38 " Buffer Size TX: %u\n"
39 " Custom CA Certificate: %s",
46 switch (evt->event_id) {
47 case HTTP_EVENT_ON_HEADER: {
50 const std::string header_value = evt->header_value;
51 ESP_LOGD(TAG,
"Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
52 container->response_headers_.push_back({header_name, header_value});
64 const char *url,
const char *method,
const std::vector<Header> &request_headers,
65 const std::vector<std::string> &lower_case_collect_headers,
uint32_t timeout_ms, [[maybe_unused]]
bool async) {
68 ESP_LOGE(TAG,
"HTTP Request failed; Not connected to network");
72 esp_http_client_method_t method_idf;
73 if (strcmp(method,
"GET") == 0) {
74 method_idf = HTTP_METHOD_GET;
75 }
else if (strcmp(method,
"POST") == 0) {
76 method_idf = HTTP_METHOD_POST;
77 }
else if (strcmp(method,
"PUT") == 0) {
78 method_idf = HTTP_METHOD_PUT;
79 }
else if (strcmp(method,
"DELETE") == 0) {
80 method_idf = HTTP_METHOD_DELETE;
81 }
else if (strcmp(method,
"PATCH") == 0) {
82 method_idf = HTTP_METHOD_PATCH;
85 ESP_LOGE(TAG,
"HTTP Request failed; Unsupported method");
90 bool secure = ESPHOME_strncasecmp_P(url, ESPHOME_PSTR(
"https:"), 6) == 0;
92 esp_http_client_config_t config = {};
95 config.method = method_idf;
96 config.timeout_ms = timeout_ms;
99 config.auth_type = HTTP_AUTH_TYPE_BASIC;
103#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
105 config.crt_bundle_attach = esp_crt_bundle_attach;
118#ifdef USE_HTTP_REQUEST_IDF_SOCKET_POLL
121 config.is_async = async && secure;
124 esp_http_client_handle_t client = esp_http_client_init(&config);
125 if (client ==
nullptr) {
127 ESP_LOGE(TAG,
"HTTP Request failed; client could not be initialized");
131 std::shared_ptr<HttpContainerIDF> container = std::make_shared<HttpContainerIDF>(client);
132 container->set_parent(
this);
134 container->timeout_ms_ = timeout_ms;
135 container->async_ = config.is_async;
137 container->set_secure(secure);
139 container->collect_headers_ = lower_case_collect_headers;
140 esp_http_client_set_user_data(client,
static_cast<void *
>(container.get()));
142 for (
const auto &header : request_headers) {
143 esp_http_client_set_header(client, header.name.c_str(), header.value.c_str());
150 const std::vector<Header> &request_headers,
151 const std::vector<std::string> &lower_case_collect_headers,
153 std::shared_ptr<HttpContainerIDF> container = this->
start_container_(url, method, std::string(body), request_headers,
154 lower_case_collect_headers, timeout_ms,
false);
155 if (container ==
nullptr) {
162 container->feed_wdt();
173 const std::vector<Header> &request_headers,
174 const std::vector<std::string> &lower_case_collect_headers,
176 return this->
start_container_(url, method, std::move(body), request_headers, lower_case_collect_headers, timeout_ms,
181 const char *url,
const char *method, std::string &&body,
const std::vector<Header> &request_headers,
182 const std::vector<std::string> &lower_case_collect_headers,
uint32_t timeout_ms,
bool async) {
183 std::shared_ptr<HttpContainerIDF> container =
184 this->
create_container_(url, method, request_headers, lower_case_collect_headers, timeout_ms, async);
185 if (container ==
nullptr) {
188 container->body_ = std::move(body);
189 container->start_time_ =
millis();
190 container->progress_time_ = container->start_time_;
199 ESP_LOGE(TAG,
"HTTP Request failed: %s", esp_err_to_name(err));
200 this->
parent_->status_momentary_error(ERROR_DURATION_MS);
208 return this->
fail_(ESP_ERR_TIMEOUT);
214#ifdef USE_HTTP_REQUEST_IDF_SOCKET_POLL
215 const int fd = esp_http_client_get_socket(this->
client_);
225 return ::select(fd + 1, write ?
nullptr : &fds, write ? &fds :
nullptr,
nullptr, &no_wait) != 0;
248 esp_err_t err = esp_http_client_open(this->
client_, this->
body_.size());
250 if (err == ESP_ERR_HTTP_EAGAIN) {
252 return this->
wait_();
256 return this->
fail_(err);
258#ifdef USE_HTTP_REQUEST_IDF_SOCKET_POLL
262 const int fd = esp_http_client_get_socket(this->
client_);
263 const int flags = fd < 0 ? -1 : fcntl(fd, F_GETFL, 0);
264 if (
flags < 0 || fcntl(fd, F_SETFL,
flags & ~O_NONBLOCK) < 0) {
265 ESP_LOGE(TAG,
"Could not make the socket blocking: fd %d, errno %d", fd, errno);
266 return this->
fail_(ESP_FAIL);
270 esp_http_client_set_timeout_ms(this->
client_, this->timeout_ms_);
282 return this->
wait_();
284 const size_t len = std::min(this->
body_.size() - this->body_sent_, SEND_CHUNK_SIZE);
288 return this->
fail_(ESP_FAIL);
291 return this->
wait_();
299 this->
body_ = std::string();
317 const int64_t
length = esp_http_client_fetch_headers(this->
client_);
331 esp_err_t err = esp_http_client_set_redirection(this->
client_);
333 ESP_LOGE(TAG,
"esp_http_client_set_redirection failed: %s", esp_err_to_name(err));
334 return this->
fail_(err);
336#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
337 char redirect_url[256]{};
338 if (esp_http_client_get_url(this->
client_, redirect_url,
sizeof(redirect_url) - 1) == ESP_OK) {
339 ESP_LOGV(TAG,
"redirecting to url: %s", redirect_url);
345 this->
body_ = std::string();
352 ESP_LOGW(TAG,
"Reach redirect limit count=%d", this->
idf_parent_()->redirect_limit_);
364 esp_http_client_get_url(this->
client_, url,
sizeof(url) - 1);
365 ESP_LOGE(TAG,
"HTTP Request failed; URL: %s; Code: %d", url, this->status_code);
366 this->
parent_->status_momentary_error(ERROR_DURATION_MS);
409 return this->
read_(buf, max_len);
415 esp_http_client_set_timeout_ms(this->
client_, 0);
416 int read_len_or_error = this->
read_(buf, max_len);
417 return read_len_or_error == -ESP_ERR_HTTP_EAGAIN ? 0 : read_len_or_error;
432 int read_len_or_error = esp_http_client_read(this->
client_, (
char *) buf, max_len);
437 if (read_len_or_error > 0) {
439 return read_len_or_error;
452 if (read_len_or_error == 0) {
457 return read_len_or_error;
461 if (this->
client_ ==
nullptr) {
466 esp_http_client_close(this->
client_);
467 esp_http_client_cleanup(this->
client_);
473 if (esp_task_wdt_status(
nullptr) == ESP_OK) {
void feed_wdt()
Feed the task watchdog.
void status_momentary_error(uint32_t length=5000)
Set error status flag and clear it after length ms.
HttpRequestComponent * parent_
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
uint32_t get_watchdog_timeout() const
void set_chunked(bool chunked)
bool is_chunked_
True if response uses chunked transfer encoding.
bool is_read_complete() const override
bool socket_ready_(bool write)
True when the socket can be read or written without waiting; always true when it cannot be checked.
HttpRequestProgress response_failed_(int status_code)
Finish with an unsuccessful status, which is reported through on_response like a success.
HttpRequestProgress poll() override
int read(uint8_t *buf, size_t max_len) override
int read_(uint8_t *buf, size_t max_len)
HttpRequestIDF * idf_parent_()
HttpRequestProgress fail_(esp_err_t err)
HttpRequestProgress wait_()
void feed_wdt()
Feeds the watchdog timer if the executing task has one attached.
int read_available(uint8_t *buf, size_t max_len) override
esp_http_client_handle_t client_
uint32_t get_watchdog_timeout() const
void dump_config() override
std::shared_ptr< HttpContainerIDF > start_container_(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, bool async)
A container that poll() takes from the connect through to the response headers.
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) override
const char * ca_certificate_
void dump_config() override
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) override
std::shared_ptr< HttpContainerIDF > create_container_(const char *url, const char *method, const std::vector< Header > &request_headers, const std::vector< std::string > &lower_case_collect_headers, uint32_t timeout_ms, bool async)
static esp_err_t http_event_handler(esp_http_client_event_t *evt)
Monitors the http client events to gather response headers.
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.
bool is_success(int const status)
Checks if the given HTTP status code indicates a successful request.
ESPHOME_LOG_TAG(TAG, "http_request")
bool is_redirect(int const status)
Returns true if the HTTP status code is a redirect.
ESPHOME_ALWAYS_INLINE bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
std::string str_lower_case(const std::string &str)
Convert the string to lower case.
void HOT delay(uint32_t ms)
uint32_t IRAM_ATTR HOT millis()
Application App
Global storage of Application pointer - only one Application can exist.