Skip to content

Commit caf828d

Browse files
committed
fix(esp_http_client): Fix OTA failure with partial download enabled
This commit fixes an issue encountered during OTA when partial download is enabled using an AWS signed URL restricted to GET requests. It also adds an API to retrieve the OTA image size from the Content-Range header.
1 parent 3e46a73 commit caf828d

File tree

5 files changed

+78
-5
lines changed

5 files changed

+78
-5
lines changed

components/esp_http_client/esp_http_client.c

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ typedef struct {
6767
int64_t data_process; /*!< data processed */
6868
int method; /*!< http method */
6969
bool is_chunked;
70+
int64_t content_range; /*!< content range */
7071
} esp_http_data_t;
7172

7273
typedef struct {
@@ -269,7 +270,29 @@ static int http_on_header_value(http_parser *parser, const char *at, size_t leng
269270
if (client->current_header_key == NULL) {
270271
return 0;
271272
}
272-
if (strcasecmp(client->current_header_key, "Location") == 0) {
273+
if (strcasecmp(client->current_header_key, "Content-Range") == 0) {
274+
HTTP_RET_ON_FALSE_DBG(http_utils_append_string(&client->current_header_value, at, length), -1, TAG, "Failed to append string");
275+
276+
int64_t total_size = -1;
277+
client->response->content_range = -1;
278+
char *slash_pos = strchr(client->current_header_value, '/');
279+
280+
if (slash_pos) {
281+
if (slash_pos[1] == '*') {
282+
ESP_LOGE(TAG, "Content-Range header has unknown total size (bytes A-B/*)");
283+
} else {
284+
char *endptr;
285+
total_size = strtol(slash_pos + 1, &endptr, 10);
286+
if (total_size > 0 && *endptr == '\0') {
287+
client->response->content_range = total_size;
288+
} else {
289+
ESP_LOGE(TAG, "Failed to extract total size from Content-Range");
290+
}
291+
}
292+
} else {
293+
ESP_LOGE(TAG, "Invalid Content-Range format (missing '/')");
294+
}
295+
} else if (strcasecmp(client->current_header_key, "Location") == 0) {
273296
HTTP_RET_ON_FALSE_DBG(http_utils_append_string(&client->location, at, length), -1, TAG, "Failed to append string");
274297
} else if (strcasecmp(client->current_header_key, "Transfer-Encoding") == 0
275298
&& memcmp(at, "chunked", length) == 0) {
@@ -1817,6 +1840,11 @@ int64_t esp_http_client_get_content_length(esp_http_client_handle_t client)
18171840
return client->response->content_length;
18181841
}
18191842

1843+
int64_t esp_http_client_get_content_range(esp_http_client_handle_t client)
1844+
{
1845+
return client->response->content_range;
1846+
}
1847+
18201848
bool esp_http_client_is_chunked_response(esp_http_client_handle_t client)
18211849
{
18221850
return client->response->is_chunked;

components/esp_http_client/include/esp_http_client.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ typedef struct {
232232
typedef enum {
233233
/* 2xx - Success */
234234
HttpStatus_Ok = 200,
235+
HttpStatus_PartialContent = 206,
235236

236237
/* 3xx - Redirection */
237238
HttpStatus_MultipleChoices = 300,
@@ -642,6 +643,19 @@ int esp_http_client_get_status_code(esp_http_client_handle_t client);
642643
*/
643644
int64_t esp_http_client_get_content_length(esp_http_client_handle_t client);
644645

646+
/**
647+
* @brief Get http response content range (from header Content-Range)
648+
* The returned value is valid only if this function is invoked after
649+
* a successful call to `esp_http_client_perform`.
650+
* Content-Range is set to -1 if parsing fails or if the Content-Range header is not present.
651+
*
652+
* @param[in] client The esp_http_client handle
653+
*
654+
* @return
655+
* - Content-Range value as bytes
656+
*/
657+
int64_t esp_http_client_get_content_range(esp_http_client_handle_t client);
658+
645659
/**
646660
* @brief Close http connection, still kept all http request resources
647661
*

components/esp_https_ota/src/esp_https_ota.c

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -365,20 +365,45 @@ esp_err_t esp_https_ota_begin(const esp_https_ota_config_t *ota_config, esp_http
365365

366366
if (https_ota_handle->partial_http_download) {
367367
esp_http_client_set_method(https_ota_handle->http_client, HTTP_METHOD_HEAD);
368+
368369
err = esp_http_client_perform(https_ota_handle->http_client);
369370
if (err == ESP_OK) {
370371
int status = esp_http_client_get_status_code(https_ota_handle->http_client);
371372
if (status != HttpStatus_Ok) {
372-
ESP_LOGE(TAG, "Received incorrect http status %d", status);
373-
err = ESP_FAIL;
374-
goto http_cleanup;
373+
// If server doesn't support HEAD request, we need to get image length from GET request
374+
// using Range header
375+
esp_http_client_set_header(https_ota_handle->http_client, "Range", "bytes=0-0");
376+
esp_http_client_set_method(https_ota_handle->http_client, HTTP_METHOD_GET);
377+
378+
err = esp_http_client_perform(https_ota_handle->http_client);
379+
if (err == ESP_OK) {
380+
status = esp_http_client_get_status_code(https_ota_handle->http_client);
381+
if (status != HttpStatus_Ok && status != HttpStatus_PartialContent) {
382+
ESP_LOGE(TAG, "Received incorrect http status %d", status);
383+
err = ESP_FAIL;
384+
goto http_cleanup;
385+
}
386+
} else {
387+
ESP_LOGE(TAG, "ESP HTTP client perform failed: %d", err);
388+
goto http_cleanup;
389+
}
390+
esp_http_client_set_header(https_ota_handle->http_client, "Range", NULL);
391+
392+
if (status == HttpStatus_Ok) {
393+
// If server responds with 200 OK, we can get image length from content-length header
394+
https_ota_handle->image_length = esp_http_client_get_content_length(https_ota_handle->http_client);
395+
} else {
396+
// If server responds with 206 Partial Content, we can get image length from content-range header
397+
https_ota_handle->image_length = esp_http_client_get_content_range(https_ota_handle->http_client);
398+
}
399+
} else {
400+
https_ota_handle->image_length = esp_http_client_get_content_length(https_ota_handle->http_client);
375401
}
376402
} else {
377403
ESP_LOGE(TAG, "ESP HTTP client perform failed: %d", err);
378404
goto http_cleanup;
379405
}
380406

381-
https_ota_handle->image_length = esp_http_client_get_content_length(https_ota_handle->http_client);
382407
if (https_ota_handle->image_length == -1) {
383408
ESP_LOGE(TAG, "Failed to get image length from http response");
384409
err = ESP_FAIL;

docs/en/api-reference/system/esp_https_ota.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ This option is useful while fetching image from a service like AWS S3, where mbe
4747

4848
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.
4949

50+
.. note::
51+
If the server uses chunked transfer encoding, partial downloads are not feasible because the total content length is not known in advance.
52+
5053
OTA Resumption
5154
--------------
5255

docs/zh_CN/api-reference/system/esp_https_ota.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ ESP HTTPS OTA 升级
4747

4848
mbedTLS Rx buffer 的默认大小为 16 KB,但如果将 ``partial_http_download`` 的 ``max_http_request_size`` 设置为 4 KB,便能将 mbedTLS Rx 的 buffer 减小到 4 KB。使用这一配置方式预计可以节省约 12 KB 内存。
4949

50+
.. note::
51+
如果服务器使用分块传输编码,则无法进行部分下载,因为无法预先获知总内容长度。
52+
5053
OTA 恢复
5154
--------
5255

0 commit comments

Comments
 (0)