mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
feat(esp_http_client): adds support to save response headers
Closes https://github.com/espressif/esp-idf/issues/17695
This commit is contained in:
@@ -41,4 +41,29 @@ menu "ESP HTTP client"
|
||||
help
|
||||
This config option helps in setting the time in millisecond to wait for event to be posted to the
|
||||
system default event loop. Set it to -1 if you need to set timeout to portMAX_DELAY.
|
||||
|
||||
config ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
bool "Save response headers"
|
||||
default n
|
||||
help
|
||||
This option will enable saving of response headers in esp_http_client component.
|
||||
When enabled, upto ESP_HTTP_CLIENT_MAX_SAVED_RESPONSE_HEADERS headers are saved.
|
||||
Enabling this option will increase the memory footprint of the component.
|
||||
|
||||
config ESP_HTTP_CLIENT_MAX_SAVED_RESPONSE_HEADERS
|
||||
depends on ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
int "Maximum number of response headers"
|
||||
default 10
|
||||
help
|
||||
This config option helps in setting the maximum number of response headers that
|
||||
can be saved in esp_http_client component.
|
||||
|
||||
config ESP_HTTP_CLIENT_MAX_RESPONSE_HEADER_SIZE
|
||||
depends on ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
int "Maximum size of response header"
|
||||
default 128
|
||||
help
|
||||
This config option helps in setting the maximum size of response header that
|
||||
can be saved in esp_http_client component.
|
||||
Note that the same size is used for key and value in the response headers.
|
||||
endmenu
|
||||
|
||||
@@ -60,6 +60,9 @@ typedef struct {
|
||||
*/
|
||||
typedef struct {
|
||||
http_header_handle_t headers; /*!< http header */
|
||||
#if CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
int saved_response_header_count;
|
||||
#endif // CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
esp_http_buffer_t *buffer; /*!< data buffer as linked list */
|
||||
int status_code; /*!< status code (integer) */
|
||||
int64_t content_length; /*!< data length */
|
||||
@@ -249,6 +252,23 @@ static int http_on_header_event(esp_http_client_handle_t client)
|
||||
client->event.header_value = client->current_header_value;
|
||||
http_dispatch_event(client, HTTP_EVENT_ON_HEADER, NULL, 0);
|
||||
http_dispatch_event_to_event_loop(HTTP_EVENT_ON_HEADER, &client, sizeof(esp_http_client_handle_t));
|
||||
|
||||
#if CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
if (client->response->saved_response_header_count >= CONFIG_ESP_HTTP_CLIENT_MAX_SAVED_RESPONSE_HEADERS) {
|
||||
ESP_LOGW(TAG, "Response header limit (%d) exceeded", CONFIG_ESP_HTTP_CLIENT_MAX_SAVED_RESPONSE_HEADERS);
|
||||
} else {
|
||||
if (strlen(client->current_header_key) > CONFIG_ESP_HTTP_CLIENT_MAX_RESPONSE_HEADER_SIZE ||
|
||||
strlen(client->current_header_value) > CONFIG_ESP_HTTP_CLIENT_MAX_RESPONSE_HEADER_SIZE) {
|
||||
ESP_LOGW(TAG, "Header '%s' exceeds max size (%d): key=%zu, value=%zu",
|
||||
client->current_header_key, CONFIG_ESP_HTTP_CLIENT_MAX_RESPONSE_HEADER_SIZE,
|
||||
strlen(client->current_header_key), strlen(client->current_header_value));
|
||||
} else {
|
||||
http_header_set(client->response->headers, client->current_header_key, client->current_header_value);
|
||||
client->response->saved_response_header_count++;
|
||||
}
|
||||
}
|
||||
#endif // CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
|
||||
free(client->current_header_key);
|
||||
free(client->current_header_value);
|
||||
client->current_header_key = NULL;
|
||||
@@ -409,6 +429,17 @@ esp_err_t esp_http_client_get_header(esp_http_client_handle_t client, const char
|
||||
return http_header_get(client->request->headers, key, value);
|
||||
}
|
||||
|
||||
#if CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
esp_err_t esp_http_client_get_response_header(esp_http_client_handle_t client, const char *key, char **value)
|
||||
{
|
||||
if (client == NULL || client->response == NULL || client->response->headers == NULL || key == NULL || value == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
return http_header_get(client->response->headers, key, value);
|
||||
}
|
||||
#endif // CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
|
||||
esp_err_t esp_http_client_delete_header(esp_http_client_handle_t client, const char *key)
|
||||
{
|
||||
if (client == NULL || client->request == NULL || client->request->headers == NULL || key == NULL) {
|
||||
@@ -725,6 +756,12 @@ esp_err_t esp_http_client_prepare(esp_http_client_handle_t client)
|
||||
free(client->auth_header);
|
||||
client->auth_header = NULL;
|
||||
}
|
||||
#if CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
if (client->response->headers != NULL) {
|
||||
http_header_clean(client->response->headers);
|
||||
}
|
||||
client->response->saved_response_header_count = 0;
|
||||
#endif // CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
http_parser_init(client->parser, HTTP_RESPONSE);
|
||||
if (client->connection_info.username) {
|
||||
if (client->connection_info.auth_type == HTTP_AUTH_TYPE_BASIC) {
|
||||
@@ -817,7 +854,9 @@ esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *co
|
||||
(client->request->headers = http_header_init()) &&
|
||||
(client->request->buffer = calloc(1, sizeof(esp_http_buffer_t))) &&
|
||||
(client->response = calloc(1, sizeof(esp_http_data_t))) &&
|
||||
#if CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
(client->response->headers = http_header_init()) &&
|
||||
#endif // CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
(client->response->buffer = calloc(1, sizeof(esp_http_buffer_t)))
|
||||
);
|
||||
|
||||
@@ -1068,7 +1107,9 @@ esp_err_t esp_http_client_cleanup(esp_http_client_handle_t client)
|
||||
free(client->request);
|
||||
}
|
||||
if (client->response) {
|
||||
#if CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
http_header_destroy(client->response->headers);
|
||||
#endif // CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
if (client->response->buffer) {
|
||||
free(client->response->buffer->data);
|
||||
esp_http_client_cached_buf_cleanup(client->response->buffer);
|
||||
@@ -1451,6 +1492,9 @@ esp_err_t esp_http_client_perform(esp_http_client_handle_t client)
|
||||
http_dispatch_event_to_event_loop(HTTP_EVENT_ERROR, &client, sizeof(esp_http_client_handle_t));
|
||||
return err;
|
||||
}
|
||||
#if CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
client->response->saved_response_header_count = 0;
|
||||
#endif // CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
/* falls through */
|
||||
case HTTP_STATE_REQ_COMPLETE_HEADER:
|
||||
if ((err = esp_http_client_send_post_data(client)) != ESP_OK) {
|
||||
|
||||
@@ -444,11 +444,37 @@ esp_err_t esp_http_client_set_header(esp_http_client_handle_t client, const char
|
||||
* @param[out] value The header value
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK
|
||||
* - ESP_FAIL
|
||||
* - ESP_OK: Header found
|
||||
* - ESP_ERR_INVALID_ARG: Invalid arguments
|
||||
* - ESP_ERR_NOT_FOUND: Header not found
|
||||
*/
|
||||
esp_err_t esp_http_client_get_header(esp_http_client_handle_t client, const char *key, char **value);
|
||||
|
||||
#if CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS || __DOXYGEN__
|
||||
/**
|
||||
* @brief Get a response header value by key
|
||||
* The value parameter will be set to NULL if there is no header which is same as
|
||||
* the key specified, otherwise the address of header value will be assigned to value parameter.
|
||||
* This function must be called after `esp_http_client_init`.
|
||||
*
|
||||
* @note Limitations:
|
||||
* - Only first CONFIG_ESP_HTTP_CLIENT_MAX_SAVED_RESPONSE_HEADERS (default: 10) headers are saved
|
||||
* - Headers exceeding CONFIG_ESP_HTTP_CLIENT_MAX_RESPONSE_HEADER_SIZE (default: 128) bytes are discarded
|
||||
* - Multi-value headers (e.g., Set-Cookie) only retain the last value
|
||||
* - Headers are case-insensitive for lookup but case-preserving for storage
|
||||
*
|
||||
* @param[in] client The esp_http_client handle
|
||||
* @param[in] key The header key
|
||||
* @param[out] value Pointer to store the header value. This pointer should not be freed by the user.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Header found
|
||||
* - ESP_ERR_INVALID_ARG: Invalid arguments
|
||||
* - ESP_ERR_NOT_FOUND: Header not found
|
||||
*/
|
||||
esp_err_t esp_http_client_get_response_header(esp_http_client_handle_t client, const char *key, char **value);
|
||||
#endif // CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS || __DOXYGEN__
|
||||
|
||||
/**
|
||||
* @brief Get http request username.
|
||||
* The address of username buffer will be assigned to value parameter.
|
||||
|
||||
@@ -69,6 +69,7 @@ esp_err_t http_header_get(http_header_handle_t header, const char *key, char **v
|
||||
*value = item->value;
|
||||
} else {
|
||||
*value = NULL;
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
@@ -63,7 +63,7 @@ esp_err_t http_header_destroy(http_header_handle_t header);
|
||||
esp_err_t http_header_set(http_header_handle_t header, const char *key, const char *value);
|
||||
|
||||
/**
|
||||
* @brief Sample as `http_header_set` but the value can be formated
|
||||
* @brief Sample as `http_header_set` but the value can be formatted
|
||||
*
|
||||
* @param[in] header The header
|
||||
* @param[in] key The key
|
||||
@@ -84,7 +84,7 @@ int http_header_set_format(http_header_handle_t header, const char *key, const c
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK
|
||||
* - ESP_FAIL
|
||||
* - ESP_ERR_NOT_FOUND
|
||||
*/
|
||||
esp_err_t http_header_get(http_header_handle_t header, const char *key, char **value);
|
||||
|
||||
|
||||
@@ -129,6 +129,68 @@ Examples of Authentication Configuration
|
||||
.auth_type = HTTP_AUTH_TYPE_BASIC,
|
||||
};
|
||||
|
||||
Response Header Access
|
||||
----------------------
|
||||
|
||||
ESP HTTP Client provides the ability to save and retrieve HTTP response headers from the server. This feature is useful when applications need to access metadata such as content type, cache control directives, custom server headers, or other response information.
|
||||
|
||||
Configuration
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
To enable response header saving, the following Kconfig options must be configured:
|
||||
|
||||
* :ref:`CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS`: Enable saving of response headers (disabled by default to conserve memory).
|
||||
* :ref:`CONFIG_ESP_HTTP_CLIENT_MAX_SAVED_RESPONSE_HEADERS`: Maximum number of response headers to save (default: 10).
|
||||
* :ref:`CONFIG_ESP_HTTP_CLIENT_MAX_RESPONSE_HEADER_SIZE`: Maximum size in bytes for both header key and value (default: 128 bytes each).
|
||||
|
||||
Usage
|
||||
^^^^^
|
||||
|
||||
Once enabled, response headers can be retrieved using the :cpp:func:`esp_http_client_get_response_header` function after performing an HTTP request. The function returns the header value for a given key.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#if CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
char *content_type = NULL;
|
||||
err = esp_http_client_get_response_header(client, "Content-Type", &content_type);
|
||||
if (err == ESP_OK && content_type != NULL) {
|
||||
ESP_LOGI(TAG, "Content-Type: %s", content_type);
|
||||
} else if (err == ESP_ERR_NOT_FOUND) {
|
||||
ESP_LOGW(TAG, "Content-Type header not found");
|
||||
}
|
||||
|
||||
char *date = NULL;
|
||||
err = esp_http_client_get_response_header(client, "Date", &date);
|
||||
if (err == ESP_OK && date != NULL) {
|
||||
ESP_LOGI(TAG, "Date: %s", date);
|
||||
}
|
||||
}
|
||||
|
||||
esp_http_client_cleanup(client);
|
||||
#endif
|
||||
|
||||
Important Limitations
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When using response header access, be aware of the following limitations:
|
||||
|
||||
* **Header Count Limit**: Only the first ``CONFIG_ESP_HTTP_CLIENT_MAX_SAVED_RESPONSE_HEADERS`` headers are saved. Additional headers beyond this limit are discarded with a warning log.
|
||||
* **Size Constraints**: Headers where either the key or value exceeds ``CONFIG_ESP_HTTP_CLIENT_MAX_RESPONSE_HEADER_SIZE`` bytes are discarded with a warning log showing the actual sizes.
|
||||
* **Multi-Value Headers**: For headers that appear multiple times in the response (e.g., ``Set-Cookie``), only the last value is retained.
|
||||
* **Case Sensitivity**: Header lookups are case-insensitive, but the original case is preserved in storage.
|
||||
* **Memory Overhead**: Enabling this feature increases memory consumption. Calculate approximate memory usage as: ``(CONFIG_ESP_HTTP_CLIENT_MAX_SAVED_RESPONSE_HEADERS * CONFIG_ESP_HTTP_CLIENT_MAX_RESPONSE_HEADER_SIZE * 2)`` bytes per client instance.
|
||||
* **Header Lifecycle**: Response headers are cleared when starting a new request with the same client handle via :cpp:func:`esp_http_client_perform` or :cpp:func:`esp_http_client_prepare`.
|
||||
|
||||
.. note::
|
||||
|
||||
The returned header value pointer is managed internally by the HTTP client and must not be freed by the application. The pointer remains valid until the client handle is cleaned up or a new request is initiated.
|
||||
|
||||
Event Handling
|
||||
--------------
|
||||
|
||||
|
||||
@@ -440,6 +440,28 @@ static void https_with_url(void)
|
||||
ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %"PRId64,
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
#if CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
ESP_LOGI(TAG, "Response headers: ");
|
||||
char *header_value = NULL;
|
||||
esp_err_t err = esp_http_client_get_response_header(client, "Content-Length", &header_value);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Content-Length: %s", header_value);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error getting Content-Length header: %s", esp_err_to_name(err));
|
||||
}
|
||||
err = esp_http_client_get_response_header(client, "Date", &header_value);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Date: %s", header_value);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error getting Date header: %s", esp_err_to_name(err));
|
||||
}
|
||||
err = esp_http_client_get_response_header(client, "Server", &header_value);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Server: %s", header_value);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error getting Server header: %s", esp_err_to_name(err));
|
||||
}
|
||||
#endif // CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
@@ -41,6 +41,9 @@ def test_examples_protocol_esp_http_client(dut: Dut) -> None:
|
||||
dut.expect(r'HTTP Absolute path redirect Status = 200, content_length = (\d)')
|
||||
dut.expect(r'HTTP Absolute path redirect \(manual\) Status = 200, content_length = (\d)')
|
||||
dut.expect(r'HTTPS Status = 200, content_length = (\d)')
|
||||
dut.expect(r'Content-Length: (\d+)')
|
||||
dut.expect(r'Date: (\w+)')
|
||||
dut.expect(r'Error getting Server header: ESP_ERR_NOT_FOUND')
|
||||
dut.expect(r'HTTPS Status = 200, content_length = (\d)')
|
||||
dut.expect(r'HTTP redirect to HTTPS Status = 200, content_length = (\d)')
|
||||
dut.expect(r'HTTP chunk encoding Status = 200, content_length = (-?\d)')
|
||||
|
||||
@@ -11,3 +11,5 @@ CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH=y
|
||||
CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH=y
|
||||
CONFIG_EXAMPLE_HTTP_ENDPOINT="httpbin.espressif.cn"
|
||||
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_CROSS_SIGNED_VERIFY=y
|
||||
CONFIG_MBEDTLS_SSL_PROTO_TLS1_3=y
|
||||
CONFIG_ESP_HTTP_CLIENT_SAVE_RESPONSE_HEADERS=y
|
||||
|
||||
Reference in New Issue
Block a user