#include "http_request_idf.h" #ifdef USE_ESP_IDF #include "esphome/components/network/util.h" #include "esphome/components/watchdog/watchdog.h" #include "esphome/core/application.h" #include "esphome/core/log.h" #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE #include "esp_crt_bundle.h" #endif #include "esp_task_wdt.h" namespace esphome { namespace http_request { static const char *const TAG = "http_request.idf"; struct UserData { const std::set &collect_headers; std::map> response_headers; }; void HttpRequestIDF::dump_config() { HttpRequestComponent::dump_config(); ESP_LOGCONFIG(TAG, " Buffer Size RX: %u\n" " Buffer Size TX: %u", this->buffer_size_rx_, this->buffer_size_tx_); } esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) { UserData *user_data = (UserData *) evt->user_data; switch (evt->event_id) { case HTTP_EVENT_ON_HEADER: { const std::string header_name = str_lower_case(evt->header_key); if (user_data->collect_headers.count(header_name)) { const std::string header_value = evt->header_value; ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str()); user_data->response_headers[header_name].push_back(header_value); break; } break; } default: { break; } } return ESP_OK; } std::shared_ptr HttpRequestIDF::perform(std::string url, std::string method, std::string body, std::list
request_headers, std::set collect_headers) { if (!network::is_connected()) { this->status_momentary_error("failed", 1000); ESP_LOGE(TAG, "HTTP Request failed; Not connected to network"); return nullptr; } esp_http_client_method_t method_idf; if (method == "GET") { method_idf = HTTP_METHOD_GET; } else if (method == "POST") { method_idf = HTTP_METHOD_POST; } else if (method == "PUT") { method_idf = HTTP_METHOD_PUT; } else if (method == "DELETE") { method_idf = HTTP_METHOD_DELETE; } else if (method == "PATCH") { method_idf = HTTP_METHOD_PATCH; } else { this->status_momentary_error("failed", 1000); ESP_LOGE(TAG, "HTTP Request failed; Unsupported method"); return nullptr; } bool secure = url.find("https:") != std::string::npos; esp_http_client_config_t config = {}; config.url = url.c_str(); config.method = method_idf; config.timeout_ms = this->timeout_; config.disable_auto_redirect = !this->follow_redirects_; config.max_redirection_count = this->redirect_limit_; config.auth_type = HTTP_AUTH_TYPE_BASIC; #if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE if (secure) { config.crt_bundle_attach = esp_crt_bundle_attach; } #endif if (this->useragent_ != nullptr) { config.user_agent = this->useragent_; } config.buffer_size = this->buffer_size_rx_; config.buffer_size_tx = this->buffer_size_tx_; const uint32_t start = millis(); watchdog::WatchdogManager wdm(this->get_watchdog_timeout()); config.event_handler = http_event_handler; auto user_data = UserData{collect_headers, {}}; config.user_data = static_cast(&user_data); esp_http_client_handle_t client = esp_http_client_init(&config); std::shared_ptr container = std::make_shared(client); container->set_parent(this); container->set_secure(secure); for (const auto &header : request_headers) { esp_http_client_set_header(client, header.name.c_str(), header.value.c_str()); } const int body_len = body.length(); esp_err_t err = esp_http_client_open(client, body_len); if (err != ESP_OK) { this->status_momentary_error("failed", 1000); ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err)); esp_http_client_cleanup(client); return nullptr; } if (body_len > 0) { int write_left = body_len; int write_index = 0; const char *buf = body.c_str(); while (write_left > 0) { int written = esp_http_client_write(client, buf + write_index, write_left); if (written < 0) { err = ESP_FAIL; break; } write_left -= written; write_index += written; } } if (err != ESP_OK) { this->status_momentary_error("failed", 1000); ESP_LOGE(TAG, "HTTP Request failed: %s", esp_err_to_name(err)); esp_http_client_cleanup(client); return nullptr; } container->feed_wdt(); container->content_length = esp_http_client_fetch_headers(client); container->feed_wdt(); container->status_code = esp_http_client_get_status_code(client); container->feed_wdt(); container->set_response_headers(user_data.response_headers); if (is_success(container->status_code)) { container->duration_ms = millis() - start; return container; } if (this->follow_redirects_) { auto num_redirects = this->redirect_limit_; while (is_redirect(container->status_code) && num_redirects > 0) { err = esp_http_client_set_redirection(client); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_http_client_set_redirection failed: %s", esp_err_to_name(err)); this->status_momentary_error("failed", 1000); esp_http_client_cleanup(client); return nullptr; } #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE char redirect_url[256]{}; if (esp_http_client_get_url(client, redirect_url, sizeof(redirect_url) - 1) == ESP_OK) { ESP_LOGV(TAG, "redirecting to url: %s", redirect_url); } #endif err = esp_http_client_open(client, 0); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_http_client_open failed: %s", esp_err_to_name(err)); this->status_momentary_error("failed", 1000); esp_http_client_cleanup(client); return nullptr; } container->feed_wdt(); container->content_length = esp_http_client_fetch_headers(client); container->feed_wdt(); container->status_code = esp_http_client_get_status_code(client); container->feed_wdt(); if (is_success(container->status_code)) { container->duration_ms = millis() - start; return container; } num_redirects--; } if (num_redirects == 0) { ESP_LOGW(TAG, "Reach redirect limit count=%d", this->redirect_limit_); } } ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code); this->status_momentary_error("failed", 1000); return container; } int HttpContainerIDF::read(uint8_t *buf, size_t max_len) { const uint32_t start = millis(); watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout()); int bufsize = std::min(max_len, this->content_length - this->bytes_read_); if (bufsize == 0) { this->duration_ms += (millis() - start); return 0; } this->feed_wdt(); int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize); this->feed_wdt(); this->bytes_read_ += read_len; this->duration_ms += (millis() - start); return read_len; } void HttpContainerIDF::end() { watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout()); esp_http_client_close(this->client_); esp_http_client_cleanup(this->client_); } void HttpContainerIDF::feed_wdt() { // Tests to see if the executing task has a watchdog timer attached if (esp_task_wdt_status(nullptr) == ESP_OK) { App.feed_wdt(); } } } // namespace http_request } // namespace esphome #endif // USE_ESP_IDF