diff --git a/components/esp_http_client/esp_http_client.c b/components/esp_http_client/esp_http_client.c index 4bf003813d..c59bd8a159 100644 --- a/components/esp_http_client/esp_http_client.c +++ b/components/esp_http_client/esp_http_client.c @@ -67,6 +67,7 @@ typedef struct { int64_t data_process; /*!< data processed */ int method; /*!< http method */ bool is_chunked; + int64_t content_range; /*!< content range */ } esp_http_data_t; typedef struct { @@ -269,7 +270,29 @@ static int http_on_header_value(http_parser *parser, const char *at, size_t leng if (client->current_header_key == NULL) { return 0; } - if (strcasecmp(client->current_header_key, "Location") == 0) { + if (strcasecmp(client->current_header_key, "Content-Range") == 0) { + HTTP_RET_ON_FALSE_DBG(http_utils_append_string(&client->current_header_value, at, length), -1, TAG, "Failed to append string"); + + int64_t total_size = -1; + client->response->content_range = -1; + char *slash_pos = strchr(client->current_header_value, '/'); + + if (slash_pos) { + if (slash_pos[1] == '*') { + ESP_LOGE(TAG, "Content-Range header has unknown total size (bytes A-B/*)"); + } else { + char *endptr; + total_size = strtol(slash_pos + 1, &endptr, 10); + if (total_size > 0 && *endptr == '\0') { + client->response->content_range = total_size; + } else { + ESP_LOGE(TAG, "Failed to extract total size from Content-Range"); + } + } + } else { + ESP_LOGE(TAG, "Invalid Content-Range format (missing '/')"); + } + } else if (strcasecmp(client->current_header_key, "Location") == 0) { HTTP_RET_ON_FALSE_DBG(http_utils_append_string(&client->location, at, length), -1, TAG, "Failed to append string"); } else if (strcasecmp(client->current_header_key, "Transfer-Encoding") == 0 && memcmp(at, "chunked", length) == 0) { @@ -1817,6 +1840,11 @@ int64_t esp_http_client_get_content_length(esp_http_client_handle_t client) return client->response->content_length; } +int64_t esp_http_client_get_content_range(esp_http_client_handle_t client) +{ + return client->response->content_range; +} + bool esp_http_client_is_chunked_response(esp_http_client_handle_t client) { return client->response->is_chunked; diff --git a/components/esp_http_client/include/esp_http_client.h b/components/esp_http_client/include/esp_http_client.h index 35ca5a877a..1dd420d5d3 100644 --- a/components/esp_http_client/include/esp_http_client.h +++ b/components/esp_http_client/include/esp_http_client.h @@ -232,6 +232,7 @@ typedef struct { typedef enum { /* 2xx - Success */ HttpStatus_Ok = 200, + HttpStatus_PartialContent = 206, /* 3xx - Redirection */ HttpStatus_MultipleChoices = 300, @@ -642,6 +643,19 @@ int esp_http_client_get_status_code(esp_http_client_handle_t client); */ int64_t esp_http_client_get_content_length(esp_http_client_handle_t client); +/** + * @brief Get http response content range (from header Content-Range) + * The returned value is valid only if this function is invoked after + * a successful call to `esp_http_client_perform`. + * Content-Range is set to -1 if parsing fails or if the Content-Range header is not present. + * + * @param[in] client The esp_http_client handle + * + * @return + * - Content-Range value as bytes + */ +int64_t esp_http_client_get_content_range(esp_http_client_handle_t client); + /** * @brief Close http connection, still kept all http request resources * diff --git a/components/esp_https_ota/src/esp_https_ota.c b/components/esp_https_ota/src/esp_https_ota.c index 7dd2ee40a5..89390591a9 100644 --- a/components/esp_https_ota/src/esp_https_ota.c +++ b/components/esp_https_ota/src/esp_https_ota.c @@ -365,20 +365,45 @@ esp_err_t esp_https_ota_begin(const esp_https_ota_config_t *ota_config, esp_http if (https_ota_handle->partial_http_download) { esp_http_client_set_method(https_ota_handle->http_client, HTTP_METHOD_HEAD); + err = esp_http_client_perform(https_ota_handle->http_client); if (err == ESP_OK) { int status = esp_http_client_get_status_code(https_ota_handle->http_client); if (status != HttpStatus_Ok) { - ESP_LOGE(TAG, "Received incorrect http status %d", status); - err = ESP_FAIL; - goto http_cleanup; + // If server doesn't support HEAD request, we need to get image length from GET request + // using Range header + esp_http_client_set_header(https_ota_handle->http_client, "Range", "bytes=0-0"); + esp_http_client_set_method(https_ota_handle->http_client, HTTP_METHOD_GET); + + err = esp_http_client_perform(https_ota_handle->http_client); + if (err == ESP_OK) { + status = esp_http_client_get_status_code(https_ota_handle->http_client); + if (status != HttpStatus_Ok && status != HttpStatus_PartialContent) { + ESP_LOGE(TAG, "Received incorrect http status %d", status); + err = ESP_FAIL; + goto http_cleanup; + } + } else { + ESP_LOGE(TAG, "ESP HTTP client perform failed: %d", err); + goto http_cleanup; + } + esp_http_client_set_header(https_ota_handle->http_client, "Range", NULL); + + if (status == HttpStatus_Ok) { + // If server responds with 200 OK, we can get image length from content-length header + https_ota_handle->image_length = esp_http_client_get_content_length(https_ota_handle->http_client); + } else { + // If server responds with 206 Partial Content, we can get image length from content-range header + https_ota_handle->image_length = esp_http_client_get_content_range(https_ota_handle->http_client); + } + } else { + https_ota_handle->image_length = esp_http_client_get_content_length(https_ota_handle->http_client); } } else { ESP_LOGE(TAG, "ESP HTTP client perform failed: %d", err); goto http_cleanup; } - https_ota_handle->image_length = esp_http_client_get_content_length(https_ota_handle->http_client); if (https_ota_handle->image_length == -1) { ESP_LOGE(TAG, "Failed to get image length from http response"); err = ESP_FAIL; diff --git a/docs/en/api-reference/system/esp_https_ota.rst b/docs/en/api-reference/system/esp_https_ota.rst index e3fcdd2025..d54f0300d2 100644 --- a/docs/en/api-reference/system/esp_https_ota.rst +++ b/docs/en/api-reference/system/esp_https_ota.rst @@ -47,6 +47,9 @@ This option is useful while fetching image from a service like AWS S3, where mbe Default value of mbedTLS Rx buffer size is set to 16 KB. By using ``partial_http_download`` with ``max_http_request_size`` of 4 KB, size of mbedTLS Rx buffer can be reduced to 4 KB. With this configuration, memory saving of around 12 KB is expected. +.. note:: + If the server uses chunked transfer encoding, partial downloads are not feasible because the total content length is not known in advance. + OTA Resumption -------------- diff --git a/docs/zh_CN/api-reference/system/esp_https_ota.rst b/docs/zh_CN/api-reference/system/esp_https_ota.rst index 669742c961..bcc64d4963 100644 --- a/docs/zh_CN/api-reference/system/esp_https_ota.rst +++ b/docs/zh_CN/api-reference/system/esp_https_ota.rst @@ -47,6 +47,9 @@ ESP HTTPS OTA 升级 mbedTLS Rx buffer 的默认大小为 16 KB,但如果将 ``partial_http_download`` 的 ``max_http_request_size`` 设置为 4 KB,便能将 mbedTLS Rx 的 buffer 减小到 4 KB。使用这一配置方式预计可以节省约 12 KB 内存。 +.. note:: + 如果服务器使用分块传输编码,则无法进行部分下载,因为无法预先获知总内容长度。 + OTA 恢复 --------