feat(http): Bring esp_http_client component and example from idf

Commit ID: dd3c0329
This commit is contained in:
yuanjm
2020-08-07 11:32:37 +08:00
parent 237311e466
commit 9ea5d2abe7
17 changed files with 1020 additions and 212 deletions

View File

@ -1,11 +1,8 @@
set(COMPONENT_SRCS "esp_http_client.c"
"lib/http_auth.c"
"lib/http_header.c"
"lib/http_utils.c")
set(COMPONENT_ADD_INCLUDEDIRS "include")
set(COMPONENT_PRIV_INCLUDEDIRS "lib/include")
set(COMPONENT_REQUIRES "http_parser")
set(COMPONENT_PRIV_REQUIRES "mbedtls" "lwip" "esp-tls" "tcp_transport" "tcpip_adapter")
register_component()
idf_component_register(SRCS "esp_http_client.c"
"lib/http_auth.c"
"lib/http_header.c"
"lib/http_utils.c"
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "lib/include"
REQUIRES nghttp
PRIV_REQUIRES mbedtls lwip esp-tls tcp_transport)

View File

@ -1,18 +1,17 @@
menu "ESP HTTP client"
config ESP_HTTP_CLIENT_ENABLE_HTTPS
bool "Enable https"
default y
help
This option will enable https protocol by linking mbedtls library and initializing SSL transport
config HTTP_BUF_SIZE
int "Default HTTP Buffer Size (both send and receive)"
default 512
range 512 1460
help
Set HTTP Buffer Size. The larger buffer size will make send and receive more size packet once.
config ESP_HTTP_CLIENT_ENABLE_HTTPS
bool "Enable https"
default y
help
This option will enable https protocol by linking mbedtls library and initializing SSL transport
config ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH
bool "Enable HTTP Basic Authentication"
default n
help
This option will enable HTTP Basic Authentication. It is disabled by default as Basic
auth uses unencrypted encoding, so it introduces a vulnerability when not using TLS
endmenu

View File

@ -2,6 +2,5 @@
# Component Makefile
#
COMPONENT_SRCDIRS := . lib
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_SRCDIRS := . lib
COMPONENT_PRIV_INCLUDEDIRS := lib/include

View File

