From 6a8bae5b1c76bbb9f3ba35a88522c3e4bd730b27 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 21 Jan 2026 12:30:42 -1000 Subject: [PATCH] unify, make consistant --- .../components/http_request/http_request.h | 24 ++++++++++++++++ .../http_request/http_request_arduino.cpp | 25 ++++++++++++++++- .../http_request/http_request_idf.cpp | 28 ++++++++++++++----- 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index ca7dcaa6b81..4d345412b53 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -79,6 +79,9 @@ inline bool is_redirect(int const status) { */ inline bool is_success(int const status) { return status >= HTTP_STATUS_OK && status < HTTP_STATUS_MULTIPLE_CHOICES; } +/// Error code returned by HttpContainer::read() when connection closed prematurely +static constexpr int HTTP_ERROR_CONNECTION_CLOSED = -1; + /// Status of a read operation enum class HttpReadStatus : uint8_t { OK, ///< Read completed successfully @@ -131,6 +134,27 @@ class HttpContainer : public Parented { int status_code; uint32_t duration_ms; + /** + * @brief Read data from the HTTP response body. + * + * This is a non-blocking read operation. The semantics are consistent across + * all platforms (Arduino and ESP-IDF): + * + * @param buf Buffer to read data into + * @param max_len Maximum number of bytes to read + * @return + * - > 0: Number of bytes read successfully + * - 0: No data available yet, caller should retry (data may still be arriving) + * - HTTP_ERROR_CONNECTION_CLOSED (-1): Connection closed prematurely + * - < -1: Other error (platform-specific error code) + * + * The caller should use get_bytes_read() and content_length to track progress. + * When get_bytes_read() >= content_length, all expected data has been received. + * + * For non-blocking read loops, use http_read_loop_result() helper which handles + * timeout tracking and converts return values to HttpReadLoopResult enum. + * For simple buffer reads, use http_read_fully() helper. + */ virtual int read(uint8_t *buf, size_t max_len) = 0; virtual void end() = 0; diff --git a/esphome/components/http_request/http_request_arduino.cpp b/esphome/components/http_request/http_request_arduino.cpp index a653942b186..d45d623b55f 100644 --- a/esphome/components/http_request/http_request_arduino.cpp +++ b/esphome/components/http_request/http_request_arduino.cpp @@ -139,6 +139,21 @@ std::shared_ptr HttpRequestArduino::perform(const std::string &ur return container; } +// Arduino HTTP read implementation +// +// Arduino's WiFiClient is inherently non-blocking - available() returns 0 when +// no data is ready. We use connected() to distinguish "no data yet" from +// "connection closed". +// +// WiFiClient behavior: +// available() > 0: data ready to read +// available() == 0 && connected(): no data yet, still connected +// available() == 0 && !connected(): connection closed +// +// We normalize these to the HttpContainer::read() contract: +// > 0: bytes read +// 0: no data yet, retry +// < 0: error (connection closed prematurely, or stream vanished) int HttpContainerArduino::read(uint8_t *buf, size_t max_len) { const uint32_t start = millis(); watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout()); @@ -154,7 +169,15 @@ int HttpContainerArduino::read(uint8_t *buf, size_t max_len) { if (bufsize == 0) { this->duration_ms += (millis() - start); - return 0; + // Check if we've read all expected content + if (this->bytes_read_ >= this->content_length) { + return 0; // All content read successfully + } + // No data available - check if connection is still open + if (!stream_ptr->connected()) { + return HTTP_ERROR_CONNECTION_CLOSED; // Connection closed prematurely + } + return 0; // No data yet, caller should retry } App.feed_wdt(); diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index b0a2d264d95..dfbb33c8a10 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -210,6 +210,19 @@ std::shared_ptr HttpRequestIDF::perform(const std::string &url, c return container; } +// ESP-IDF HTTP read implementation +// +// Uses non-blocking mode (config.is_async = true) for consistent behavior with Arduino. +// esp_http_client_read() in async mode returns: +// > 0: bytes read +// 0: connection closed (end of stream) +// -ESP_ERR_HTTP_EAGAIN (0x7007): no data available yet (would block) +// other negative: error +// +// We normalize these to the HttpContainer::read() contract: +// > 0: bytes read +// 0: no data yet, retry +// < 0: error (connection closed prematurely, or other error) int HttpContainerIDF::read(uint8_t *buf, size_t max_len) { const uint32_t start = millis(); watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout()); @@ -217,7 +230,7 @@ int HttpContainerIDF::read(uint8_t *buf, size_t max_len) { // Check if we've already read all expected content if (this->bytes_read_ >= this->content_length) { this->duration_ms += (millis() - start); - return 0; // All content read + return 0; // All content read successfully } this->feed_wdt(); @@ -231,16 +244,17 @@ int HttpContainerIDF::read(uint8_t *buf, size_t max_len) { return read_len_or_error; } - // read_len_or_error < 0: check for EAGAIN (no data available in non-blocking mode) - // ESP_ERR_HTTP_EAGAIN is returned as a negative error code + // No data available yet in non-blocking mode + // ESP_ERR_HTTP_EAGAIN (0x7007) is returned as negative if (read_len_or_error == -ESP_ERR_HTTP_EAGAIN) { - return 0; // No data available yet, caller should retry + return 0; // No data yet, caller should retry } + // Connection closed by server if (read_len_or_error == 0) { - // Connection closed, but we haven't read all content yet (early check handles success case) - // This is a premature close - return error - return -1; + // We haven't read all content yet (early check handles success case) + // Return error so caller exits immediately instead of waiting for timeout + return HTTP_ERROR_CONNECTION_CLOSED; } // Other negative value - real error, return the actual error code for debugging