unify, make consistant

This commit is contained in:
J. Nick Koston
2026-01-21 12:30:42 -10:00
parent dffc9257dd
commit 6a8bae5b1c
3 changed files with 69 additions and 8 deletions
@@ -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<HttpRequestComponent> {
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;
@@ -139,6 +139,21 @@ std::shared_ptr<HttpContainer> 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();
@@ -210,6 +210,19 @@ std::shared_ptr<HttpContainer> 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