fix(esp_http_client): implement chunked transfer encoding in before tranport write

Closes https://github.com/espressif/esp-idf/issues/17685
This commit is contained in:
nilesh.kale
2025-12-18 11:14:18 +05:30
parent 80116d4e07
commit c3a413a8a5
6 changed files with 288 additions and 8 deletions

View File

@@ -815,6 +815,192 @@ static void http_native_request(void)
esp_http_client_cleanup(client);
}
/*
* http_chunked_request() tests chunked transfer encoding support.
*
* This test verifies that esp_http_client_chunk_write_begin/write/chunk_write_end correctly
* format data according to RFC 7230 chunked transfer encoding. It sends multiple chunks
* to demonstrate the chunked API usage.
*
* Test steps:
* 1. Set up POST request with chunked encoding (write_len = -1)
* 2. Write multiple chunks using chunk_write_begin() / write() / chunk_write_end() per chunk
* 3. Pass last_chunk=true in chunk_write_end() on the final chunk to send the terminator
* 4. Verify server accepts the request (Status 200)
*/
static void http_chunked_request(void)
{
char output_buffer[MAX_HTTP_OUTPUT_BUFFER + 1] = {0};
esp_http_client_config_t config = {
.url = "http://"CONFIG_EXAMPLE_HTTP_ENDPOINT"/post",
.event_handler = _http_event_handler,
.user_data = output_buffer,
.timeout_ms = 10000,
};
ESP_LOGI(TAG, "HTTP chunked request test =>");
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_http_client_set_method(client, HTTP_METHOD_POST);
esp_http_client_set_header(client, "Content-Type", "application/json");
// Open with write_len = -1 to enable chunked encoding (sets Transfer-Encoding: chunked and removes Content-Length automatically)
esp_err_t err = esp_http_client_open(client, -1);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
esp_http_client_cleanup(client);
return;
}
// Send multiple chunks to demonstrate chunked encoding
const char *chunk1 = "{\"message\":\"Hello";
const char *chunk2 = "\", \"chunks\":";
const char *chunk3 = "3}";
const char *chunks[] = { chunk1, chunk2, chunk3 };
const int num_chunks = sizeof(chunks) / sizeof(chunks[0]);
for (int i = 0; i < num_chunks; i++) {
int len = (int)strlen(chunks[i]);
if (esp_http_client_chunk_write_begin(client, len) != 0) {
ESP_LOGE(TAG, "Chunk write begin failed for chunk %d", i + 1);
esp_http_client_close(client);
esp_http_client_cleanup(client);
return;
}
int wlen = esp_http_client_write(client, chunks[i], len);
if (wlen != len) {
ESP_LOGE(TAG, "Write failed for chunk %d (got %d, expected %d)", i + 1, wlen, len);
esp_http_client_close(client);
esp_http_client_cleanup(client);
return;
}
bool is_last = (i == num_chunks - 1);
if (esp_http_client_chunk_write_end(client, is_last) != 0) {
ESP_LOGE(TAG, "Chunk write end failed for chunk %d", i + 1);
esp_http_client_close(client);
esp_http_client_cleanup(client);
return;
}
}
// Fetch headers and read response
int64_t content_length = esp_http_client_fetch_headers(client);
if (content_length < 0) {
ESP_LOGE(TAG, "HTTP client fetch headers failed");
esp_http_client_close(client);
esp_http_client_cleanup(client);
return;
}
int status_code = esp_http_client_get_status_code(client);
ESP_LOGI(TAG, "HTTP POST Status = %d, content_length = %"PRId64, status_code, content_length);
if (status_code == 200) {
ESP_LOGI(TAG, "Chunked encoding test passed - server accepted the request");
// Read response only on success
int data_read = esp_http_client_read_response(client, output_buffer, MAX_HTTP_OUTPUT_BUFFER);
if (data_read >= 0) {
ESP_LOGD(TAG, "Response received: %.*s", data_read, output_buffer);
} else {
ESP_LOGE(TAG, "Failed to read response");
}
} else if (status_code == 400) {
ESP_LOGE(TAG, "Chunked encoding test failed - server rejected malformed request");
} else {
ESP_LOGW(TAG, "Chunked encoding test returned unexpected status code: %d", status_code);
}
esp_http_client_close(client);
esp_http_client_cleanup(client);
}
/*
* http_chunked_request_async() chunked transfer encoding in async (non-blocking) mode.
* Same flow as http_chunked_request() but with is_async = true. Retry esp_http_client_write()
* with remaining data until the full body is written.
*/
static void http_chunked_request_async(void)
{
char output_buffer[MAX_HTTP_OUTPUT_BUFFER + 1] = {0};
esp_http_client_config_t config = {
.url = "http://"CONFIG_EXAMPLE_HTTP_ENDPOINT"/post",
.event_handler = _http_event_handler,
.user_data = output_buffer,
.is_async = true,
.timeout_ms = 10000,
};
ESP_LOGI(TAG, "HTTP chunked request (async mode) test =>");
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_http_client_set_method(client, HTTP_METHOD_POST);
esp_http_client_set_header(client, "Content-Type", "application/json");
if (esp_http_client_open(client, -1) != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection");
esp_http_client_cleanup(client);
return;
}
const char *body = "{\"message\":\"Hello, async chunked encoding!\"}";
int body_len = (int)strlen(body);
int wlen;
// Send chunk header
if (esp_http_client_chunk_write_begin(client, body_len) != 0) {
ESP_LOGE(TAG, "Chunk write begin failed");
esp_http_client_close(client);
esp_http_client_cleanup(client);
return;
}
// Send chunk body with async retry
int written = 0;
while (written < body_len) {
wlen = esp_http_client_write(client, body + written, body_len - written);
if (wlen < 0) {
ESP_LOGE(TAG, "Write failed: %d", wlen);
esp_http_client_close(client);
esp_http_client_cleanup(client);
return;
}
written += wlen;
if (written < body_len) {
vTaskDelay(pdMS_TO_TICKS(10));
}
}
// Send chunk trailer + final terminator
if (esp_http_client_chunk_write_end(client, true) != 0) {
ESP_LOGE(TAG, "Chunk write end failed");
esp_http_client_close(client);
esp_http_client_cleanup(client);
return;
}
int64_t content_length = esp_http_client_fetch_headers(client);
if (content_length < 0) {
ESP_LOGE(TAG, "HTTP client fetch headers failed");
esp_http_client_close(client);
esp_http_client_cleanup(client);
return;
}
int status_code = esp_http_client_get_status_code(client);
ESP_LOGI(TAG, "HTTP POST Status = %d, content_length = %"PRId64, status_code, content_length);
if (status_code == 200) {
ESP_LOGI(TAG, "Async chunked encoding test passed");
int n = esp_http_client_read_response(client, output_buffer, MAX_HTTP_OUTPUT_BUFFER);
if (n >= 0) {
ESP_LOGD(TAG, "Response: %.*s", n, output_buffer);
}
} else {
ESP_LOGW(TAG, "Async chunked test status: %d", status_code);
}
esp_http_client_close(client);
esp_http_client_cleanup(client);
}
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
static void http_partial_download(void)
{
@@ -891,6 +1077,8 @@ static void http_test_task(void *pvParameters)
#endif
https_with_invalid_url();
http_native_request();
http_chunked_request();
http_chunked_request_async();
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
http_partial_download();
#endif