@ -110,7 +110,8 @@ struct esp_http_client {
esp_http_state_t state;
http_event_handle_cb event_handler;
int timeout_ms;
int buffer_size;
int buffer_size_rx;
int buffer_size_tx;
bool disable_auto_redirect;
esp_http_client_event_t event;
int data_written_index;
@ -152,20 +153,6 @@ static const char *HTTP_METHOD_MAPPING[] = {
"OPTIONS"
};
/**
* Enum for the HTTP status codes.
*/
enum HttpStatus_Code
{
/* 3xx - Redirection */
HttpStatus_MovedPermanently = 301,
HttpStatus_Found = 302,
/* 4xx - Client Error */
HttpStatus_Unauthorized = 401
};
static esp_err_t esp_http_client_request_send(esp_http_client_handle_t client, int write_len);
static esp_err_t esp_http_client_connect(esp_http_client_handle_t client);
static esp_err_t esp_http_client_send_post_data(esp_http_client_handle_t client);
@ -297,6 +284,63 @@ esp_err_t esp_http_client_delete_header(esp_http_client_handle_t client, const c
return http_header_delete(client->request->headers, key);
}
esp_err_t esp_http_client_get_username(esp_http_client_handle_t client, char **value)
{
if (client == NULL || value == NULL) {
ESP_LOGE(TAG, "client or value must not be NULL");
return ESP_ERR_INVALID_ARG;
}
*value = client->connection_info.username;
return ESP_OK;
}
esp_err_t esp_http_client_set_username(esp_http_client_handle_t client, const char *username)
{
if (client == NULL) {
ESP_LOGE(TAG, "client must not be NULL");
return ESP_ERR_INVALID_ARG;
}
if (client->connection_info.username != NULL) {
free(client->connection_info.username);
}
client->connection_info.username = username ? strdup(username) : NULL;
return ESP_OK;
}
esp_err_t esp_http_client_get_password(esp_http_client_handle_t client, char **value)
{
if (client == NULL || value == NULL) {
ESP_LOGE(TAG, "client or value must not be NULL");
return ESP_ERR_INVALID_ARG;
}
*value = client->connection_info.password;
return ESP_OK;
}
esp_err_t esp_http_client_set_password(esp_http_client_handle_t client, char *password)
{
if (client == NULL) {
ESP_LOGE(TAG, "client must not be NULL");
return ESP_ERR_INVALID_ARG;
}
if (client->connection_info.password != NULL) {
memset(client->connection_info.password, 0, strlen(client->connection_info.password));
free(client->connection_info.password);
}
client->connection_info.password = password ? strdup(password) : NULL;
return ESP_OK;
}
esp_err_t esp_http_client_set_authtype(esp_http_client_handle_t client, esp_http_client_auth_type_t auth_type)
{
if (client == NULL) {
ESP_LOGE(TAG, "client must not be NULL");
return ESP_ERR_INVALID_ARG;
}
client->connection_info.auth_type = auth_type;
return ESP_OK;
}
static esp_err_t _set_config(esp_http_client_handle_t client, const esp_http_client_config_t *config)
{
client->connection_info.method = config->method;
@ -306,11 +350,16 @@ static esp_err_t _set_config(esp_http_client_handle_t client, const esp_http_cli
client->timeout_ms = config->timeout_ms;
client->max_redirection_count = config->max_redirection_count;
client->user_data = config->user_data;
client->buffer_size = config->buffer_size;
client->buffer_size_rx = config->buffer_size;
client->buffer_size_tx = config->buffer_size_tx;
client->disable_auto_redirect = config->disable_auto_redirect;
if (config->buffer_size == 0) {
client->buffer_size = DEFAULT_HTTP_BUF_SIZE;
client->buffer_size_rx = DEFAULT_HTTP_BUF_SIZE;
}
if (config->buffer_size_tx == 0) {
client->buffer_size_tx = DEFAULT_HTTP_BUF_SIZE;
}
if (client->max_redirection_count == 0) {
@ -461,8 +510,7 @@ esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *co
if (!_success) {
ESP_LOGE(TAG, "Error allocate memory");
esp_http_client_cleanup(client);
return NULL;
goto error;
}
_success = (
@ -473,8 +521,7 @@ esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *co
);
if (!_success) {
ESP_LOGE(TAG, "Error initialize transport");
esp_http_client_cleanup(client);
return NULL;
goto error;
}
#ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS
esp_transport_handle_t ssl;
@ -486,41 +533,66 @@ esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *co
if (!_success) {
ESP_LOGE(TAG, "Error initialize SSL Transport");
esp_http_client_cleanup(client);
return NULL;
goto error;
}
if (config->cert_pem) {
if (config->use_global_ca_store == true) {
esp_transport_ssl_enable_global_ca_store(ssl);
} else if (config->cert_pem) {
esp_transport_ssl_set_cert_data(ssl, config->cert_pem, strlen(config->cert_pem));
}
if (config->client_cert_pem) {
esp_transport_ssl_set_client_cert_data(ssl, config->client_cert_pem, strlen(config->client_cert_pem));
}
if (config->client_key_pem) {
esp_transport_ssl_set_client_key_data(ssl, config->client_key_pem, strlen(config->client_key_pem));
}
if (config->skip_cert_common_name_check) {
esp_transport_ssl_skip_common_name_check(ssl);
}
#endif
if (_set_config(client, config) != ESP_OK) {
ESP_LOGE(TAG, "Error set configurations");
esp_http_client_cleanup(client);
return NULL;
goto error;
}
_success = (
(client->request->buffer->data = malloc(client->buffer_size)) &&
(client->response->buffer->data = malloc(client->buffer_size))
(client->request->buffer->data = malloc(client->buffer_size_tx)) &&
(client->response->buffer->data = malloc(client->buffer_size_rx))
);
if (!_success) {
ESP_LOGE(TAG, "Allocation failed");
esp_http_client_cleanup(client);
return NULL;
goto error;
}
_success = (
(esp_http_client_set_url(client, config->url) == ESP_OK) &&
(esp_http_client_set_header(client, "User-Agent", DEFAULT_HTTP_USER_AGENT) == ESP_OK) &&
(esp_http_client_set_header(client, "Host", client->connection_info.host) == ESP_OK)
);
if (config->host != NULL && config->path != NULL) {
_success = (
(esp_http_client_set_header(client, "User-Agent", DEFAULT_HTTP_USER_AGENT) == ESP_OK) &&
(esp_http_client_set_header(client, "Host", client->connection_info.host) == ESP_OK)
);
if (!_success) {
ESP_LOGE(TAG, "Error set default configurations");
esp_http_client_cleanup(client);
return NULL;
if (!_success) {
ESP_LOGE(TAG, "Error while setting default configurations");
goto error;
}
} else if (config->url != NULL) {
_success = (
(esp_http_client_set_url(client, config->url) == ESP_OK) &&
(esp_http_client_set_header(client, "User-Agent", DEFAULT_HTTP_USER_AGENT) == ESP_OK) &&
(esp_http_client_set_header(client, "Host", client->connection_info.host) == ESP_OK)
);
if (!_success) {
ESP_LOGE(TAG, "Error while setting default configurations");
goto error;
}
} else {
ESP_LOGE(TAG, "config should have either URL or host & path");
goto error;
}
client->parser_settings->on_message_begin = http_on_message_begin;
@ -537,6 +609,9 @@ esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *co
client->state = HTTP_STATE_INIT;
return client;
error:
esp_http_client_cleanup(client);
return NULL;
}
esp_err_t esp_http_client_cleanup(esp_http_client_handle_t client)
@ -546,14 +621,22 @@ esp_err_t esp_http_client_cleanup(esp_http_client_handle_t client)
}
esp_http_client_close(client);
esp_transport_list_destroy(client->transport_list);
http_header_destroy(client->request->headers);
free(client->request->buffer->data);
free(client->request->buffer);
free(client->request);
http_header_destroy(client->response->headers);
free(client->response->buffer->data);
free(client->response->buffer);
free(client->response);
if (client->request) {
http_header_destroy(client->request->headers);
if (client->request->buffer) {
free(client->request->buffer->data);
}
free(client->request->buffer);
free(client->request);
}
if (client->response) {
http_header_destroy(client->response->headers);
if (client->response->buffer) {
free(client->response->buffer->data);
}
free(client->response->buffer);
free(client->response);
}
free(client->parser);
free(client->parser_settings);
@ -567,10 +650,20 @@ esp_err_t esp_http_client_cleanup(esp_http_client_handle_t client)
return ESP_OK;
}
esp_err_t esp_http_client_set_redirection(esp_http_client_handle_t client)
{
if (client == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (client->location == NULL) {
return ESP_ERR_INVALID_ARG;
}
ESP_LOGD(TAG, "Redirect to %s", client->location);
return esp_http_client_set_url(client, client->location);
}
static esp_err_t esp_http_check_response(esp_http_client_handle_t client)
{
char *auth_header = NULL;
if (client->redirect_counter >= client->max_redirection_count || client->disable_auto_redirect) {
ESP_LOGE(TAG, "Error, reach max_redirection_count count=%d", client->redirect_counter);
return ESP_ERR_HTTP_MAX_REDIRECT;
@ -578,44 +671,13 @@ static esp_err_t esp_http_check_response(esp_http_client_handle_t client)
switch (client->response->status_code) {
case HttpStatus_MovedPermanently:
case HttpStatus_Found:
ESP_LOGI(TAG, "Redirect to %s", client->location);
esp_http_client_set_url(client, client->location);
case HttpStatus_TemporaryRedirect:
esp_http_client_set_redirection(client);
client->redirect_counter ++;
client->process_again = 1;
break;
case HttpStatus_Unauthorized:
auth_header = client->auth_header;
if (auth_header) {
http_utils_trim_whitespace(&auth_header);
ESP_LOGD(TAG, "UNAUTHORIZED: %s", auth_header);
client->redirect_counter ++;
if (http_utils_str_starts_with(auth_header, "Digest") == 0) {
ESP_LOGD(TAG, "type = Digest");
client->connection_info.auth_type = HTTP_AUTH_TYPE_DIGEST;
} else if (http_utils_str_starts_with(auth_header, "Basic") == 0) {
ESP_LOGD(TAG, "type = Basic");
client->connection_info.auth_type = HTTP_AUTH_TYPE_BASIC;
} else {
client->connection_info.auth_type = HTTP_AUTH_TYPE_NONE;
ESP_LOGE(TAG, "This authentication method is not supported: %s", auth_header);
break;
}
_clear_auth_data(client);
client->auth_data->method = strdup(HTTP_METHOD_MAPPING[client->connection_info.method]);
client->auth_data->nc = 1;
client->auth_data->realm = http_utils_get_string_between(auth_header, "realm=\"", "\"");
client->auth_data->algorithm = http_utils_get_string_between(auth_header, "algorithm=", ",");
client->auth_data->qop = http_utils_get_string_between(auth_header, "qop=\"", "\"");
client->auth_data->nonce = http_utils_get_string_between(auth_header, "nonce=\"", "\"");
client->auth_data->opaque = http_utils_get_string_between(auth_header, "opaque=\"", "\"");
client->process_again = 1;
} else {
client->connection_info.auth_type = HTTP_AUTH_TYPE_NONE;
ESP_LOGW(TAG, "This request requires authentication, but does not provide header information for that");
}
esp_http_client_add_auth(client);
}
return ESP_OK;
}
@ -639,23 +701,34 @@ esp_err_t esp_http_client_set_url(esp_http_client_handle_t client, const char *u
ESP_LOGE(TAG, "Error parse url %s", url);
return ESP_ERR_INVALID_ARG;
}
old_host = client->connection_info.host;
if (client->connection_info.host) {
old_host = strdup(client->connection_info.host);
}
old_port = client->connection_info.port;
if (purl.field_data[UF_HOST].len) {
http_utils_assign_string(&client->connection_info.host, url + purl.field_data[UF_HOST].off, purl.field_data[UF_HOST].len);
HTTP_MEM_CHECK(TAG, client->connection_info.host, return ESP_ERR_NO_MEM);
HTTP_MEM_CHECK(TAG, client->connection_info.host, {
free(old_host);
return ESP_ERR_NO_MEM;
});
}
// Close the connection if host was changed
if (old_host && client->connection_info.host
&& strcasecmp(old_host, (const void *)client->connection_info.host) != 0) {
ESP_LOGD(TAG, "New host assign = %s", client->connection_info.host);
if (esp_http_client_set_header(client, "Host", client->connection_info.host) != ESP_OK) {
free(old_host);
return ESP_ERR_NO_MEM;
}
esp_http_client_close(client);
}
if (old_host) {
free(old_host);
old_host = NULL;
}
if (purl.field_data[UF_SCHEMA].len) {
http_utils_assign_string(&client->connection_info.scheme, url + purl.field_data[UF_SCHEMA].off, purl.field_data[UF_SCHEMA].len);
HTTP_MEM_CHECK(TAG, client->connection_info.scheme, return ESP_ERR_NO_MEM);
@ -693,13 +766,7 @@ esp_err_t esp_http_client_set_url(esp_http_client_handle_t client, const char *u
} else {
return ESP_ERR_NO_MEM;
}
} else {
free(client->connection_info.username);
free(client->connection_info.password);
client->connection_info.username = NULL;
client->connection_info.password = NULL;
}
}
//Reset path and query if there are no information
if (purl.field_data[UF_PATH].len) {
@ -740,13 +807,29 @@ static int esp_http_client_get_data(esp_http_client_handle_t client)
ESP_LOGD(TAG, "data_process=%d, content_length=%d", client->response->data_process, client->response->content_length);
int rlen = esp_transport_read(client->transport, res_buffer->data, client->buffer_size, client->timeout_ms);
int rlen = esp_transport_read(client->transport, res_buffer->data, client->buffer_size_rx, client->timeout_ms);
if (rlen >= 0) {
http_parser_execute(client->parser, client->parser_settings, res_buffer->data, rlen);
}
return rlen;
}
bool esp_http_client_is_complete_data_received(esp_http_client_handle_t client)
{
if (client->response->is_chunked) {
if (!client->is_chunk_complete) {
ESP_LOGD(TAG, "Chunks were not completely read");
return false;
}
} else {
if (client->response->data_process != client->response->content_length) {
ESP_LOGD(TAG, "Data processed %d != Data specified in content length %d", client->response->data_process, client->response->content_length);
return false;
}
}
return true;
}
int esp_http_client_read(esp_http_client_handle_t client, char *buffer, int len)
{
esp_http_buffer_t *res_buffer = client->response->buffer;
@ -770,19 +853,36 @@ int esp_http_client_read(esp_http_client_handle_t client, char *buffer, int len)
} else {
is_data_remain = client->response->data_process < client->response->content_length;
}
ESP_LOGD(TAG, "is_data_remain=%d, is_chunked=%d", is_data_remain, client->response->is_chunked);
ESP_LOGD(TAG, "is_data_remain=%d, is_chunked=%d, content_length=%d", is_data_remain, client->response->is_chunked, client->response->content_length);
if (!is_data_remain) {
break;
}
int byte_to_read = need_read;
if (byte_to_read > client->buffer_size) {
byte_to_read = client->buffer_size;
if (byte_to_read > client->buffer_size_rx) {
byte_to_read = client->buffer_size_rx;
}
errno = 0;
rlen = esp_transport_read(client->transport, res_buffer->data, byte_to_read, client->timeout_ms);
ESP_LOGD(TAG, "need_read=%d, byte_to_read=%d, rlen=%d, ridx=%d", need_read, byte_to_read, rlen, ridx);
if (rlen <= 0) {
return ridx;
if (errno != 0) {
esp_log_level_t sev = ESP_LOG_WARN;
/* On connection close from server, recv should ideally return 0 but we have error conversion
* in `tcp_transport` SSL layer which translates it `-1` and hence below additional checks */
if (rlen == -1 && errno == ENOTCONN && client->response->is_chunked) {
/* Explicit call to parser for invoking `message_complete` callback */
http_parser_execute(client->parser, client->parser_settings, res_buffer->data, 0);
/* ...and lowering the message severity, as closed connection from server side is expected in chunked transport */
sev = ESP_LOG_DEBUG;
}
ESP_LOG_LEVEL(sev, TAG, "esp_transport_read returned:%d and errno:%d ", rlen, errno);
}
if (rlen < 0 && ridx == 0) {
return ESP_FAIL;
} else {
return ridx;
}
}
res_buffer->output_ptr = buffer + ridx;
http_parser_execute(client->parser, client->parser_settings, res_buffer->data, rlen);
@ -893,8 +993,8 @@ int esp_http_client_fetch_headers(esp_http_client_handle_t client)
client->response->status_code = -1;
while (client->state < HTTP_STATE_RES_COMPLETE_HEADER) {
buffer->len = esp_transport_read(client->transport, buffer->data, client->buffer_size, client->timeout_ms);
if (buffer->len < 0) {
buffer->len = esp_transport_read(client->transport, buffer->data, client->buffer_size_rx, client->timeout_ms);
if (buffer->len <= 0) {
return ESP_FAIL;
}
http_parser_execute(client->parser, client->parser_settings, buffer->data, buffer->len);
@ -971,26 +1071,26 @@ static int http_client_prepare_first_line(esp_http_client_handle_t client, int w
const char *method = HTTP_METHOD_MAPPING[client->connection_info.method];
int first_line_len = snprintf(client->request->buffer->data,
client->buffer_size, "%s %s",
client->buffer_size_tx, "%s %s",
method,
client->connection_info.path);
if (first_line_len >= client->buffer_size) {
if (first_line_len >= client->buffer_size_tx) {
ESP_LOGE(TAG, "Out of buffer");
return -1;
}
if (client->connection_info.query) {
first_line_len += snprintf(client->request->buffer->data + first_line_len,
client->buffer_size - first_line_len, "?%s", client->connection_info.query);
if (first_line_len >= client->buffer_size) {
client->buffer_size_tx - first_line_len, "?%s", client->connection_info.query);
if (first_line_len >= client->buffer_size_tx) {
ESP_LOGE(TAG, "Out of buffer");
return -1;
}
}
first_line_len += snprintf(client->request->buffer->data + first_line_len,
client->buffer_size - first_line_len, " %s\r\n", DEFAULT_HTTP_PROTOCOL);
if (first_line_len >= client->buffer_size) {
client->buffer_size_tx - first_line_len, " %s\r\n", DEFAULT_HTTP_PROTOCOL);
if (first_line_len >= client->buffer_size_tx) {
ESP_LOGE(TAG, "Out of buffer");
return -1;
}
@ -1025,7 +1125,7 @@ static esp_err_t esp_http_client_request_send(esp_http_client_handle_t client, i
}
}
int wlen = client->buffer_size - first_line_len;
int wlen = client->buffer_size_tx - first_line_len;
while ((client->header_index = http_header_generate_string(client->request->headers, client->header_index, client->request->buffer->data + first_line_len, &wlen))) {
if (wlen <= 0) {
break;
@ -1049,11 +1149,12 @@ static esp_err_t esp_http_client_request_send(esp_http_client_handle_t client, i
client->data_write_left -= wret;
client->data_written_index += wret;
}
wlen = client->buffer_size;
wlen = client->buffer_size_tx;
}
client->data_written_index = 0;
client->data_write_left = client->post_len;
http_dispatch_event(client, HTTP_EVENT_HEADERS_SENT, NULL, 0);
client->state = HTTP_STATE_REQ_COMPLETE_HEADER;
return ESP_OK;
}
@ -1088,12 +1189,13 @@ success:
esp_err_t esp_http_client_open(esp_http_client_handle_t client, int write_len)
{
client->post_len = write_len;
esp_err_t err;
if ((err = esp_http_client_connect(client)) != ESP_OK) {
return err;
}
if ((err = esp_http_client_request_send(client, write_len)) != ESP_OK) {
return err;
return err;
}
return ESP_OK;
}
@ -1121,7 +1223,7 @@ int esp_http_client_write(esp_http_client_handle_t client, const char *buffer, i
esp_err_t esp_http_client_close(esp_http_client_handle_t client)
{
if (client->state >= HTTP_STATE_INIT) {
http_dispatch_event(client, HTTP_EVENT_DISCONNECTED, NULL, 0);
http_dispatch_event(client, HTTP_EVENT_DISCONNECTED, esp_transport_get_error_handle(client->transport), 0);
client->state = HTTP_STATE_INIT;
return esp_transport_close(client->transport);
}
@ -1175,11 +1277,83 @@ bool esp_http_client_is_chunked_response(esp_http_client_handle_t client)
esp_http_client_transport_t esp_http_client_get_transport_type(esp_http_client_handle_t client)
{
if (!strcmp(client->connection_info.scheme, "https") ) {
if (!strcasecmp(client->connection_info.scheme, "https") ) {
return HTTP_TRANSPORT_OVER_SSL;
} else if (!strcmp(client->connection_info.scheme, "http")) {
} else if (!strcasecmp(client->connection_info.scheme, "http")) {
return HTTP_TRANSPORT_OVER_TCP;
} else {
return HTTP_TRANSPORT_UNKNOWN;
}
}
void esp_http_client_add_auth(esp_http_client_handle_t client)
{
if (client == NULL) {
return;
}
if (client->state != HTTP_STATE_RES_COMPLETE_HEADER) {
return;
}
char *auth_header = client->auth_header;
if (auth_header) {
http_utils_trim_whitespace(&auth_header);
ESP_LOGD(TAG, "UNAUTHORIZED: %s", auth_header);
client->redirect_counter++;
if (http_utils_str_starts_with(auth_header, "Digest") == 0) {
ESP_LOGD(TAG, "type = Digest");
client->connection_info.auth_type = HTTP_AUTH_TYPE_DIGEST;
#ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH
} else if (http_utils_str_starts_with(auth_header, "Basic") == 0) {
ESP_LOGD(TAG, "type = Basic");
client->connection_info.auth_type = HTTP_AUTH_TYPE_BASIC;
#endif
} else {
client->connection_info.auth_type = HTTP_AUTH_TYPE_NONE;
ESP_LOGE(TAG, "This authentication method is not supported: %s", auth_header);
return;
}
_clear_auth_data(client);
client->auth_data->method = strdup(HTTP_METHOD_MAPPING[client->connection_info.method]);
client->auth_data->nc = 1;
client->auth_data->realm = http_utils_get_string_between(auth_header, "realm=\"", "\"");
client->auth_data->algorithm = http_utils_get_string_between(auth_header, "algorithm=", ",");
client->auth_data->qop = http_utils_get_string_between(auth_header, "qop=\"", "\"");
client->auth_data->nonce = http_utils_get_string_between(auth_header, "nonce=\"", "\"");
client->auth_data->opaque = http_utils_get_string_between(auth_header, "opaque=\"", "\"");
client->process_again = 1;
} else {
client->connection_info.auth_type = HTTP_AUTH_TYPE_NONE;
ESP_LOGW(TAG, "This request requires authentication, but does not provide header information for that");
}
}
int esp_http_client_read_response(esp_http_client_handle_t client, char *buffer, int len)
{
int read_len = 0;
while (read_len < len) {
int data_read = esp_http_client_read(client, buffer + read_len, len - read_len);
if (data_read <= 0) {
return read_len;
}
read_len += data_read;
}
return read_len;
}
esp_err_t esp_http_client_get_url(esp_http_client_handle_t client, char *url, const int len)
{
if (client == NULL || url == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (client->connection_info.host && client->connection_info.scheme && client->connection_info.path) {
snprintf(url, len, "%s://%s%s", client->connection_info.scheme, client->connection_info.host, client->connection_info.path);
return ESP_OK;
} else {
ESP_LOGE(TAG, "Failed to get URL");
}
return ESP_FAIL;
}

View File

@ -24,7 +24,7 @@
extern "C" {
#endif
#define DEFAULT_HTTP_BUF_SIZE CONFIG_HTTP_BUF_SIZE
#define DEFAULT_HTTP_BUF_SIZE (512)
typedef struct esp_http_client *esp_http_client_handle_t;
typedef struct esp_http_client_event *esp_http_client_event_handle_t;
@ -35,7 +35,9 @@ typedef struct esp_http_client_event *esp_http_client_event_handle_t;
typedef enum {
HTTP_EVENT_ERROR = 0, /*!< This event occurs when there are any errors during execution */
HTTP_EVENT_ON_CONNECTED, /*!< Once the HTTP has been connected to the server, no data exchange has been performed */
HTTP_EVENT_HEADER_SENT, /*!< After sending all the headers to the server */
HTTP_EVENT_HEADERS_SENT, /*!< After sending all the headers to the server */
HTTP_EVENT_HEADER_SENT = HTTP_EVENT_HEADERS_SENT, /*!< This header has been kept for backward compatability
and will be deprecated in future versions esp-idf */
HTTP_EVENT_ON_HEADER, /*!< Occurs when receiving each header sent from the server */
HTTP_EVENT_ON_DATA, /*!< Occurs when receiving data from the server, possibly multiple portions of the packet */
HTTP_EVENT_ON_FINISH, /*!< Occurs when finish a HTTP session */
@ -105,18 +107,35 @@ typedef struct {
esp_http_client_auth_type_t auth_type; /*!< Http authentication type, see `esp_http_client_auth_type_t` */
const char *path; /*!< HTTP Path, if not set, default is `/` */
const char *query; /*!< HTTP query */
const char *cert_pem; /*!< SSL Certification, PEM format as string, if the client requires to verify server */
const char *cert_pem; /*!< SSL server certification, PEM format as string, if the client requires to verify server */
const char *client_cert_pem; /*!< SSL client certification, PEM format as string, if the server requires to verify client */
const char *client_key_pem; /*!< SSL client key, PEM format as string, if the server requires to verify client */
esp_http_client_method_t method; /*!< HTTP Method */
int timeout_ms; /*!< Network timeout in milliseconds */
bool disable_auto_redirect; /*!< Disable HTTP automatic redirects */
int max_redirection_count; /*!< Max redirection number, using default value if zero*/
http_event_handle_cb event_handler; /*!< HTTP Event Handle */
esp_http_client_transport_t transport_type; /*!< HTTP transport type, see `esp_http_client_transport_t` */
int buffer_size; /*!< HTTP buffer size (both send and receive) */
int buffer_size; /*!< HTTP receive buffer size */
int buffer_size_tx; /*!< HTTP transmit buffer size */
void *user_data; /*!< HTTP user_data context */
bool is_async; /*!< Set asynchronous mode, only supported with HTTPS for now */
bool use_global_ca_store; /*!< Use a global ca_store for all the connections in which this bool is set. */
bool skip_cert_common_name_check; /*!< Skip any validation of server certificate CN field */
} esp_http_client_config_t;
/**
* Enum for the HTTP status codes.
*/
typedef enum {
/* 3xx - Redirection */
HttpStatus_MovedPermanently = 301,
HttpStatus_Found = 302,
HttpStatus_TemporaryRedirect = 307,
/* 4xx - Client Error */
HttpStatus_Unauthorized = 401
} HttpStatus_Code;
#define ESP_ERR_HTTP_BASE (0x7000) /*!< Starting number of HTTP error codes */
#define ESP_ERR_HTTP_MAX_REDIRECT (ESP_ERR_HTTP_BASE + 1) /*!< The error exceeds the number of HTTP redirects */
@ -232,13 +251,83 @@ esp_err_t esp_http_client_set_header(esp_http_client_handle_t client, const char
*/
esp_err_t esp_http_client_get_header(esp_http_client_handle_t client, const char *key, char **value);
/**
* @brief Get http request username.
* The address of username buffer will be assigned to value parameter.
* This function must be called after `esp_http_client_init`.
*
* @param[in] client The esp_http_client handle
* @param[out] value The username value
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG
*/
esp_err_t esp_http_client_get_username(esp_http_client_handle_t client, char **value);
/**
* @brief Set http request username.
* The value of username parameter will be assigned to username buffer.
* If the username parameter is NULL then username buffer will be freed.
*
* @param[in] client The esp_http_client handle
* @param[in] username The username value
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG
*/
esp_err_t esp_http_client_set_username(esp_http_client_handle_t client, const char *username);
/**
* @brief Get http request password.
* The address of password buffer will be assigned to value parameter.
* This function must be called after `esp_http_client_init`.
*
* @param[in] client The esp_http_client handle
* @param[out] value The password value
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG
*/
esp_err_t esp_http_client_get_password(esp_http_client_handle_t client, char **value);
/**
* @brief Set http request password.
* The value of password parameter will be assigned to password buffer.
* If the password parameter is NULL then password buffer will be freed.
*
* @param[in] client The esp_http_client handle
* @param[in] password The password value
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG
*/
esp_err_t esp_http_client_set_password(esp_http_client_handle_t client, char *password);
/**
* @brief Set http request auth_type.
*
* @param[in] client The esp_http_client handle
* @param[in] auth_type The esp_http_client auth type
*
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG
*/
esp_err_t esp_http_client_set_authtype(esp_http_client_handle_t client, esp_http_client_auth_type_t auth_type);
/**
* @brief Set http request method
*
* @param[in] client The esp_http_client handle
* @param[in] method The method
*
* @return ESP_OK
* @return
* - ESP_OK
* - ESP_ERR_INVALID_ARG
*/
esp_err_t esp_http_client_set_method(esp_http_client_handle_t client, esp_http_client_method_t method);
@ -373,6 +462,68 @@ esp_err_t esp_http_client_cleanup(esp_http_client_handle_t client);
*/
esp_http_client_transport_t esp_http_client_get_transport_type(esp_http_client_handle_t client);
/**
* @brief Set redirection URL.
* When received the 30x code from the server, the client stores the redirect URL provided by the server.
* This function will set the current URL to redirect to enable client to execute the redirection request.
*
* @param[in] client The esp_http_client handle
*
* @return
* - ESP_OK
* - ESP_FAIL
*/
esp_err_t esp_http_client_set_redirection(esp_http_client_handle_t client);
/**
* @brief On receiving HTTP Status code 401, this API can be invoked to add authorization
* information.
*
* @note There is a possibility of receiving body message with redirection status codes, thus make sure
* to flush off body data after calling this API.
*
* @param[in] client The esp_http_client handle
*/
void esp_http_client_add_auth(esp_http_client_handle_t client);
/**
* @brief Checks if entire data in the response has been read without any error.
*
* @param[in] client The esp_http_client handle
*
* @return
* - true
* - false
*/
bool esp_http_client_is_complete_data_received(esp_http_client_handle_t client);
/**
* @brief Helper API to read larger data chunks
* This is a helper API which internally calls `esp_http_client_read` multiple times till the end of data is reached or till the buffer gets full.
*
* @param[in] client The esp_http_client handle
* @param buffer The buffer
* @param[in] len The buffer length
*
* @return
* - Length of data was read
*/
int esp_http_client_read_response(esp_http_client_handle_t client, char *buffer, int len);
/**
* @brief Get URL from client
*
* @param[in] client The esp_http_client handle
* @param[inout] url The buffer to store URL
* @param[in] len The buffer length
*
* @return
* - ESP_OK
* - ESP_FAIL
*/
esp_err_t esp_http_client_get_url(esp_http_client_handle_t client, char *url, const int len);
#ifdef __cplusplus
}

View File

@ -17,11 +17,11 @@
#include <stdio.h>
#include <stdarg.h>
#include "tcpip_adapter.h"
#include "esp_netif.h"
#include "lwip/sockets.h"
#include "rom/md5_hash.h"
#include "esp32/rom/md5_hash.h"
#include "mbedtls/base64.h"
#include "esp_base64.h"
#include "esp_system.h"
#include "esp_log.h"
@ -81,10 +81,6 @@ char *http_auth_digest(const char *username, const char *password, esp_http_auth
return NULL;
}
if (auth_data->algorithm == NULL) {
auth_data->algorithm = "md5";
}
ha1 = calloc(1, MD5_MAX_LEN);
HTTP_MEM_CHECK(TAG, ha1, goto _digest_exit);
@ -138,19 +134,17 @@ _digest_exit:
char *http_auth_basic(const char *username, const char *password)
{
int out;
char *user_info = NULL;
char *digest = NULL;
size_t n = 0, size = 0;
size_t n = 0;
asprintf(&user_info, "%s:%s", username, password);
HTTP_MEM_CHECK(TAG, user_info, return NULL);
size = strlen(user_info);
n = (size / 3) * 4 + 1; // String to Base64 length calculation
if (size % 3 != 0)
n += 4;
digest = calloc(1, 6 + n);
mbedtls_base64_encode(NULL, 0, &n, (const unsigned char *)user_info, strlen(user_info));
digest = calloc(1, 6 + n + 1);
HTTP_MEM_CHECK(TAG, digest, goto _basic_exit);
strcpy(digest, "Basic ");
n = esp_base64_encode((const unsigned char *)user_info, strlen(user_info), (unsigned char *)digest + 6, n);
mbedtls_base64_encode((unsigned char *)digest + 6, n, (size_t *)&out, (const unsigned char *)user_info, strlen(user_info));
_basic_exit:
free(user_info);
return digest;

View File

@ -37,7 +37,7 @@ typedef struct http_header_item {
STAILQ_HEAD(http_header, http_header_item);
http_header_handle_t http_header_init()
http_header_handle_t http_header_init(void)
{
http_header_handle_t header = calloc(1, sizeof(struct http_header));
HTTP_MEM_CHECK(TAG, header, return NULL);
@ -178,6 +178,8 @@ int http_header_generate_string(http_header_handle_t header, int index, char *bu
int idx = 0;
int ret_idx = -1;
bool is_end = false;
// iterate over the header entries to calculate buffer size and determine last item
STAILQ_FOREACH(item, header, next) {
if (item->value && idx >= index) {
siz += strlen(item->key);
@ -187,7 +189,10 @@ int http_header_generate_string(http_header_handle_t header, int index, char *bu
idx ++;
if (siz + 1 > *buffer_len - 2) {
// if this item would not fit to the buffer, return the index of the last fitting one
ret_idx = idx - 1;
ESP_LOGE(TAG, "Buffer length is small to fit all the headers");
break;
}
}
@ -195,10 +200,12 @@ int http_header_generate_string(http_header_handle_t header, int index, char *bu
return 0;
}
if (ret_idx < 0) {
// all items would fit, mark this as the end of http header string
ret_idx = idx;
is_end = true;
}
// iterate again over the header entries to write only the fitting indeces
int str_len = 0;
idx = 0;
STAILQ_FOREACH(item, header, next) {
@ -208,6 +215,7 @@ int http_header_generate_string(http_header_handle_t header, int index, char *bu
idx ++;
}
if (is_end) {
// write the http header terminator if all header entries have been written in this function call
str_len += snprintf(buffer + str_len, *buffer_len - str_len, "\r\n");
}
*buffer_len = str_len;

View File

@ -15,7 +15,7 @@
#ifndef _HTTP_HEADER_H_
#define _HTTP_HEADER_H_
#include "rom/queue.h"
#include "sys/queue.h"
#include "esp_err.h"
#ifdef __cplusplus
@ -32,7 +32,7 @@ typedef struct http_header_item *http_header_item_handle_t;
* - http_header_handle_t
* - NULL if any errors
*/
http_header_handle_t http_header_init();
http_header_handle_t http_header_init(void);
/**
* @brief Cleanup and free all http header pairs

View File

@ -0,0 +1,3 @@
idf_component_register(SRC_DIRS "."
PRIV_INCLUDE_DIRS "."
PRIV_REQUIRES unity test_utils esp_http_client)

View File

@ -0,0 +1 @@
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

View File

@ -0,0 +1,139 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdlib.h>
#include <stdbool.h>
#include <esp_system.h>
#include <esp_http_client.h>
#include "unity.h"
#include "test_utils.h"
#define HOST "httpbin.org"
#define USERNAME "user"
#define PASSWORD "challenge"
TEST_CASE("Test in common case: Only URL and hostname are specified.", "[ESP HTTP CLIENT]")
{
esp_http_client_config_t config_incorrect = {0};
test_case_uses_tcpip();
esp_http_client_handle_t client = esp_http_client_init(&config_incorrect);
TEST_ASSERT(client == NULL);
esp_http_client_config_t config_with_url = {
.url = "http://httpbin.org/get",
};
client = esp_http_client_init(&config_with_url);
TEST_ASSERT(client != NULL);
TEST_ASSERT(esp_http_client_cleanup(client) == ESP_OK);
esp_http_client_config_t config_with_hostname_path = {
.host = HOST,
.path = "/get",
};
client = esp_http_client_init(&config_with_hostname_path);
TEST_ASSERT(client != NULL);
TEST_ASSERT(esp_http_client_cleanup(client) == ESP_OK);
}
TEST_CASE("Get username and password after initialization.", "[ESP HTTP CLIENT]")
{
esp_http_client_config_t config_with_auth = {
.host = HOST,
.path = "/",
.username = USERNAME,
.password = PASSWORD
};
char *value = NULL;
esp_http_client_handle_t client = esp_http_client_init(&config_with_auth);
TEST_ASSERT_NOT_NULL(client);
// Test with username
esp_err_t r = esp_http_client_get_username(client, &value);
TEST_ASSERT_EQUAL(ESP_OK, r);
TEST_ASSERT_NOT_NULL(value);
TEST_ASSERT_EQUAL_STRING(USERNAME, value);
// Test with password
value = NULL;
r = esp_http_client_get_password(client, &value);
TEST_ASSERT_EQUAL(ESP_OK, r);
TEST_ASSERT_NOT_NULL(value);
TEST_ASSERT_EQUAL_STRING(PASSWORD, value);
esp_http_client_cleanup(client);
}
/**
* Test case to test that, the esp_http_client_set_url won't drop username and password
* when pass a path "/abc" for url.
**/
TEST_CASE("Username is unmodified when we change to new path", "[ESP HTTP CLIENT]")
{
esp_http_client_config_t config_with_auth = {
.host = HOST,
.path = "/",
.username = USERNAME,
.password = PASSWORD
};
char *value = NULL;
esp_http_client_handle_t client = esp_http_client_init(&config_with_auth);
TEST_ASSERT_NOT_NULL(client);
esp_err_t r = esp_http_client_get_username(client, &value);
TEST_ASSERT_EQUAL(ESP_OK, r);
TEST_ASSERT_NOT_NULL(value);
TEST_ASSERT_EQUAL_STRING(USERNAME, value);
esp_http_client_set_url(client, "/something-else/");
r = esp_http_client_get_username(client, &value);
TEST_ASSERT_EQUAL(ESP_OK, r);
TEST_ASSERT_NOT_NULL(value);
TEST_ASSERT_EQUAL_STRING(USERNAME, value);
esp_http_client_cleanup(client);
}
/**
* Test case to test that, the esp_http_client_set_url do not reset the auth credentials
* Explicit APIs esp_http_client_set_username and esp_http_client_set_password are used to change
* the auth credentials
**/
TEST_CASE("Username and password will not reset if new absolute URL doesnot specify auth credentials.", "[ESP HTTP CLIENT]")
{
esp_http_client_config_t config_with_auth = {
.host = HOST,
.path = "/",
.username = USERNAME,
.password = PASSWORD
};
char *value = NULL;
esp_http_client_handle_t client = esp_http_client_init(&config_with_auth);
TEST_ASSERT_NOT_NULL(client);
esp_err_t r = esp_http_client_get_username(client, &value);
TEST_ASSERT_EQUAL(ESP_OK, r);
TEST_ASSERT_NOT_NULL(value);
TEST_ASSERT_EQUAL_STRING(USERNAME, value);
esp_http_client_set_url(client, "http://" HOST "/get");
esp_http_client_set_username(client, value);
esp_http_client_set_password(client, value);
//checks if username is set or not
r = esp_http_client_get_username(client, &value);
TEST_ASSERT_EQUAL(ESP_OK, r);
//If username is set then value should not be NULL
TEST_ASSERT_NOT_NULL(value);
//checks if password is set or not
r = esp_http_client_get_password(client, &value);
TEST_ASSERT_EQUAL(ESP_OK, r);
//If password is set then value should not be NULL
TEST_ASSERT_NOT_NULL(value);
esp_http_client_cleanup(client);
}

View File

@ -0,0 +1,67 @@
import re
import os
import ttfw_idf
@ttfw_idf.idf_example_test(env_tag="Example_EthKitV1")
def test_examples_protocol_esp_http_client(env, extra_data):
"""
steps: |
1. join AP
2. Send HTTP request to httpbin.org
"""
dut1 = env.get_dut("esp_http_client", "examples/protocols/esp_http_client", dut_class=ttfw_idf.ESP32DUT)
# check and log bin size
binary_file = os.path.join(dut1.app.binary_path, "esp-http-client-example.bin")
bin_size = os.path.getsize(binary_file)
ttfw_idf.log_performance("esp_http_client_bin_size", "{}KB".format(bin_size // 1024))
ttfw_idf.check_performance("esp_http_client_bin_size", bin_size // 1024, dut1.TARGET)
# start test
dut1.start_app()
dut1.expect("Connected to AP, begin http example", timeout=30)
dut1.expect(re.compile(r"HTTP GET Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP POST Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP PUT Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP PATCH Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP DELETE Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP HEAD Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP Basic Auth Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP Basic Auth redirect Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP Digest Auth Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTPS Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP chunk encoding Status = 200, content_length = (-?\d)"))
# content-len for chunked encoding is typically -1, could be a positive length in some cases
dut1.expect(re.compile(r"HTTP Stream reader Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"Last esp error code: 0x8001"))
dut1.expect("Finish http example")
# test mbedtls dynamic resource
dut1 = env.get_dut("esp_http_client", "examples/protocols/esp_http_client", dut_class=ttfw_idf.ESP32DUT, app_config_name='ssldyn')
# check and log bin size
binary_file = os.path.join(dut1.app.binary_path, "esp-http-client-example.bin")
bin_size = os.path.getsize(binary_file)
ttfw_idf.log_performance("esp_http_client_bin_size", "{}KB".format(bin_size // 1024))
ttfw_idf.check_performance("esp_http_client_bin_size", bin_size // 1024, dut1.TARGET)
# start test
dut1.start_app()
dut1.expect("Connected to AP, begin http example", timeout=30)
dut1.expect(re.compile(r"HTTP GET Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP POST Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP PUT Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP PATCH Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP DELETE Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP HEAD Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP Basic Auth Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP Basic Auth redirect Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP Digest Auth Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTPS Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"HTTP chunk encoding Status = 200, content_length = (-?\d)"))
# content-len for chunked encoding is typically -1, could be a positive length in some cases
dut1.expect(re.compile(r"HTTP Stream reader Status = 200, content_length = (\d)"))
dut1.expect(re.compile(r"Last esp error code: 0x8001"))
dut1.expect("Finish http example")
if __name__ == '__main__':
test_examples_protocol_esp_http_client()

View File

@ -1,8 +1,6 @@
set(COMPONENT_SRCS "esp_http_client_example.c")
# Embed the server root certificate into the final binary
#
# (If this was a component, we would set COMPONENT_EMBED_TXTFILES here.)
set(COMPONENT_EMBED_TXTFILES howsmyssl_com_root_cert.pem)
register_component()
idf_component_register(SRCS "esp_http_client_example.c"
INCLUDE_DIRS "."
EMBED_TXTFILES howsmyssl_com_root_cert.pem)

View File

@ -9,20 +9,20 @@
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "protocol_examples_common.h"
#include "nvs.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "protocol_examples_common.h"
#include "esp_tls.h"
#include "esp_http_client.h"
#define MAX_HTTP_RECV_BUFFER 512
#define MAX_HTTP_OUTPUT_BUFFER 2048
static const char *TAG = "HTTP_CLIENT";
/* Root cert for howsmyssl.com, taken from howsmyssl_com_root_cert.pem
@ -40,6 +40,8 @@ extern const char howsmyssl_com_root_cert_pem_end[] asm("_binary_howsmyssl_com
esp_err_t _http_event_handler(esp_http_client_event_t *evt)
{
static char *output_buffer; // Buffer to store response of http request from event handler
static int output_len; // Stores number of bytes read
switch(evt->event_id) {
case HTTP_EVENT_ERROR:
ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
@ -55,27 +57,73 @@ esp_err_t _http_event_handler(esp_http_client_event_t *evt)
break;
case HTTP_EVENT_ON_DATA:
ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
/*
* Check for chunked encoding is added as the URL for chunked encoding used in this example returns binary data.
* However, event handler can also be used in case chunked encoding is used.
*/
if (!esp_http_client_is_chunked_response(evt->client)) {
// Write out data
// printf("%.*s", evt->data_len, (char*)evt->data);
// If user_data buffer is configured, copy the response into the buffer
if (evt->user_data) {
memcpy(evt->user_data + output_len, evt->data, evt->data_len);
} else {
if (output_buffer == NULL) {
output_buffer = (char *) malloc(esp_http_client_get_content_length(evt->client));
output_len = 0;
if (output_buffer == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for output buffer");
return ESP_FAIL;
}
}
memcpy(output_buffer + output_len, evt->data, evt->data_len);
}
output_len += evt->data_len;
}
break;
case HTTP_EVENT_ON_FINISH:
ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
if (output_buffer != NULL) {
// Response is accumulated in output_buffer. Uncomment the below line to print the accumulated response
// ESP_LOG_BUFFER_HEX(TAG, output_buffer, output_len);
free(output_buffer);
output_buffer = NULL;
output_len = 0;
}
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
int mbedtls_err = 0;
esp_err_t err = esp_tls_get_and_clear_last_error(evt->data, &mbedtls_err, NULL);
if (err != 0) {
if (output_buffer != NULL) {
free(output_buffer);
output_buffer = NULL;
output_len = 0;
}
ESP_LOGI(TAG, "Last esp error code: 0x%x", err);
ESP_LOGI(TAG, "Last mbedtls failure: 0x%x", mbedtls_err);
}
break;
}
return ESP_OK;
}
static void http_rest()
static void http_rest_with_url(void)
{
char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
/**
* NOTE: All the configuration parameters for http_client must be spefied either in URL or as host and path parameters.
* If host and path parameters are not set, query parameter will be ignored. In such cases,
* query parameter should be specified in URL.
*
* If URL as well as host and path parameters are specified, values of host and path will be considered.
*/
esp_http_client_config_t config = {
.url = "http://httpbin.org/get",
.host = "httpbin.org",
.path = "/get",
.query = "esp",
.event_handler = _http_event_handler,
.user_data = local_response_buffer, // Pass address of local buffer to get response
};
esp_http_client_handle_t client = esp_http_client_init(&config);
@ -86,13 +134,15 @@ static void http_rest()
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "HTTP GET request failed: %d", err);
ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err));
}
ESP_LOG_BUFFER_HEX(TAG, local_response_buffer, strlen(local_response_buffer));
// POST
const char *post_data = "field1=value1&field2=value2";
const char *post_data = "{\"field1\":\"value1\"}";
esp_http_client_set_url(client, "http://httpbin.org/post");
esp_http_client_set_method(client, HTTP_METHOD_POST);
esp_http_client_set_header(client, "Content-Type", "application/json");
esp_http_client_set_post_field(client, post_data, strlen(post_data));
err = esp_http_client_perform(client);
if (err == ESP_OK) {
@ -100,7 +150,7 @@ static void http_rest()
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "HTTP POST request failed: %d", err);
ESP_LOGE(TAG, "HTTP POST request failed: %s", esp_err_to_name(err));
}
//PUT
@ -112,7 +162,7 @@ static void http_rest()
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "HTTP PUT request failed: %d", err);
ESP_LOGE(TAG, "HTTP PUT request failed: %s", esp_err_to_name(err));
}
//PATCH
@ -125,7 +175,7 @@ static void http_rest()
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "HTTP PATCH request failed: %d", err);
ESP_LOGE(TAG, "HTTP PATCH request failed: %s", esp_err_to_name(err));
}
//DELETE
@ -137,7 +187,7 @@ static void http_rest()
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "HTTP DELETE request failed: %d", err);
ESP_LOGE(TAG, "HTTP DELETE request failed: %s", esp_err_to_name(err));
}
//HEAD
@ -149,13 +199,100 @@ static void http_rest()
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "HTTP HEAD request failed: %d", err);
ESP_LOGE(TAG, "HTTP HEAD request failed: %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
}
static void http_auth_basic()
static void http_rest_with_hostname_path(void)
{
esp_http_client_config_t config = {
.host = "httpbin.org",
.path = "/get",
.transport_type = HTTP_TRANSPORT_OVER_TCP,
.event_handler = _http_event_handler,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
// GET
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err));
}
// POST
const char *post_data = "field1=value1&field2=value2";
esp_http_client_set_url(client, "/post");
esp_http_client_set_method(client, HTTP_METHOD_POST);
esp_http_client_set_post_field(client, post_data, strlen(post_data));
err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTP POST Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "HTTP POST request failed: %s", esp_err_to_name(err));
}
//PUT
esp_http_client_set_url(client, "/put");
esp_http_client_set_method(client, HTTP_METHOD_PUT);
err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTP PUT Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "HTTP PUT request failed: %s", esp_err_to_name(err));
}
//PATCH
esp_http_client_set_url(client, "/patch");
esp_http_client_set_method(client, HTTP_METHOD_PATCH);
esp_http_client_set_post_field(client, NULL, 0);
err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTP PATCH Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "HTTP PATCH request failed: %s", esp_err_to_name(err));
}
//DELETE
esp_http_client_set_url(client, "/delete");
esp_http_client_set_method(client, HTTP_METHOD_DELETE);
err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTP DELETE Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "HTTP DELETE request failed: %s", esp_err_to_name(err));
}
//HEAD
esp_http_client_set_url(client, "/get");
esp_http_client_set_method(client, HTTP_METHOD_HEAD);
err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTP HEAD Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "HTTP HEAD request failed: %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
}
#if CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH
static void http_auth_basic(void)
{
esp_http_client_config_t config = {
.url = "http://user:passwd@httpbin.org/basic-auth/user/passwd",
@ -170,12 +307,12 @@ static void http_auth_basic()
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "Error perform http request %d", err);
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
}
static void http_auth_basic_redirect()
static void http_auth_basic_redirect(void)
{
esp_http_client_config_t config = {
.url = "http://user:passwd@httpbin.org/basic-auth/user/passwd",
@ -183,18 +320,19 @@ static void http_auth_basic_redirect()
};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTP Basic Auth redirect Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "Error perform http request %d", err);
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
}
#endif
static void http_auth_digest()
static void http_auth_digest(void)
{
esp_http_client_config_t config = {
.url = "http://user:passwd@httpbin.org/digest-auth/auth/user/passwd/MD5/never",
@ -202,17 +340,18 @@ static void http_auth_digest()
};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTP Digest Auth Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "Error perform http request %d", err);
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
}
static void https()
static void https_with_url(void)
{
esp_http_client_config_t config = {
.url = "https://www.howsmyssl.com",
@ -227,12 +366,34 @@ static void https()
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "Error perform http request %d", err);
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
}
static void http_relative_redirect()
static void https_with_hostname_path(void)
{
esp_http_client_config_t config = {
.host = "www.howsmyssl.com",
.path = "/",
.transport_type = HTTP_TRANSPORT_OVER_SSL,
.event_handler = _http_event_handler,
.cert_pem = howsmyssl_com_root_cert_pem_start,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
}
static void http_relative_redirect(void)
{
esp_http_client_config_t config = {
.url = "http://httpbin.org/relative-redirect/3",
@ -246,12 +407,12 @@ static void http_relative_redirect()
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "Error perform http request %d", err);
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
}
static void http_absolute_redirect()
static void http_absolute_redirect(void)
{
esp_http_client_config_t config = {
.url = "http://httpbin.org/absolute-redirect/3",
@ -265,12 +426,12 @@ static void http_absolute_redirect()
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "Error perform http request %d", err);
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
}
static void http_redirect_to_https()
static void http_redirect_to_https(void)
{
esp_http_client_config_t config = {
.url = "http://httpbin.org/redirect-to?url=https%3A%2F%2Fwww.howsmyssl.com",
@ -284,13 +445,13 @@ static void http_redirect_to_https()
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "Error perform http request %d", err);
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
}
static void http_download_chunk()
static void http_download_chunk(void)
{
esp_http_client_config_t config = {
.url = "http://httpbin.org/stream-bytes/8912",
@ -304,12 +465,12 @@ static void http_download_chunk()
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "Error perform http request %d", err);
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
}
static void http_perform_as_stream_reader()
static void http_perform_as_stream_reader(void)
{
char *buffer = malloc(MAX_HTTP_RECV_BUFFER + 1);
if (buffer == NULL) {
@ -318,12 +479,11 @@ static void http_perform_as_stream_reader()
}
esp_http_client_config_t config = {
.url = "http://httpbin.org/get",
.event_handler = _http_event_handler,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_err_t err;
if ((err = esp_http_client_open(client, 0)) != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection: %d", err);
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
free(buffer);
return;
}
@ -345,7 +505,7 @@ static void http_perform_as_stream_reader()
free(buffer);
}
static void https_async()
static void https_async(void)
{
esp_http_client_config_t config = {
.url = "https://postman-echo.com/post",
@ -372,37 +532,135 @@ static void https_async()
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "Error perform http request %d", err);
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
}
static void https_with_invalid_url(void)
{
esp_http_client_config_t config = {
.url = "https://not.existent.url",
.event_handler = _http_event_handler,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
} else {
ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
}
/*
* http_native_request() demonstrates use of low level APIs to connect to a server,
* make a http request and read response. Event handler is not used in this case.
* Note: This approach should only be used in case use of low level APIs is required.
* The easiest way is to use esp_http_perform()
*/
static void http_native_request(void)
{
char output_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0}; // Buffer to store response of http request
int content_length = 0;
esp_http_client_config_t config = {
.url = "http://httpbin.org/get",
};
esp_http_client_handle_t client = esp_http_client_init(&config);
// GET Request
esp_http_client_set_method(client, HTTP_METHOD_GET);
esp_err_t err = esp_http_client_open(client, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
} else {
content_length = esp_http_client_fetch_headers(client);
if (content_length < 0) {
ESP_LOGE(TAG, "HTTP client fetch headers failed");
} else {
int data_read = esp_http_client_read_response(client, output_buffer, MAX_HTTP_OUTPUT_BUFFER);
if (data_read >= 0) {
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
ESP_LOG_BUFFER_HEX(TAG, output_buffer, strlen(output_buffer));
} else {
ESP_LOGE(TAG, "Failed to read response");
}
}
}
esp_http_client_close(client);
// POST Request
const char *post_data = "{\"field1\":\"value1\"}";
esp_http_client_set_url(client, "http://httpbin.org/post");
esp_http_client_set_method(client, HTTP_METHOD_POST);
esp_http_client_set_header(client, "Content-Type", "application/json");
err = esp_http_client_open(client, strlen(post_data));
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
} else {
int wlen = esp_http_client_write(client, post_data, strlen(post_data));
if (wlen < 0) {
ESP_LOGE(TAG, "Write failed");
}
int data_read = esp_http_client_read_response(client, output_buffer, MAX_HTTP_OUTPUT_BUFFER);
if (data_read >= 0) {
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
ESP_LOG_BUFFER_HEX(TAG, output_buffer, strlen(output_buffer));
} else {
ESP_LOGE(TAG, "Failed to read response");
}
}
esp_http_client_cleanup(client);
}
static void http_test_task(void *pvParameters)
{
ESP_LOGI(TAG, "Connected to AP, begin http example");
http_rest();
http_rest_with_url();
http_rest_with_hostname_path();
#if CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH
http_auth_basic();
http_auth_basic_redirect();
#endif
http_auth_digest();
http_relative_redirect();
http_absolute_redirect();
https();
https_with_url();
https_with_hostname_path();
http_redirect_to_https();
http_download_chunk();
http_perform_as_stream_reader();
https_async();
https_with_invalid_url();
http_native_request();
ESP_LOGI(TAG, "Finish http example");
vTaskDelete(NULL);
}
void app_main()
void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
ESP_LOGI(TAG, "Connected to AP, begin http example");
xTaskCreate(&http_test_task, "http_test_task", 8192, NULL, 5, NULL);
}

View File

@ -0,0 +1,10 @@
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
CONFIG_EXAMPLE_ETH_PHY_IP101=y
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
CONFIG_EXAMPLE_CONNECT_IPV6=y
CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH=y

View File

@ -0,0 +1,13 @@
CONFIG_EXAMPLE_CONNECT_ETHERNET=y
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
CONFIG_EXAMPLE_ETH_PHY_IP101=y
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5
CONFIG_EXAMPLE_ETH_PHY_ADDR=1
CONFIG_EXAMPLE_CONNECT_IPV6=y
CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH=y
CONFIG_MBEDTLS_DYNAMIC_BUFFER=y
CONFIG_MBEDTLS_DYNAMIC_FREE_PEER_CERT=y
CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y

View File

@ -1,3 +0,0 @@
CONFIG_NEWLIB_LIBRARY_LEVEL_NORMAL=y
CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=1024
CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384