From ee32bd51a13cb8a71fb324b95bcbe9e4fdf0b024 Mon Sep 17 00:00:00 2001 From: Supreet Deshpande Date: Wed, 13 Feb 2019 14:55:43 +0530 Subject: [PATCH] feature/esp_http_client: Added the esp_http_client component from idf. Added the component and the example from IDF. --- components/esp_http_client/CMakeLists.txt | 11 + components/esp_http_client/Kconfig | 10 + components/esp_http_client/component.mk | 6 + components/esp_http_client/esp_http_client.c | 1185 +++++++++++++++++ .../esp_http_client/include/esp_http_client.h | 382 ++++++ components/esp_http_client/lib/http_auth.c | 151 +++ components/esp_http_client/lib/http_header.c | 239 ++++ components/esp_http_client/lib/http_utils.c | 125 ++ .../esp_http_client/lib/include/http_auth.h | 60 + .../esp_http_client/lib/include/http_header.h | 128 ++ .../esp_http_client/lib/include/http_utils.h | 86 ++ .../protocols/esp_http_client/CMakeLists.txt | 6 + examples/protocols/esp_http_client/Makefile | 9 + examples/protocols/esp_http_client/README.md | 3 + .../esp_http_client/esp_http_client_test.py | 52 + .../esp_http_client/main/CMakeLists.txt | 11 + .../esp_http_client/main/Kconfig.projbuild | 17 + .../protocols/esp_http_client/main/app_wifi.c | 75 ++ .../protocols/esp_http_client/main/app_wifi.h | 17 + .../esp_http_client/main/component.mk | 8 + .../main/esp_http_client_example.c | 407 ++++++ .../main/howsmyssl_com_root_cert.pem | 27 + 22 files changed, 3015 insertions(+) create mode 100644 components/esp_http_client/CMakeLists.txt create mode 100644 components/esp_http_client/Kconfig create mode 100644 components/esp_http_client/component.mk create mode 100644 components/esp_http_client/esp_http_client.c create mode 100644 components/esp_http_client/include/esp_http_client.h create mode 100644 components/esp_http_client/lib/http_auth.c create mode 100644 components/esp_http_client/lib/http_header.c create mode 100644 components/esp_http_client/lib/http_utils.c create mode 100644 components/esp_http_client/lib/include/http_auth.h create mode 100644 components/esp_http_client/lib/include/http_header.h create mode 100644 components/esp_http_client/lib/include/http_utils.h create mode 100644 examples/protocols/esp_http_client/CMakeLists.txt create mode 100644 examples/protocols/esp_http_client/Makefile create mode 100644 examples/protocols/esp_http_client/README.md create mode 100644 examples/protocols/esp_http_client/esp_http_client_test.py create mode 100644 examples/protocols/esp_http_client/main/CMakeLists.txt create mode 100644 examples/protocols/esp_http_client/main/Kconfig.projbuild create mode 100644 examples/protocols/esp_http_client/main/app_wifi.c create mode 100644 examples/protocols/esp_http_client/main/app_wifi.h create mode 100644 examples/protocols/esp_http_client/main/component.mk create mode 100644 examples/protocols/esp_http_client/main/esp_http_client_example.c create mode 100644 examples/protocols/esp_http_client/main/howsmyssl_com_root_cert.pem diff --git a/components/esp_http_client/CMakeLists.txt b/components/esp_http_client/CMakeLists.txt new file mode 100644 index 00000000..2bea7ce0 --- /dev/null +++ b/components/esp_http_client/CMakeLists.txt @@ -0,0 +1,11 @@ +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 "nghttp") +set(COMPONENT_PRIV_REQUIRES "mbedtls" "lwip" "esp-tls" "tcp_transport") + +register_component() diff --git a/components/esp_http_client/Kconfig b/components/esp_http_client/Kconfig new file mode 100644 index 00000000..f4e1d1b0 --- /dev/null +++ b/components/esp_http_client/Kconfig @@ -0,0 +1,10 @@ +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 + +endmenu diff --git a/components/esp_http_client/component.mk b/components/esp_http_client/component.mk new file mode 100644 index 00000000..96b5b6c4 --- /dev/null +++ b/components/esp_http_client/component.mk @@ -0,0 +1,6 @@ +# +# Component Makefile +# + +COMPONENT_SRCDIRS := . lib +COMPONENT_PRIV_INCLUDEDIRS := lib/include diff --git a/components/esp_http_client/esp_http_client.c b/components/esp_http_client/esp_http_client.c new file mode 100644 index 00000000..4a82a50e --- /dev/null +++ b/components/esp_http_client/esp_http_client.c @@ -0,0 +1,1185 @@ +// Copyright 2015-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 + +#include "esp_system.h" +#include "esp_log.h" + +#include "http_header.h" +#include "esp_transport.h" +#include "esp_transport_tcp.h" +#include "http_utils.h" +#include "http_auth.h" +#include "sdkconfig.h" +#include "esp_http_client.h" +#include "errno.h" + +#ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS +#include "esp_transport_ssl.h" +#endif + +static const char *TAG = "HTTP_CLIENT"; + +/** + * HTTP Buffer + */ +typedef struct { + char *data; /*!< The HTTP data received from the server */ + int len; /*!< The HTTP data len received from the server */ + char *raw_data; /*!< The HTTP data after decoding */ + int raw_len; /*!< The HTTP data len after decoding */ + char *output_ptr; /*!< The destination address of the data to be copied to after decoding */ +} esp_http_buffer_t; + +/** + * private HTTP Data structure + */ +typedef struct { + http_header_handle_t headers; /*!< http header */ + esp_http_buffer_t *buffer; /*!< data buffer as linked list */ + int status_code; /*!< status code (integer) */ + int content_length; /*!< data length */ + int data_offset; /*!< offset to http data (Skip header) */ + int data_process; /*!< data processed */ + int method; /*!< http method */ + bool is_chunked; +} esp_http_data_t; + +typedef struct { + char *url; + char *scheme; + char *host; + int port; + char *username; + char *password; + char *path; + char *query; + char *cert_pem; + esp_http_client_method_t method; + esp_http_client_auth_type_t auth_type; + esp_http_client_transport_t transport_type; + int max_store_header_size; +} connection_info_t; + +typedef enum { + HTTP_STATE_UNINIT = 0, + HTTP_STATE_INIT, + HTTP_STATE_CONNECTED, + HTTP_STATE_REQ_COMPLETE_HEADER, + HTTP_STATE_REQ_COMPLETE_DATA, + HTTP_STATE_RES_COMPLETE_HEADER, + HTTP_STATE_RES_COMPLETE_DATA, + HTTP_STATE_CLOSE +} esp_http_state_t; +/** + * HTTP client class + */ +struct esp_http_client { + int redirect_counter; + int max_redirection_count; + int process_again; + struct http_parser *parser; + struct http_parser_settings *parser_settings; + esp_transport_list_handle_t transport_list; + esp_transport_handle_t transport; + esp_http_data_t *request; + esp_http_data_t *response; + void *user_data; + esp_http_auth_data_t *auth_data; + char *post_data; + char *location; + char *auth_header; + char *current_header_key; + char *current_header_value; + int post_len; + connection_info_t connection_info; + bool is_chunk_complete; + esp_http_state_t state; + http_event_handle_cb event_handler; + int timeout_ms; + int buffer_size; + bool disable_auto_redirect; + esp_http_client_event_t event; + int data_written_index; + int data_write_left; + bool first_line_prepared; + int header_index; + bool is_async; +}; + +typedef struct esp_http_client esp_http_client_t; + +static esp_err_t _clear_connection_info(esp_http_client_handle_t client); +/** + * Default settings + */ +#define DEFAULT_HTTP_PORT (80) +#define DEFAULT_HTTPS_PORT (443) + +#define ASYNC_TRANS_CONNECT_FAIL -1 +#define ASYNC_TRANS_CONNECTING 0 +#define ASYNC_TRANS_CONNECT_PASS 1 + +static const char *DEFAULT_HTTP_USER_AGENT = "ESP32 HTTP Client/1.0"; +static const char *DEFAULT_HTTP_PROTOCOL = "HTTP/1.1"; +static const char *DEFAULT_HTTP_PATH = "/"; +static int DEFAULT_MAX_REDIRECT = 10; +static int DEFAULT_TIMEOUT_MS = 5000; + +static const char *HTTP_METHOD_MAPPING[] = { + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + "HEAD", + "NOTIFY", + "SUBSCRIBE", + "UNSUBSCRIBE", + "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); + +static esp_err_t http_dispatch_event(esp_http_client_t *client, esp_http_client_event_id_t event_id, void *data, int len) +{ + esp_http_client_event_t *event = &client->event; + + if (client->event_handler) { + event->event_id = event_id; + event->user_data = client->user_data; + event->data = data; + event->data_len = len; + return client->event_handler(event); + } + return ESP_OK; +} + +static int http_on_message_begin(http_parser *parser) +{ + esp_http_client_t *client = parser->data; + ESP_LOGD(TAG, "on_message_begin"); + + client->response->is_chunked = false; + client->is_chunk_complete = false; + return 0; +} + +static int http_on_url(http_parser *parser, const char *at, size_t length) +{ + ESP_LOGD(TAG, "http_on_url"); + return 0; +} + +static int http_on_status(http_parser *parser, const char *at, size_t length) +{ + return 0; +} + +static int http_on_header_field(http_parser *parser, const char *at, size_t length) +{ + esp_http_client_t *client = parser->data; + http_utils_assign_string(&client->current_header_key, at, length); + + return 0; +} + +static int http_on_header_value(http_parser *parser, const char *at, size_t length) +{ + esp_http_client_handle_t client = parser->data; + if (client->current_header_key == NULL) { + return 0; + } + if (strcasecmp(client->current_header_key, "Location") == 0) { + http_utils_assign_string(&client->location, at, length); + } else if (strcasecmp(client->current_header_key, "Transfer-Encoding") == 0 + && memcmp(at, "chunked", length) == 0) { + client->response->is_chunked = true; + } else if (strcasecmp(client->current_header_key, "WWW-Authenticate") == 0) { + http_utils_assign_string(&client->auth_header, at, length); + } + http_utils_assign_string(&client->current_header_value, at, length); + + ESP_LOGD(TAG, "HEADER=%s:%s", client->current_header_key, client->current_header_value); + client->event.header_key = client->current_header_key; + client->event.header_value = client->current_header_value; + http_dispatch_event(client, HTTP_EVENT_ON_HEADER, NULL, 0); + free(client->current_header_key); + free(client->current_header_value); + client->current_header_key = NULL; + client->current_header_value = NULL; + return 0; +} + +static int http_on_headers_complete(http_parser *parser) +{ + esp_http_client_handle_t client = parser->data; + client->response->status_code = parser->status_code; + client->response->data_offset = parser->nread; + client->response->content_length = parser->content_length; + client->response->data_process = 0; + ESP_LOGD(TAG, "http_on_headers_complete, status=%d, offset=%d, nread=%d", parser->status_code, client->response->data_offset, parser->nread); + client->state = HTTP_STATE_RES_COMPLETE_HEADER; + return 0; +} + +static int http_on_body(http_parser *parser, const char *at, size_t length) +{ + esp_http_client_t *client = parser->data; + ESP_LOGD(TAG, "http_on_body %d", length); + client->response->buffer->raw_data = (char *)at; + if (client->response->buffer->output_ptr) { + memcpy(client->response->buffer->output_ptr, (char *)at, length); + client->response->buffer->output_ptr += length; + } + + client->response->data_process += length; + client->response->buffer->raw_len += length; + http_dispatch_event(client, HTTP_EVENT_ON_DATA, (void *)at, length); + return 0; +} + +static int http_on_message_complete(http_parser *parser) +{ + ESP_LOGD(TAG, "http_on_message_complete, parser=%x", (int)parser); + esp_http_client_handle_t client = parser->data; + client->is_chunk_complete = true; + return 0; +} + +static int http_on_chunk_complete(http_parser *parser) +{ + ESP_LOGD(TAG, "http_on_chunk_complete"); + return 0; +} + +esp_err_t esp_http_client_set_header(esp_http_client_handle_t client, const char *key, const char *value) +{ + return http_header_set(client->request->headers, key, value); +} + +esp_err_t esp_http_client_get_header(esp_http_client_handle_t client, const char *key, char **value) +{ + return http_header_get(client->request->headers, key, value); +} + +esp_err_t esp_http_client_delete_header(esp_http_client_handle_t client, const char *key) +{ + return http_header_delete(client->request->headers, key); +} + +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; + client->connection_info.port = config->port; + client->connection_info.auth_type = config->auth_type; + client->event_handler = config->event_handler; + 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->disable_auto_redirect = config->disable_auto_redirect; + + if (config->buffer_size == 0) { + client->buffer_size = DEFAULT_HTTP_BUF_SIZE; + } + + if (client->max_redirection_count == 0) { + client->max_redirection_count = DEFAULT_MAX_REDIRECT; + } + + if (config->path) { + client->connection_info.path = strdup(config->path); + } else { + client->connection_info.path = strdup(DEFAULT_HTTP_PATH); + } + HTTP_MEM_CHECK(TAG, client->connection_info.path, { + return ESP_ERR_NO_MEM; + }); + + if (config->host) { + client->connection_info.host = strdup(config->host); + + HTTP_MEM_CHECK(TAG, client->connection_info.host, { + _clear_connection_info(client); + return ESP_ERR_NO_MEM; + }); + } + + if (config->query) { + client->connection_info.query = strdup(config->query); + HTTP_MEM_CHECK(TAG, client->connection_info.query, { + _clear_connection_info(client); + return ESP_ERR_NO_MEM; + }); + } + + if (config->username) { + client->connection_info.username = strdup(config->username); + HTTP_MEM_CHECK(TAG, client->connection_info.username, { + _clear_connection_info(client); + return ESP_ERR_NO_MEM; + }); + } + + if (config->password) { + client->connection_info.password = strdup(config->password); + HTTP_MEM_CHECK(TAG, client->connection_info.password, { + _clear_connection_info(client); + return ESP_ERR_NO_MEM; + }); + } + + if (config->transport_type == HTTP_TRANSPORT_OVER_SSL) { + http_utils_assign_string(&client->connection_info.scheme, "https", 0); + if (client->connection_info.port == 0) { + client->connection_info.port = DEFAULT_HTTPS_PORT; + } + } else { + http_utils_assign_string(&client->connection_info.scheme, "http", 0); + if (client->connection_info.port == 0) { + client->connection_info.port = DEFAULT_HTTP_PORT; + } + } + if (client->timeout_ms == 0) { + client->timeout_ms = DEFAULT_TIMEOUT_MS; + } + if (config->is_async) { + client->is_async = true; + } + + return ESP_OK; +} + +static esp_err_t _clear_connection_info(esp_http_client_handle_t client) +{ + free(client->connection_info.path); + free(client->connection_info.host); + free(client->connection_info.query); + free(client->connection_info.username); + if (client->connection_info.password) { + memset(client->connection_info.password, 0, strlen(client->connection_info.password)); + free(client->connection_info.password); + } + free(client->connection_info.scheme); + free(client->connection_info.url); + memset(&client->connection_info, 0, sizeof(connection_info_t)); + return ESP_OK; +} + +static esp_err_t _clear_auth_data(esp_http_client_handle_t client) +{ + if (client->auth_data == NULL) { + return ESP_FAIL; + } + + free(client->auth_data->method); + free(client->auth_data->realm); + free(client->auth_data->algorithm); + free(client->auth_data->qop); + free(client->auth_data->nonce); + free(client->auth_data->opaque); + memset(client->auth_data, 0, sizeof(esp_http_auth_data_t)); + return ESP_OK; +} + +static esp_err_t esp_http_client_prepare(esp_http_client_handle_t client) +{ + client->process_again = 0; + client->response->data_process = 0; + client->first_line_prepared = false; + http_parser_init(client->parser, HTTP_RESPONSE); + if (client->connection_info.username) { + char *auth_response = NULL; + + if (client->connection_info.auth_type == HTTP_AUTH_TYPE_BASIC) { + auth_response = http_auth_basic(client->connection_info.username, client->connection_info.password); + } else if (client->connection_info.auth_type == HTTP_AUTH_TYPE_DIGEST && client->auth_data) { + client->auth_data->uri = client->connection_info.path; + client->auth_data->cnonce = ((uint64_t)esp_random() << 32) + esp_random(); + auth_response = http_auth_digest(client->connection_info.username, client->connection_info.password, client->auth_data); + client->auth_data->nc ++; + } + + if (auth_response) { + ESP_LOGD(TAG, "auth_response=%s", auth_response); + esp_http_client_set_header(client, "Authorization", auth_response); + free(auth_response); + } + } + return ESP_OK; +} + +esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *config) +{ + + esp_http_client_handle_t client; + esp_transport_handle_t tcp; + bool _success; + + _success = ( + (client = calloc(1, sizeof(esp_http_client_t))) && + (client->parser = calloc(1, sizeof(struct http_parser))) && + (client->parser_settings = calloc(1, sizeof(struct http_parser_settings))) && + (client->auth_data = calloc(1, sizeof(esp_http_auth_data_t))) && + (client->request = calloc(1, sizeof(esp_http_data_t))) && + (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))) && + (client->response->headers = http_header_init()) && + (client->response->buffer = calloc(1, sizeof(esp_http_buffer_t))) + ); + + if (!_success) { + ESP_LOGE(TAG, "Error allocate memory"); + esp_http_client_cleanup(client); + return NULL; + } + + _success = ( + (client->transport_list = esp_transport_list_init()) && + (tcp = esp_transport_tcp_init()) && + (esp_transport_set_default_port(tcp, DEFAULT_HTTP_PORT) == ESP_OK) && + (esp_transport_list_add(client->transport_list, tcp, "http") == ESP_OK) + ); + if (!_success) { + ESP_LOGE(TAG, "Error initialize transport"); + esp_http_client_cleanup(client); + return NULL; + } +#ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS + esp_transport_handle_t ssl; + _success = ( + (ssl = esp_transport_ssl_init()) && + (esp_transport_set_default_port(ssl, DEFAULT_HTTPS_PORT) == ESP_OK) && + (esp_transport_list_add(client->transport_list, ssl, "https") == ESP_OK) + ); + + if (!_success) { + ESP_LOGE(TAG, "Error initialize SSL Transport"); + esp_http_client_cleanup(client); + return NULL; + } + + if (config->cert_pem) { + esp_transport_ssl_set_cert_data(ssl, config->cert_pem, strlen(config->cert_pem)); + } +#endif + + if (_set_config(client, config) != ESP_OK) { + ESP_LOGE(TAG, "Error set configurations"); + esp_http_client_cleanup(client); + return NULL; + } + _success = ( + (client->request->buffer->data = malloc(client->buffer_size)) && + (client->response->buffer->data = malloc(client->buffer_size)) + ); + + if (!_success) { + ESP_LOGE(TAG, "Allocation failed"); + esp_http_client_cleanup(client); + return 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 set default configurations"); + esp_http_client_cleanup(client); + return NULL; + } + + client->parser_settings->on_message_begin = http_on_message_begin; + client->parser_settings->on_url = http_on_url; + client->parser_settings->on_status = http_on_status; + client->parser_settings->on_header_field = http_on_header_field; + client->parser_settings->on_header_value = http_on_header_value; + client->parser_settings->on_headers_complete = http_on_headers_complete; + client->parser_settings->on_body = http_on_body; + client->parser_settings->on_message_complete = http_on_message_complete; + client->parser_settings->on_chunk_complete = http_on_chunk_complete; + client->parser->data = client; + client->event.client = client; + + client->state = HTTP_STATE_INIT; + return client; +} + +esp_err_t esp_http_client_cleanup(esp_http_client_handle_t client) +{ + if (client == NULL) { + return ESP_FAIL; + } + 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); + + free(client->parser); + free(client->parser_settings); + _clear_connection_info(client); + _clear_auth_data(client); + free(client->auth_data); + free(client->current_header_key); + free(client->location); + free(client->auth_header); + free(client); + return ESP_OK; +} + +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; + } + 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); + 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"); + } + } + return ESP_OK; +} + +esp_err_t esp_http_client_set_url(esp_http_client_handle_t client, const char *url) +{ + char *old_host = NULL; + struct http_parser_url purl; + int old_port; + + if (client == NULL || url == NULL) { + ESP_LOGE(TAG, "client or url must not NULL"); + return ESP_ERR_INVALID_ARG; + } + + http_parser_url_init(&purl); + + int parser_status = http_parser_parse_url(url, strlen(url), 0, &purl); + + if (parser_status != 0) { + ESP_LOGE(TAG, "Error parse url %s", url); + return ESP_ERR_INVALID_ARG; + } + old_host = 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); + } + // 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) { + return ESP_ERR_NO_MEM; + } + esp_http_client_close(client); + } + + 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); + + if (strcasecmp(client->connection_info.scheme, "http") == 0) { + client->connection_info.port = DEFAULT_HTTP_PORT; + } else if (strcasecmp(client->connection_info.scheme, "https") == 0) { + client->connection_info.port = DEFAULT_HTTPS_PORT; + } + } + + if (purl.field_data[UF_PORT].len) { + client->connection_info.port = strtol((const char*)(url + purl.field_data[UF_PORT].off), NULL, 10); + } + + if (old_port != client->connection_info.port) { + esp_http_client_close(client); + } + + if (purl.field_data[UF_USERINFO].len) { + char *user_info = NULL; + http_utils_assign_string(&user_info, url + purl.field_data[UF_USERINFO].off, purl.field_data[UF_USERINFO].len); + if (user_info) { + char *username = user_info; + char *password = strchr(user_info, ':'); + if (password) { + *password = 0; + password ++; + http_utils_assign_string(&client->connection_info.password, password, 0); + HTTP_MEM_CHECK(TAG, client->connection_info.password, return ESP_ERR_NO_MEM); + } + http_utils_assign_string(&client->connection_info.username, username, 0); + HTTP_MEM_CHECK(TAG, client->connection_info.username, return ESP_ERR_NO_MEM); + free(user_info); + } 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) { + http_utils_assign_string(&client->connection_info.path, url + purl.field_data[UF_PATH].off, purl.field_data[UF_PATH].len); + } else { + http_utils_assign_string(&client->connection_info.path, "/", 0); + } + HTTP_MEM_CHECK(TAG, client->connection_info.path, return ESP_ERR_NO_MEM); + + if (purl.field_data[UF_QUERY].len) { + http_utils_assign_string(&client->connection_info.query, url + purl.field_data[UF_QUERY].off, purl.field_data[UF_QUERY].len); + HTTP_MEM_CHECK(TAG, client->connection_info.query, return ESP_ERR_NO_MEM); + } else if (client->connection_info.query) { + free(client->connection_info.query); + client->connection_info.query = NULL; + } + + return ESP_OK; +} + +esp_err_t esp_http_client_set_method(esp_http_client_handle_t client, esp_http_client_method_t method) +{ + client->connection_info.method = method; + return ESP_OK; +} + +static int esp_http_client_get_data(esp_http_client_handle_t client) +{ + if (client->state < HTTP_STATE_RES_COMPLETE_HEADER) { + return ESP_FAIL; + } + + if (client->connection_info.method == HTTP_METHOD_HEAD) { + return 0; + } + + esp_http_buffer_t *res_buffer = client->response->buffer; + + 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); + if (rlen >= 0) { + http_parser_execute(client->parser, client->parser_settings, res_buffer->data, rlen); + } + return rlen; +} + +int esp_http_client_read(esp_http_client_handle_t client, char *buffer, int len) +{ + esp_http_buffer_t *res_buffer = client->response->buffer; + + int rlen = ESP_FAIL, ridx = 0; + if (res_buffer->raw_len) { + int remain_len = client->response->buffer->raw_len; + if (remain_len > len) { + remain_len = len; + } + memcpy(buffer, res_buffer->raw_data, remain_len); + res_buffer->raw_len -= remain_len; + res_buffer->raw_data += remain_len; + ridx = remain_len; + } + int need_read = len - ridx; + bool is_data_remain = true; + while (need_read > 0 && is_data_remain) { + if (client->response->is_chunked) { + is_data_remain = !client->is_chunk_complete; + } 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); + if (!is_data_remain) { + break; + } + int byte_to_read = need_read; + if (byte_to_read > client->buffer_size) { + byte_to_read = client->buffer_size; + } + 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; + } + res_buffer->output_ptr = buffer + ridx; + http_parser_execute(client->parser, client->parser_settings, res_buffer->data, rlen); + ridx += res_buffer->raw_len; + need_read -= res_buffer->raw_len; + + res_buffer->raw_len = 0; //clear + res_buffer->output_ptr = NULL; + } + + return ridx; +} + +esp_err_t esp_http_client_perform(esp_http_client_handle_t client) +{ + esp_err_t err; + do { + if (client->process_again) { + esp_http_client_prepare(client); + } + switch (client->state) { + /* In case of blocking esp_http_client_perform(), the following states will fall through one after the after; + in case of non-blocking esp_http_client_perform(), if there is an error condition, like EINPROGRESS or EAGAIN, + then the esp_http_client_perform() API will return ESP_ERR_HTTP_EAGAIN error. The user may call + esp_http_client_perform API again, and for this reason, we maintain the states */ + case HTTP_STATE_INIT: + if ((err = esp_http_client_connect(client)) != ESP_OK) { + if (client->is_async && err == ESP_ERR_HTTP_CONNECTING) { + return ESP_ERR_HTTP_EAGAIN; + } + return err; + } + /* falls through */ + case HTTP_STATE_CONNECTED: + if ((err = esp_http_client_request_send(client, client->post_len)) != ESP_OK) { + if (client->is_async && errno == EAGAIN) { + return ESP_ERR_HTTP_EAGAIN; + } + return err; + } + /* falls through */ + case HTTP_STATE_REQ_COMPLETE_HEADER: + if ((err = esp_http_client_send_post_data(client)) != ESP_OK) { + if (client->is_async && errno == EAGAIN) { + return ESP_ERR_HTTP_EAGAIN; + } + return err; + } + /* falls through */ + case HTTP_STATE_REQ_COMPLETE_DATA: + if (esp_http_client_fetch_headers(client) < 0) { + if (client->is_async && errno == EAGAIN) { + return ESP_ERR_HTTP_EAGAIN; + } + return ESP_ERR_HTTP_FETCH_HEADER; + } + /* falls through */ + case HTTP_STATE_RES_COMPLETE_HEADER: + if ((err = esp_http_check_response(client)) != ESP_OK) { + ESP_LOGE(TAG, "Error response"); + return err; + } + while (client->response->is_chunked && !client->is_chunk_complete) { + if (esp_http_client_get_data(client) <= 0) { + if (client->is_async && errno == EAGAIN) { + return ESP_ERR_HTTP_EAGAIN; + } + ESP_LOGD(TAG, "Read finish or server requests close"); + break; + } + } + while (client->response->data_process < client->response->content_length) { + if (esp_http_client_get_data(client) <= 0) { + if (client->is_async && errno == EAGAIN) { + return ESP_ERR_HTTP_EAGAIN; + } + ESP_LOGD(TAG, "Read finish or server requests close"); + break; + } + } + http_dispatch_event(client, HTTP_EVENT_ON_FINISH, NULL, 0); + + if (!http_should_keep_alive(client->parser)) { + ESP_LOGD(TAG, "Close connection"); + esp_http_client_close(client); + } else { + if (client->state > HTTP_STATE_CONNECTED) { + client->state = HTTP_STATE_CONNECTED; + client->first_line_prepared = false; + } + } + break; + default: + break; + } + } while (client->process_again); + return ESP_OK; +} + +int esp_http_client_fetch_headers(esp_http_client_handle_t client) +{ + if (client->state < HTTP_STATE_REQ_COMPLETE_HEADER) { + return ESP_FAIL; + } + + client->state = HTTP_STATE_REQ_COMPLETE_DATA; + esp_http_buffer_t *buffer = client->response->buffer; + 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) { + return ESP_FAIL; + } + http_parser_execute(client->parser, client->parser_settings, buffer->data, buffer->len); + } + ESP_LOGD(TAG, "content_length = %d", client->response->content_length); + if (client->response->content_length <= 0) { + client->response->is_chunked = true; + return 0; + } + return client->response->content_length; +} + +static esp_err_t esp_http_client_connect(esp_http_client_handle_t client) +{ + esp_err_t err; + + if (client->state == HTTP_STATE_UNINIT) { + ESP_LOGE(TAG, "Client has not been initialized"); + return ESP_ERR_INVALID_STATE; + } + + if ((err = esp_http_client_prepare(client)) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize request data"); + esp_http_client_close(client); + return err; + } + + if (client->state < HTTP_STATE_CONNECTED) { + ESP_LOGD(TAG, "Begin connect to: %s://%s:%d", client->connection_info.scheme, client->connection_info.host, client->connection_info.port); + client->transport = esp_transport_list_get_transport(client->transport_list, client->connection_info.scheme); + if (client->transport == NULL) { + ESP_LOGE(TAG, "No transport found"); +#ifndef CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS + if (strcasecmp(client->connection_info.scheme, "https") == 0) { + ESP_LOGE(TAG, "Please enable HTTPS at menuconfig to allow requesting via https"); + } +#endif + return ESP_ERR_HTTP_INVALID_TRANSPORT; + } + if (!client->is_async) { + if (esp_transport_connect(client->transport, client->connection_info.host, client->connection_info.port, client->timeout_ms) < 0) { + ESP_LOGE(TAG, "Connection failed, sock < 0"); + return ESP_ERR_HTTP_CONNECT; + } + } else { + int ret = esp_transport_connect_async(client->transport, client->connection_info.host, client->connection_info.port, client->timeout_ms); + if (ret == ASYNC_TRANS_CONNECT_FAIL) { + ESP_LOGE(TAG, "Connection failed"); + if (strcasecmp(client->connection_info.scheme, "http") == 0) { + ESP_LOGE(TAG, "Asynchronous mode doesn't work for HTTP based connection"); + return ESP_ERR_INVALID_ARG; + } + return ESP_ERR_HTTP_CONNECT; + } else if (ret == ASYNC_TRANS_CONNECTING) { + ESP_LOGD(TAG, "Connection not yet established"); + return ESP_ERR_HTTP_CONNECTING; + } + } + client->state = HTTP_STATE_CONNECTED; + http_dispatch_event(client, HTTP_EVENT_ON_CONNECTED, NULL, 0); + } + return ESP_OK; +} + +static int http_client_prepare_first_line(esp_http_client_handle_t client, int write_len) +{ + if (write_len >= 0) { + http_header_set_format(client->request->headers, "Content-Length", "%d", write_len); + } else { + esp_http_client_set_header(client, "Transfer-Encoding", "chunked"); + esp_http_client_set_method(client, HTTP_METHOD_POST); + } + + const char *method = HTTP_METHOD_MAPPING[client->connection_info.method]; + + int first_line_len = snprintf(client->request->buffer->data, + client->buffer_size, "%s %s", + method, + client->connection_info.path); + if (first_line_len >= client->buffer_size) { + 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) { + 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) { + ESP_LOGE(TAG, "Out of buffer"); + return -1; + } + return first_line_len; +} + +static esp_err_t esp_http_client_request_send(esp_http_client_handle_t client, int write_len) +{ + int first_line_len = 0; + if (!client->first_line_prepared) { + if ((first_line_len = http_client_prepare_first_line(client, write_len)) < 0) { + return first_line_len; + } + client->first_line_prepared = true; + client->header_index = 0; + client->data_written_index = 0; + client->data_write_left = 0; + } + + if (client->data_write_left > 0) { + /* sending leftover data from previous call to esp_http_client_request_send() API */ + int wret = 0; + if (((wret = esp_http_client_write(client, client->request->buffer->data + client->data_written_index, client->data_write_left)) < 0)) { + ESP_LOGE(TAG, "Error write request"); + return ESP_ERR_HTTP_WRITE_DATA; + } + client->data_write_left -= wret; + client->data_written_index += wret; + if (client->is_async && client->data_write_left > 0) { + return ESP_ERR_HTTP_WRITE_DATA; /* In case of EAGAIN error, we return ESP_ERR_HTTP_WRITE_DATA, + and the handling of EAGAIN should be done in the higher level APIs. */ + } + } + + int wlen = client->buffer_size - 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; + } + if (first_line_len) { + wlen += first_line_len; + first_line_len = 0; + } + client->request->buffer->data[wlen] = 0; + ESP_LOGD(TAG, "Write header[%d]: %s", client->header_index, client->request->buffer->data); + + client->data_write_left = wlen; + client->data_written_index = 0; + while (client->data_write_left > 0) { + int wret = esp_transport_write(client->transport, client->request->buffer->data + client->data_written_index, client->data_write_left, client->timeout_ms); + if (wret <= 0) { + ESP_LOGE(TAG, "Error write request"); + esp_http_client_close(client); + return ESP_ERR_HTTP_WRITE_DATA; + } + client->data_write_left -= wret; + client->data_written_index += wret; + } + wlen = client->buffer_size; + } + + client->data_written_index = 0; + client->data_write_left = client->post_len; + client->state = HTTP_STATE_REQ_COMPLETE_HEADER; + return ESP_OK; +} + +static esp_err_t esp_http_client_send_post_data(esp_http_client_handle_t client) +{ + if (client->state != HTTP_STATE_REQ_COMPLETE_HEADER) { + ESP_LOGE(TAG, "Invalid state"); + return ESP_ERR_INVALID_STATE; + } + if (!(client->post_data && client->post_len)) { + goto success; + } + + int wret = esp_http_client_write(client, client->post_data + client->data_written_index, client->data_write_left); + if (wret < 0) { + return wret; + } + client->data_write_left -= wret; + client->data_written_index += wret; + + if (client->data_write_left <= 0) { + goto success; + } else { + return ESP_ERR_HTTP_WRITE_DATA; + } + +success: + client->state = HTTP_STATE_REQ_COMPLETE_DATA; + return ESP_OK; +} + +esp_err_t esp_http_client_open(esp_http_client_handle_t client, int 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 ESP_OK; +} + +int esp_http_client_write(esp_http_client_handle_t client, const char *buffer, int len) +{ + if (client->state < HTTP_STATE_REQ_COMPLETE_HEADER) { + return ESP_FAIL; + } + + int wlen = 0, widx = 0; + while (len > 0) { + wlen = esp_transport_write(client->transport, buffer + widx, len, client->timeout_ms); + /* client->async_block is initialised in case of non-blocking IO, and in this case we return how + much ever data was written by the esp_transport_write() API. */ + if (client->is_async || wlen <= 0) { + return wlen; + } + widx += wlen; + len -= wlen; + } + return widx; +} + +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); + client->state = HTTP_STATE_INIT; + return esp_transport_close(client->transport); + } + return ESP_OK; +} + +esp_err_t esp_http_client_set_post_field(esp_http_client_handle_t client, const char *data, int len) +{ + esp_err_t err = ESP_OK; + client->post_data = (char *)data; + client->post_len = len; + ESP_LOGD(TAG, "set post file length = %d", len); + if (client->post_data) { + char *value = NULL; + if ((err = esp_http_client_get_header(client, "Content-Type", &value)) != ESP_OK) { + return err; + } + if (value == NULL) { + err = esp_http_client_set_header(client, "Content-Type", "application/x-www-form-urlencoded"); + } + } else { + client->post_len = 0; + err = esp_http_client_set_header(client, "Content-Type", NULL); + } + return err; +} + +int esp_http_client_get_post_field(esp_http_client_handle_t client, char **data) +{ + if (client->post_data) { + *data = client->post_data; + return client->post_len; + } + return 0; +} + +int esp_http_client_get_status_code(esp_http_client_handle_t client) +{ + return client->response->status_code; +} + +int esp_http_client_get_content_length(esp_http_client_handle_t client) +{ + return client->response->content_length; +} + +bool esp_http_client_is_chunked_response(esp_http_client_handle_t client) +{ + return client->response->is_chunked; +} + +esp_http_client_transport_t esp_http_client_get_transport_type(esp_http_client_handle_t client) +{ + if (!strcmp(client->connection_info.scheme, "https") ) { + return HTTP_TRANSPORT_OVER_SSL; + } else if (!strcmp(client->connection_info.scheme, "http")) { + return HTTP_TRANSPORT_OVER_TCP; + } else { + return HTTP_TRANSPORT_UNKNOWN; + } +} diff --git a/components/esp_http_client/include/esp_http_client.h b/components/esp_http_client/include/esp_http_client.h new file mode 100644 index 00000000..4e940a6d --- /dev/null +++ b/components/esp_http_client/include/esp_http_client.h @@ -0,0 +1,382 @@ +// Copyright 2015-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. + +#ifndef _ESP_HTTP_CLIENT_H +#define _ESP_HTTP_CLIENT_H + +#include "freertos/FreeRTOS.h" +#include "http_parser.h" +#include "sdkconfig.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#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; + +/** + * @brief HTTP Client events id + */ +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_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 */ + HTTP_EVENT_DISCONNECTED, /*!< The connection has been disconnected */ +} esp_http_client_event_id_t; + +/** + * @brief HTTP Client events data + */ +typedef struct esp_http_client_event { + esp_http_client_event_id_t event_id; /*!< event_id, to know the cause of the event */ + esp_http_client_handle_t client; /*!< esp_http_client_handle_t context */ + void *data; /*!< data of the event */ + int data_len; /*!< data length of data */ + void *user_data; /*!< user_data context, from esp_http_client_config_t user_data */ + char *header_key; /*!< For HTTP_EVENT_ON_HEADER event_id, it's store current http header key */ + char *header_value; /*!< For HTTP_EVENT_ON_HEADER event_id, it's store current http header value */ +} esp_http_client_event_t; + + +/** + * @brief HTTP Client transport + */ +typedef enum { + HTTP_TRANSPORT_UNKNOWN = 0x0, /*!< Unknown */ + HTTP_TRANSPORT_OVER_TCP, /*!< Transport over tcp */ + HTTP_TRANSPORT_OVER_SSL, /*!< Transport over ssl */ +} esp_http_client_transport_t; + +typedef esp_err_t (*http_event_handle_cb)(esp_http_client_event_t *evt); + +/** + * @brief HTTP method + */ +typedef enum { + HTTP_METHOD_GET = 0, /*!< HTTP GET Method */ + HTTP_METHOD_POST, /*!< HTTP POST Method */ + HTTP_METHOD_PUT, /*!< HTTP PUT Method */ + HTTP_METHOD_PATCH, /*!< HTTP PATCH Method */ + HTTP_METHOD_DELETE, /*!< HTTP DELETE Method */ + HTTP_METHOD_HEAD, /*!< HTTP HEAD Method */ + HTTP_METHOD_NOTIFY, /*!< HTTP NOTIFY Method */ + HTTP_METHOD_SUBSCRIBE, /*!< HTTP SUBSCRIBE Method */ + HTTP_METHOD_UNSUBSCRIBE,/*!< HTTP UNSUBSCRIBE Method */ + HTTP_METHOD_OPTIONS, /*!< HTTP OPTIONS Method */ + HTTP_METHOD_MAX, +} esp_http_client_method_t; + +/** + * @brief HTTP Authentication type + */ +typedef enum { + HTTP_AUTH_TYPE_NONE = 0, /*!< No authention */ + HTTP_AUTH_TYPE_BASIC, /*!< HTTP Basic authentication */ + HTTP_AUTH_TYPE_DIGEST, /*!< HTTP Disgest authentication */ +} esp_http_client_auth_type_t; + +/** + * @brief HTTP configuration + */ +typedef struct { + const char *url; /*!< HTTP URL, the information on the URL is most important, it overrides the other fields below, if any */ + const char *host; /*!< Domain or IP as string */ + int port; /*!< Port to connect, default depend on esp_http_client_transport_t (80 or 443) */ + const char *username; /*!< Using for Http authentication */ + const char *password; /*!< Using for Http authentication */ + 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 */ + 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) */ + void *user_data; /*!< HTTP user_data context */ + bool is_async; /*!< Set asynchronous mode, only supported with HTTPS for now */ +} esp_http_client_config_t; + + +#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 */ +#define ESP_ERR_HTTP_CONNECT (ESP_ERR_HTTP_BASE + 2) /*!< Error open the HTTP connection */ +#define ESP_ERR_HTTP_WRITE_DATA (ESP_ERR_HTTP_BASE + 3) /*!< Error write HTTP data */ +#define ESP_ERR_HTTP_FETCH_HEADER (ESP_ERR_HTTP_BASE + 4) /*!< Error read HTTP header from server */ +#define ESP_ERR_HTTP_INVALID_TRANSPORT (ESP_ERR_HTTP_BASE + 5) /*!< There are no transport support for the input scheme */ +#define ESP_ERR_HTTP_CONNECTING (ESP_ERR_HTTP_BASE + 6) /*!< HTTP connection hasn't been established yet */ +#define ESP_ERR_HTTP_EAGAIN (ESP_ERR_HTTP_BASE + 7) /*!< Mapping of errno EAGAIN to esp_err_t */ + +/** + * @brief Start a HTTP session + * This function must be the first function to call, + * and it returns a esp_http_client_handle_t that you must use as input to other functions in the interface. + * This call MUST have a corresponding call to esp_http_client_cleanup when the operation is complete. + * + * @param[in] config The configurations, see `http_client_config_t` + * + * @return + * - `esp_http_client_handle_t` + * - NULL if any errors + */ +esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *config); + +/** + * @brief Invoke this function after `esp_http_client_init` and all the options calls are made, and will perform the + * transfer as described in the options. It must be called with the same esp_http_client_handle_t as input as the esp_http_client_init call returned. + * esp_http_client_perform performs the entire request in either blocking or non-blocking manner. By default, the API performs request in a blocking manner and returns when done, + * or if it failed, and in non-blocking manner, it returns if EAGAIN/EWOULDBLOCK or EINPROGRESS is encountered, or if it failed. And in case of non-blocking request, + * the user may call this API multiple times unless request & response is complete or there is a failure. To enable non-blocking esp_http_client_perform(), `is_async` member of esp_http_client_config_t + * must be set while making a call to esp_http_client_init() API. + * You can do any amount of calls to esp_http_client_perform while using the same esp_http_client_handle_t. The underlying connection may be kept open if the server allows it. + * If you intend to transfer more than one file, you are even encouraged to do so. + * esp_http_client will then attempt to re-use the same connection for the following transfers, thus making the operations faster, less CPU intense and using less network resources. + * Just note that you will have to use `esp_http_client_set_**` between the invokes to set options for the following esp_http_client_perform. + * + * @note You must never call this function simultaneously from two places using the same client handle. + * Let the function return first before invoking it another time. + * If you want parallel transfers, you must use several esp_http_client_handle_t. + * This function include `esp_http_client_open` -> `esp_http_client_write` -> `esp_http_client_fetch_headers` -> `esp_http_client_read` (and option) `esp_http_client_close`. + * + * @param client The esp_http_client handle + * + * @return + * - ESP_OK on successful + * - ESP_FAIL on error + */ +esp_err_t esp_http_client_perform(esp_http_client_handle_t client); + +/** + * @brief Set URL for client, when performing this behavior, the options in the URL will replace the old ones + * + * @param[in] client The esp_http_client handle + * @param[in] url The url + * + * @return + * - ESP_OK + * - ESP_FAIL + */ +esp_err_t esp_http_client_set_url(esp_http_client_handle_t client, const char *url); + +/** + * @brief Set post data, this function must be called before `esp_http_client_perform`. + * Note: The data parameter passed to this function is a pointer and this function will not copy the data + * + * @param[in] client The esp_http_client handle + * @param[in] data post data pointer + * @param[in] len post length + * + * @return + * - ESP_OK + * - ESP_FAIL + */ +esp_err_t esp_http_client_set_post_field(esp_http_client_handle_t client, const char *data, int len); + +/** + * @brief Get current post field information + * + * @param[in] client The client + * @param[out] data Point to post data pointer + * + * @return Size of post data + */ +int esp_http_client_get_post_field(esp_http_client_handle_t client, char **data); + +/** + * @brief Set http request header, this function must be called after esp_http_client_init and before any + * perform function + * + * @param[in] client The esp_http_client handle + * @param[in] key The header key + * @param[in] value The header value + * + * @return + * - ESP_OK + * - ESP_FAIL + */ +esp_err_t esp_http_client_set_header(esp_http_client_handle_t client, const char *key, const char *value); + +/** + * @brief Get http request header. + * 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`. + * + * @param[in] client The esp_http_client handle + * @param[in] key The header key + * @param[out] value The header value + * + * @return + * - ESP_OK + * - ESP_FAIL + */ +esp_err_t esp_http_client_get_header(esp_http_client_handle_t client, const char *key, char **value); + +/** + * @brief Set http request method + * + * @param[in] client The esp_http_client handle + * @param[in] method The method + * + * @return ESP_OK + */ +esp_err_t esp_http_client_set_method(esp_http_client_handle_t client, esp_http_client_method_t method); + +/** + * @brief Delete http request header + * + * @param[in] client The esp_http_client handle + * @param[in] key The key + * + * @return + * - ESP_OK + * - ESP_FAIL + */ +esp_err_t esp_http_client_delete_header(esp_http_client_handle_t client, const char *key); + +/** + * @brief This function will be open the connection, write all header strings and return + * + * @param[in] client The esp_http_client handle + * @param[in] write_len HTTP Content length need to write to the server + * + * @return + * - ESP_OK + * - ESP_FAIL + */ +esp_err_t esp_http_client_open(esp_http_client_handle_t client, int write_len); + +/** + * @brief This function will write data to the HTTP connection previously opened by esp_http_client_open() + * + * @param[in] client The esp_http_client handle + * @param buffer The buffer + * @param[in] len This value must not be larger than the write_len parameter provided to esp_http_client_open() + * + * @return + * - (-1) if any errors + * - Length of data written + */ +int esp_http_client_write(esp_http_client_handle_t client, const char *buffer, int len); + +/** + * @brief This function need to call after esp_http_client_open, it will read from http stream, process all receive headers + * + * @param[in] client The esp_http_client handle + * + * @return + * - (0) if stream doesn't contain content-length header, or chunked encoding (checked by `esp_http_client_is_chunked` response) + * - (-1: ESP_FAIL) if any errors + * - Download data length defined by content-length header + */ +int esp_http_client_fetch_headers(esp_http_client_handle_t client); + + +/** + * @brief Check response data is chunked + * + * @param[in] client The esp_http_client handle + * + * @return true or false + */ +bool esp_http_client_is_chunked_response(esp_http_client_handle_t client); + +/** + * @brief Read data from http stream + * + * @param[in] client The esp_http_client handle + * @param buffer The buffer + * @param[in] len The length + * + * @return + * - (-1) if any errors + * - Length of data was read + */ +int esp_http_client_read(esp_http_client_handle_t client, char *buffer, int len); + + +/** + * @brief Get http response status code, the valid value if this function invoke after `esp_http_client_perform` + * + * @param[in] client The esp_http_client handle + * + * @return Status code + */ +int esp_http_client_get_status_code(esp_http_client_handle_t client); + +/** + * @brief Get http response content length (from header Content-Length) + * the valid value if this function invoke after `esp_http_client_perform` + * + * @param[in] client The esp_http_client handle + * + * @return + * - (-1) Chunked transfer + * - Content-Length value as bytes + */ +int esp_http_client_get_content_length(esp_http_client_handle_t client); + +/** + * @brief Close http connection, still kept all http request resources + * + * @param[in] client The esp_http_client handle + * + * @return + * - ESP_OK + * - ESP_FAIL + */ +esp_err_t esp_http_client_close(esp_http_client_handle_t client); + +/** + * @brief This function must be the last function to call for an session. + * It is the opposite of the esp_http_client_init function and must be called with the same handle as input that a esp_http_client_init call returned. + * This might close all connections this handle has used and possibly has kept open until now. + * Don't call this function if you intend to transfer more files, re-using handles is a key to good performance with esp_http_client. + * + * @param[in] client The esp_http_client handle + * + * @return + * - ESP_OK + * - ESP_FAIL + */ +esp_err_t esp_http_client_cleanup(esp_http_client_handle_t client); + +/** + * @brief Get transport type + * + * @param[in] client The esp_http_client handle + * + * @return + * - HTTP_TRANSPORT_UNKNOWN + * - HTTP_TRANSPORT_OVER_TCP + * - HTTP_TRANSPORT_OVER_SSL + */ +esp_http_client_transport_t esp_http_client_get_transport_type(esp_http_client_handle_t client); + + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/components/esp_http_client/lib/http_auth.c b/components/esp_http_client/lib/http_auth.c new file mode 100644 index 00000000..c406937c --- /dev/null +++ b/components/esp_http_client/lib/http_auth.c @@ -0,0 +1,151 @@ +// Copyright 2015-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 +#include +#include +#include + +#include "tcpip_adapter.h" +#include "lwip/sockets.h" +#include "rom/md5_hash.h" +#include "mbedtls/base64.h" + +#include "esp_system.h" +#include "esp_log.h" + +#include "http_utils.h" +#include "http_auth.h" + +#define MD5_MAX_LEN (33) +#define HTTP_AUTH_BUF_LEN (1024) + +static const char *TAG = "HTTP_AUTH"; + +/** + * @brief This function hash a formatted string with MD5 and format the result as ascii characters + * + * @param md The buffer will hold the ascii result + * @param[in] fmt The format + * + * @return Length of the result + */ +static int md5_printf(char *md, const char *fmt, ...) +{ + unsigned char *buf; + unsigned char digest[MD5_MAX_LEN]; + int len, i; + struct MD5Context md5_ctx; + va_list ap; + va_start(ap, fmt); + len = vasprintf((char **)&buf, fmt, ap); + if (buf == NULL) { + return ESP_FAIL; + } + + MD5Init(&md5_ctx); + MD5Update(&md5_ctx, buf, len); + MD5Final(digest, &md5_ctx); + + for (i = 0; i < 16; ++i) { + sprintf(&md[i * 2], "%02x", (unsigned int)digest[i]); + } + va_end(ap); + + free(buf); + return MD5_MAX_LEN; +} + +char *http_auth_digest(const char *username, const char *password, esp_http_auth_data_t *auth_data) +{ + char *ha1, *ha2 = NULL; + char *digest = NULL; + char *auth_str = NULL; + + if (username == NULL || + password == NULL || + auth_data->nonce == NULL || + auth_data->uri == NULL || + auth_data->realm == NULL) { + return NULL; + } + + ha1 = calloc(1, MD5_MAX_LEN); + HTTP_MEM_CHECK(TAG, ha1, goto _digest_exit); + + ha2 = calloc(1, MD5_MAX_LEN); + HTTP_MEM_CHECK(TAG, ha2, goto _digest_exit); + + digest = calloc(1, MD5_MAX_LEN); + HTTP_MEM_CHECK(TAG, digest, goto _digest_exit); + + if (md5_printf(ha1, "%s:%s:%s", username, auth_data->realm, password) <= 0) { + goto _digest_exit; + } + + ESP_LOGD(TAG, "%s %s %s %s\r\n", "Digest", username, auth_data->realm, password); + if (strcasecmp(auth_data->algorithm, "md5-sess") == 0) { + if (md5_printf(ha1, "%s:%s:%016llx", ha1, auth_data->nonce, auth_data->cnonce) <= 0) { + goto _digest_exit; + } + } + if (md5_printf(ha2, "%s:%s", auth_data->method, auth_data->uri) <= 0) { + goto _digest_exit; + } + + //support qop = auth + if (auth_data->qop && strcasecmp(auth_data->qop, "auth-int") == 0) { + if (md5_printf(ha2, "%s:%s", ha2, "entity") <= 0) { + goto _digest_exit; + } + } + + if (auth_data->qop) { + // response=MD5(HA1:nonce:nonceCount:cnonce:qop:HA2) + if (md5_printf(digest, "%s:%s:%08x:%016llx:%s:%s", ha1, auth_data->nonce, auth_data->nc, auth_data->cnonce, auth_data->qop, ha2) <= 0) { + goto _digest_exit; + } + } else { + // response=MD5(HA1:nonce:HA2) + if (md5_printf(digest, "%s:%s:%s", ha1, auth_data->nonce, ha2) <= 0) { + goto _digest_exit; + } + } + asprintf(&auth_str, "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", algorithm=\"MD5\", " + "response=\"%s\", opaque=\"%s\", qop=%s, nc=%08x, cnonce=\"%016llx\"", + username, auth_data->realm, auth_data->nonce, auth_data->uri, digest, auth_data->opaque, auth_data->qop, auth_data->nc, auth_data->cnonce); +_digest_exit: + free(ha1); + free(ha2); + free(digest); + return auth_str; +} + +char *http_auth_basic(const char *username, const char *password) +{ + int out; + char *user_info = NULL; + char *digest = NULL; + size_t n = 0; + asprintf(&user_info, "%s:%s", username, password); + HTTP_MEM_CHECK(TAG, user_info, return NULL); + 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 "); + 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; +} diff --git a/components/esp_http_client/lib/http_header.c b/components/esp_http_client/lib/http_header.c new file mode 100644 index 00000000..b771f6f6 --- /dev/null +++ b/components/esp_http_client/lib/http_header.c @@ -0,0 +1,239 @@ +// Copyright 2015-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 +#include +#include +#include +#include +#include "esp_log.h" +#include "http_header.h" +#include "http_utils.h" + +static const char *TAG = "HTTP_HEADER"; +#define HEADER_BUFFER (1024) + +/** + * dictionary item struct, with key-value pair + */ +typedef struct http_header_item { + char *key; /*!< key */ + char *value; /*!< value */ + STAILQ_ENTRY(http_header_item) next; /*!< Point to next entry */ +} http_header_item_t; + +STAILQ_HEAD(http_header, http_header_item); + + +http_header_handle_t http_header_init() +{ + http_header_handle_t header = calloc(1, sizeof(struct http_header)); + HTTP_MEM_CHECK(TAG, header, return NULL); + STAILQ_INIT(header); + return header; +} + +esp_err_t http_header_destroy(http_header_handle_t header) +{ + esp_err_t err = http_header_clean(header); + free(header); + return err; +} + +http_header_item_handle_t http_header_get_item(http_header_handle_t header, const char *key) +{ + http_header_item_handle_t item; + if (header == NULL || key == NULL) { + return NULL; + } + STAILQ_FOREACH(item, header, next) { + if (strcasecmp(item->key, key) == 0) { + return item; + } + } + return NULL; +} + +esp_err_t http_header_get(http_header_handle_t header, const char *key, char **value) +{ + http_header_item_handle_t item; + + item = http_header_get_item(header, key); + if (item) { + *value = item->value; + } else { + *value = NULL; + } + + return ESP_OK; +} + +static esp_err_t http_header_new_item(http_header_handle_t header, const char *key, const char *value) +{ + http_header_item_handle_t item; + + item = calloc(1, sizeof(http_header_item_t)); + HTTP_MEM_CHECK(TAG, item, return ESP_ERR_NO_MEM); + http_utils_assign_string(&item->key, key, 0); + HTTP_MEM_CHECK(TAG, item->key, goto _header_new_item_exit); + http_utils_trim_whitespace(&item->key); + http_utils_assign_string(&item->value, value, 0); + HTTP_MEM_CHECK(TAG, item->value, goto _header_new_item_exit); + http_utils_trim_whitespace(&item->value); + STAILQ_INSERT_TAIL(header, item, next); + return ESP_OK; +_header_new_item_exit: + free(item->key); + free(item->value); + return ESP_ERR_NO_MEM; +} + +esp_err_t http_header_set(http_header_handle_t header, const char *key, const char *value) +{ + http_header_item_handle_t item; + + if (value == NULL) { + return http_header_delete(header, key); + } + + item = http_header_get_item(header, key); + + if (item) { + free(item->value); + item->value = strdup(value); + http_utils_trim_whitespace(&item->value); + return ESP_OK; + } + return http_header_new_item(header, key, value); +} + +esp_err_t http_header_set_from_string(http_header_handle_t header, const char *key_value_data) +{ + char *eq_ch; + char *p_str; + + p_str = strdup(key_value_data); + HTTP_MEM_CHECK(TAG, p_str, return ESP_ERR_NO_MEM); + eq_ch = strchr(p_str, ':'); + if (eq_ch == NULL) { + free(p_str); + return ESP_ERR_INVALID_ARG; + } + *eq_ch = 0; + + http_header_set(header, p_str, eq_ch + 1); + free(p_str); + return ESP_OK; +} + + +esp_err_t http_header_delete(http_header_handle_t header, const char *key) +{ + http_header_item_handle_t item = http_header_get_item(header, key); + if (item) { + STAILQ_REMOVE(header, item, http_header_item, next); + free(item->key); + free(item->value); + free(item); + } else { + return ESP_ERR_NOT_FOUND; + } + return ESP_OK; +} + + +int http_header_set_format(http_header_handle_t header, const char *key, const char *format, ...) +{ + va_list argptr; + int len = 0; + char *buf = NULL; + va_start(argptr, format); + len = vasprintf(&buf, format, argptr); + HTTP_MEM_CHECK(TAG, buf, return 0); + va_end(argptr); + if (buf == NULL) { + return 0; + } + http_header_set(header, key, buf); + free(buf); + return len; +} + +int http_header_generate_string(http_header_handle_t header, int index, char *buffer, int *buffer_len) +{ + http_header_item_handle_t item; + int siz = 0; + int idx = 0; + int ret_idx = -1; + bool is_end = false; + STAILQ_FOREACH(item, header, next) { + if (item->value && idx >= index) { + siz += strlen(item->key); + siz += strlen(item->value); + siz += 4; //': ' and '\r\n' + } + idx ++; + + if (siz + 1 > *buffer_len - 2) { + ret_idx = idx - 1; + } + } + + if (siz == 0) { + return 0; + } + if (ret_idx < 0) { + ret_idx = idx; + is_end = true; + } + + int str_len = 0; + idx = 0; + STAILQ_FOREACH(item, header, next) { + if (item->value && idx >= index && idx < ret_idx) { + str_len += snprintf(buffer + str_len, *buffer_len - str_len, "%s: %s\r\n", item->key, item->value); + } + idx ++; + } + if (is_end) { + str_len += snprintf(buffer + str_len, *buffer_len - str_len, "\r\n"); + } + *buffer_len = str_len; + return ret_idx; +} + +esp_err_t http_header_clean(http_header_handle_t header) +{ + http_header_item_handle_t item = STAILQ_FIRST(header), tmp; + while (item != NULL) { + tmp = STAILQ_NEXT(item, next); + free(item->key); + free(item->value); + free(item); + item = tmp; + } + STAILQ_INIT(header); + return ESP_OK; +} + +int http_header_count(http_header_handle_t header) +{ + http_header_item_handle_t item; + int count = 0; + STAILQ_FOREACH(item, header, next) { + count ++; + } + return count; +} diff --git a/components/esp_http_client/lib/http_utils.c b/components/esp_http_client/lib/http_utils.c new file mode 100644 index 00000000..267e39e6 --- /dev/null +++ b/components/esp_http_client/lib/http_utils.c @@ -0,0 +1,125 @@ +// Copyright 2015-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 +#include +#include +#include +#include + +#include "http_utils.h" + +#ifndef mem_check +#define mem_check(x) assert(x) +#endif + +char *http_utils_join_string(const char *first_str, int len_first, const char *second_str, int len_second) +{ + int first_str_len = len_first > 0 ? len_first : strlen(first_str); + int second_str_len = len_second > 0 ? len_second : strlen(second_str); + char *ret = NULL; + if (first_str_len + second_str_len > 0) { + ret = calloc(1, first_str_len + second_str_len + 1); + mem_check(ret); + memcpy(ret, first_str, first_str_len); + memcpy(ret + first_str_len, second_str, second_str_len); + } + return ret; +} + +char *http_utils_assign_string(char **str, const char *new_str, int len) +{ + int l = len; + if (new_str == NULL) { + return NULL; + } + char *old_str = *str; + if (l <= 0) { + l = strlen(new_str); + } + if (old_str) { + old_str = realloc(old_str, l + 1); + mem_check(old_str); + old_str[l] = 0; + } else { + old_str = calloc(1, l + 1); + mem_check(old_str); + } + memcpy(old_str, new_str, l); + *str = old_str; + return old_str; +} + +void http_utils_trim_whitespace(char **str) +{ + char *end, *start; + if (str == NULL) { + return; + } + start = *str; + if (start == NULL) { + return; + } + // Trim leading space + while (isspace((unsigned char)*start)) start ++; + + if (*start == 0) { // All spaces? + **str = 0; + return; + } + + // Trim trailing space + end = (char *)(start + strlen(start) - 1); + while (end > start && isspace((unsigned char)*end)) { + end--; + } + + // Write new null terminator + *(end + 1) = 0; + memmove(*str, start, strlen(start) + 1); +} + +char *http_utils_get_string_between(const char *str, const char *begin, const char *end) +{ + char *found = strstr(str, begin); + char *ret = NULL; + if (found) { + found += strlen(begin); + char *found_end = strstr(found, end); + if (found_end) { + ret = calloc(1, found_end - found + 1); + mem_check(ret); + memcpy(ret, found, found_end - found); + return ret; + } + } + return NULL; +} + +int http_utils_str_starts_with(const char *str, const char *start) +{ + int i; + int match_str_len = strlen(str); + int start_len = strlen(start); + + if (start_len > match_str_len) { + return -1; + } + for (i = 0; i < start_len; i++) { + if (str[i] != start[i]) { + return 1; + } + } + return 0; +} diff --git a/components/esp_http_client/lib/include/http_auth.h b/components/esp_http_client/lib/include/http_auth.h new file mode 100644 index 00000000..8835666c --- /dev/null +++ b/components/esp_http_client/lib/include/http_auth.h @@ -0,0 +1,60 @@ +// Copyright 2015-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. + + +#ifndef _HTTP_BASIC_AUTH_H_ +#define _HTTP_BASIC_AUTH_H_ + +/** + * HTTP Digest authentication data + */ +typedef struct { + char *method; /*!< Request method, example: GET */ + char *algorithm; /*!< Authentication algorithm */ + char *uri; /*!< URI of request example: /path/to/file.html */ + char *realm; /*!< Authentication realm */ + char *nonce; /*!< Authentication nonce */ + char *qop; /*!< Authentication qop */ + char *opaque; /*!< Authentication opaque */ + uint64_t cnonce; /*!< Authentication cnonce */ + int nc; /*!< Authentication nc */ +} esp_http_auth_data_t; + +/** + * @brief This use for Http digest authentication method, create http header for digest authentication. + * The returned string need to free after use + * + * @param[in] username The username + * @param[in] password The password + * @param auth_data The auth data + * + * @return + * - HTTP Header value of Authorization, valid for digest authentication, must be freed after usage + * - NULL + */ +char *http_auth_digest(const char *username, const char *password, esp_http_auth_data_t *auth_data); + +/** + * @brief This use for Http basic authentication method, create header value for basic http authentication + * The returned string need to free after use + * + * @param[in] username The username + * @param[in] password The password + * + * @return + * - HTTP Header value of Authorization, valid for basic authentication, must be free after use + * - NULL + */ +char *http_auth_basic(const char *username, const char *password); +#endif diff --git a/components/esp_http_client/lib/include/http_header.h b/components/esp_http_client/lib/include/http_header.h new file mode 100644 index 00000000..7c680daf --- /dev/null +++ b/components/esp_http_client/lib/include/http_header.h @@ -0,0 +1,128 @@ +// Copyright 2015-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. + +#ifndef _HTTP_HEADER_H_ +#define _HTTP_HEADER_H_ + +#include "rom/queue.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct http_header *http_header_handle_t; +typedef struct http_header_item *http_header_item_handle_t; + +/** + * @brief initialize and allocate the memory for the header object + * + * @return + * - http_header_handle_t + * - NULL if any errors + */ +http_header_handle_t http_header_init(); + +/** + * @brief Cleanup and free all http header pairs + * + * @param[in] header The header + * + * @return + * - ESP_OK + * - ESP_FAIL + */ +esp_err_t http_header_clean(http_header_handle_t header); + +/** + * @brief Cleanup with http_header_clean and destroy http header handle object + * + * @param[in] header The header + * + * @return + * - ESP_OK + * - ESP_FAIL + */ +esp_err_t http_header_destroy(http_header_handle_t header); + +/** + * @brief Add a key-value pair of http header to the list, + * note that with value = NULL, this function will remove the header with `key` already exists in the list. + * + * @param[in] header The header + * @param[in] key The key + * @param[in] value The value + * + * @return + * - ESP_OK + * - ESP_FAIL + */ +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 + * + * @param[in] header The header + * @param[in] key The key + * @param[in] format The format + * @param[in] ... format parameters + * + * @return Total length of value + */ +int http_header_set_format(http_header_handle_t header, const char *key, const char *format, ...); + +/** + * @brief Get a value of header in header list + * The address of the value will be assign set to `value` parameter or NULL if no header with the key exists in the list + * + * @param[in] header The header + * @param[in] key The key + * @param[out] value The value + * + * @return + * - ESP_OK + * - ESP_FAIL + */ +esp_err_t http_header_get(http_header_handle_t header, const char *key, char **value); + +/** + * @brief Create HTTP header string from the header with index, output string to buffer with buffer_len + * Also return the last index of header was generated + * + * @param[in] header The header + * @param[in] index The index + * @param buffer The buffer + * @param buffer_len The buffer length + * + * @return The last index of header was generated + */ +int http_header_generate_string(http_header_handle_t header, int index, char *buffer, int *buffer_len); + +/** + * @brief Remove the header with key from the headers list + * + * @param[in] header The header + * @param[in] key The key + * + * @return + * - ESP_OK + * - ESP_FAIL + */ +esp_err_t http_header_delete(http_header_handle_t header, const char *key); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/esp_http_client/lib/include/http_utils.h b/components/esp_http_client/lib/include/http_utils.h new file mode 100644 index 00000000..d5784022 --- /dev/null +++ b/components/esp_http_client/lib/include/http_utils.h @@ -0,0 +1,86 @@ +// Copyright 2015-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. + + +#ifndef _HTTP_UTILS_H_ +#define _HTTP_UTILS_H_ +#include +#include "esp_transport_utils.h" +/** + * @brief Assign new_str to *str pointer, and realloc *str if it not NULL + * + * @param str pointer to string pointer + * @param new_str assign this tring to str + * @param len length of string, 0 if new_str is zero terminated + * + * @return + * - new_str pointer + * - NULL + */ +char *http_utils_assign_string(char **str, const char *new_str, int len); + +/** + * @brief Remove white space at begin and end of string + * + * @param[in] str The string + * + * @return New strings have been trimmed + */ +void http_utils_trim_whitespace(char **str); + +/** + * @brief Gets the string between 2 string. + * It will allocate a new memory space for this string, so you need to free it when no longer use + * + * @param[in] str The source string + * @param[in] begin The begin string + * @param[in] end The end string + * + * @return The string between begin and end + */ +char *http_utils_get_string_between(const char *str, const char *begin, const char *end); + +/** + * @brief Join 2 strings to one + * It will allocate a new memory space for this string, so you need to free it when no longer use + * + * @param[in] first_str The first string + * @param[in] len_first The length first + * @param[in] second_str The second string + * @param[in] len_second The length second + * + * @return + * - New string pointer + * - NULL: Invalid input + */ +char *http_utils_join_string(const char *first_str, int len_first, const char *second_str, int len_second); + +/** + * @brief Check if ``str`` is start with ``start`` + * + * @param[in] str The string + * @param[in] start The start + * + * @return + * - (-1) if length of ``start`` larger than length of ``str`` + * - (1) if ``start`` NOT starts with ``start`` + * - (0) if ``str`` starts with ``start`` + */ +int http_utils_str_starts_with(const char *str, const char *start); + + +#define HTTP_MEM_CHECK(TAG, a, action) ESP_TRANSPORT_MEM_CHECK(TAG, a, action) + + +#endif diff --git a/examples/protocols/esp_http_client/CMakeLists.txt b/examples/protocols/esp_http_client/CMakeLists.txt new file mode 100644 index 00000000..6896d5ac --- /dev/null +++ b/examples/protocols/esp_http_client/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(esp-http-client-example) diff --git a/examples/protocols/esp_http_client/Makefile b/examples/protocols/esp_http_client/Makefile new file mode 100644 index 00000000..851c3475 --- /dev/null +++ b/examples/protocols/esp_http_client/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := esp-http-client-example + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/protocols/esp_http_client/README.md b/examples/protocols/esp_http_client/README.md new file mode 100644 index 00000000..c4225fde --- /dev/null +++ b/examples/protocols/esp_http_client/README.md @@ -0,0 +1,3 @@ +# ESP HTTP Client Example + +See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/protocols/esp_http_client/esp_http_client_test.py b/examples/protocols/esp_http_client/esp_http_client_test.py new file mode 100644 index 00000000..063510a8 --- /dev/null +++ b/examples/protocols/esp_http_client/esp_http_client_test.py @@ -0,0 +1,52 @@ +import re +import os +import sys + +# this is a test case write with tiny-test-fw. +# to run test cases outside tiny-test-fw, +# we need to set environment variable `TEST_FW_PATH`, +# then get and insert `TEST_FW_PATH` to sys path before import FW module +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + +import TinyFW +import IDF + + +@IDF.idf_example_test(env_tag="Example_WIFI", ignore=True) +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") + # 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) + IDF.log_performance("esp_http_client_bin_size", "{}KB".format(bin_size//1024)) + IDF.check_performance("esp_http_client_bin_size", bin_size//1024) + # 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"HTTP Relative path redirect Status = 200, content_length = (\d)")) + dut1.expect(re.compile(r"HTTP Absolute path redirect Status = 200, content_length = (\d)")) + dut1.expect(re.compile(r"HTTPS Status = 200, content_length = (\d)")) + dut1.expect(re.compile(r"HTTP redirect to HTTPS Status = 200, content_length = (\d)"), timeout=10) + dut1.expect(re.compile(r"HTTP chunk encoding Status = 200, content_length = -1")) + dut1.expect(re.compile(r"HTTP Stream reader Status = 200, content_length = (\d)")) + dut1.expect("Finish http example") + + +if __name__ == '__main__': + test_examples_protocol_esp_http_client() diff --git a/examples/protocols/esp_http_client/main/CMakeLists.txt b/examples/protocols/esp_http_client/main/CMakeLists.txt new file mode 100644 index 00000000..093811e6 --- /dev/null +++ b/examples/protocols/esp_http_client/main/CMakeLists.txt @@ -0,0 +1,11 @@ +set(COMPONENT_SRCS "app_wifi.c" + "esp_http_client_example.c") +set(COMPONENT_ADD_INCLUDEDIRS ".") + + +# 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() diff --git a/examples/protocols/esp_http_client/main/Kconfig.projbuild b/examples/protocols/esp_http_client/main/Kconfig.projbuild new file mode 100644 index 00000000..1c7241da --- /dev/null +++ b/examples/protocols/esp_http_client/main/Kconfig.projbuild @@ -0,0 +1,17 @@ +menu "Example Configuration" + +config WIFI_SSID + string "WiFi SSID" + default "myssid" + help + SSID (network name) for the example to connect to. + +config WIFI_PASSWORD + string "WiFi Password" + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. + + Can be left blank if the network has no security set. + +endmenu diff --git a/examples/protocols/esp_http_client/main/app_wifi.c b/examples/protocols/esp_http_client/main/app_wifi.c new file mode 100644 index 00000000..da5ac44d --- /dev/null +++ b/examples/protocols/esp_http_client/main/app_wifi.c @@ -0,0 +1,75 @@ +/* ESP HTTP Client Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "esp_system.h" +#include "freertos/event_groups.h" +#include "esp_wifi.h" +#include "esp_event_loop.h" +#include "esp_log.h" +#include "app_wifi.h" + +static const char *TAG = "WIFI"; + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int CONNECTED_BIT = BIT0; + +static esp_err_t event_handler(void *ctx, system_event_t *event) +{ + switch (event->event_id) { + case SYSTEM_EVENT_STA_START: + esp_wifi_connect(); + break; + case SYSTEM_EVENT_STA_GOT_IP: + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + break; + case SYSTEM_EVENT_STA_DISCONNECTED: + /* This is a workaround as ESP32 WiFi libs don't currently + auto-reassociate. */ + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + break; + default: + break; + } + return ESP_OK; +} + +void app_wifi_initialise(void) +{ + tcpip_adapter_init(); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + wifi_config_t wifi_config = { + .sta = { + .ssid = CONFIG_WIFI_SSID, + .password = CONFIG_WIFI_PASSWORD, + }, + }; + ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config)); + ESP_ERROR_CHECK(esp_wifi_start()); + +} + +void app_wifi_wait_connected() +{ + xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY); +} diff --git a/examples/protocols/esp_http_client/main/app_wifi.h b/examples/protocols/esp_http_client/main/app_wifi.h new file mode 100644 index 00000000..91c886d5 --- /dev/null +++ b/examples/protocols/esp_http_client/main/app_wifi.h @@ -0,0 +1,17 @@ +/* ESP HTTP Client Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#ifndef _APP_WIFI_H_ +#define _APP_WIFI_H_ + +void app_wifi_initialise(void); +void app_wifi_wait_connected(); + + +#endif diff --git a/examples/protocols/esp_http_client/main/component.mk b/examples/protocols/esp_http_client/main/component.mk new file mode 100644 index 00000000..cb97ca08 --- /dev/null +++ b/examples/protocols/esp_http_client/main/component.mk @@ -0,0 +1,8 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + +# embed files from the "certs" directory as binary data symbols +# in the app +COMPONENT_EMBED_TXTFILES := howsmyssl_com_root_cert.pem diff --git a/examples/protocols/esp_http_client/main/esp_http_client_example.c b/examples/protocols/esp_http_client/main/esp_http_client_example.c new file mode 100644 index 00000000..ac5e1942 --- /dev/null +++ b/examples/protocols/esp_http_client/main/esp_http_client_example.c @@ -0,0 +1,407 @@ +/* ESP HTTP Client Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "app_wifi.h" + +#include "esp_http_client.h" + +#define MAX_HTTP_RECV_BUFFER 512 +static const char *TAG = "HTTP_CLIENT"; + +/* Root cert for howsmyssl.com, taken from howsmyssl_com_root_cert.pem + + The PEM file was extracted from the output of this command: + openssl s_client -showcerts -connect www.howsmyssl.com:443 event_id) { + case HTTP_EVENT_ERROR: + ESP_LOGD(TAG, "HTTP_EVENT_ERROR"); + break; + case HTTP_EVENT_ON_CONNECTED: + ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED"); + break; + case HTTP_EVENT_HEADER_SENT: + ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT"); + break; + case HTTP_EVENT_ON_HEADER: + ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value); + break; + case HTTP_EVENT_ON_DATA: + ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len); + if (!esp_http_client_is_chunked_response(evt->client)) { + // Write out data + // printf("%.*s", evt->data_len, (char*)evt->data); + } + + break; + case HTTP_EVENT_ON_FINISH: + ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH"); + break; + case HTTP_EVENT_DISCONNECTED: + ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED"); + break; + } + return ESP_OK; +} + +static void http_rest() +{ + 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); + + // 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, "http://httpbin.org/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, "http://httpbin.org/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, "http://httpbin.org/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, "http://httpbin.org/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, "http://httpbin.org/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); +} + +static void http_auth_basic() +{ + esp_http_client_config_t config = { + .url = "http://user:passwd@httpbin.org/basic-auth/user/passwd", + .event_handler = _http_event_handler, + .auth_type = HTTP_AUTH_TYPE_BASIC, + }; + 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 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_auth_basic_redirect() +{ + esp_http_client_config_t config = { + .url = "http://user:passwd@httpbin.org/basic-auth/user/passwd", + .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, "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 %s", esp_err_to_name(err)); + } + esp_http_client_cleanup(client); +} + +static void http_auth_digest() +{ + esp_http_client_config_t config = { + .url = "http://user:passwd@httpbin.org/digest-auth/auth/user/passwd/MD5/never", + .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, "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 %s", esp_err_to_name(err)); + } + esp_http_client_cleanup(client); +} + +static void https() +{ + esp_http_client_config_t config = { + .url = "https://www.howsmyssl.com", + .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() +{ + esp_http_client_config_t config = { + .url = "http://httpbin.org/relative-redirect/3", + .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, "HTTP Relative path 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 %s", esp_err_to_name(err)); + } + esp_http_client_cleanup(client); +} + +static void http_absolute_redirect() +{ + esp_http_client_config_t config = { + .url = "http://httpbin.org/absolute-redirect/3", + .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, "HTTP Absolute path 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 %s", esp_err_to_name(err)); + } + esp_http_client_cleanup(client); +} + +static void http_redirect_to_https() +{ + esp_http_client_config_t config = { + .url = "http://httpbin.org/redirect-to?url=https%3A%2F%2Fwww.howsmyssl.com", + .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, "HTTP redirect to 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_download_chunk() +{ + esp_http_client_config_t config = { + .url = "http://httpbin.org/stream-bytes/8912", + .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, "HTTP chunk encoding 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_perform_as_stream_reader() +{ + char *buffer = malloc(MAX_HTTP_RECV_BUFFER + 1); + if (buffer == NULL) { + ESP_LOGE(TAG, "Cannot malloc http receive buffer"); + return; + } + 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: %s", esp_err_to_name(err)); + free(buffer); + return; + } + int content_length = esp_http_client_fetch_headers(client); + int total_read_len = 0, read_len; + if (total_read_len < content_length && content_length <= MAX_HTTP_RECV_BUFFER) { + read_len = esp_http_client_read(client, buffer, content_length); + if (read_len <= 0) { + ESP_LOGE(TAG, "Error read data"); + } + buffer[read_len] = 0; + ESP_LOGD(TAG, "read_len = %d", read_len); + } + ESP_LOGI(TAG, "HTTP Stream reader Status = %d, content_length = %d", + esp_http_client_get_status_code(client), + esp_http_client_get_content_length(client)); + esp_http_client_close(client); + esp_http_client_cleanup(client); + free(buffer); +} + +static void https_async() +{ + esp_http_client_config_t config = { + .url = "https://postman-echo.com/post", + .event_handler = _http_event_handler, + .is_async = true, + .timeout_ms = 5000, + }; + esp_http_client_handle_t client = esp_http_client_init(&config); + esp_err_t err; + const char *post_data = "Using a Palantír requires a person with great strength of will and wisdom. The Palantíri were meant to " + "be used by the Dúnedain to communicate throughout the Realms in Exile. During the War of the Ring, " + "the Palantíri were used by many individuals. Sauron used the Ithil-stone to take advantage of the users " + "of the other two stones, the Orthanc-stone and Anor-stone, but was also susceptible to deception himself."; + esp_http_client_set_method(client, HTTP_METHOD_POST); + esp_http_client_set_post_field(client, post_data, strlen(post_data)); + while (1) { + err = esp_http_client_perform(client); + if (err != ESP_ERR_HTTP_EAGAIN) { + break; + } + } + 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_test_task(void *pvParameters) +{ + app_wifi_wait_connected(); + ESP_LOGI(TAG, "Connected to AP, begin http example"); + http_rest(); + http_auth_basic(); + http_auth_basic_redirect(); + http_auth_digest(); + http_relative_redirect(); + http_absolute_redirect(); + https(); + http_redirect_to_https(); + http_download_chunk(); + http_perform_as_stream_reader(); + https_async(); + ESP_LOGI(TAG, "Finish http example"); + vTaskDelete(NULL); +} + +void app_main() +{ + 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); + app_wifi_initialise(); + + xTaskCreate(&http_test_task, "http_test_task", 8192, NULL, 5, NULL); +} diff --git a/examples/protocols/esp_http_client/main/howsmyssl_com_root_cert.pem b/examples/protocols/esp_http_client/main/howsmyssl_com_root_cert.pem new file mode 100644 index 00000000..0002462c --- /dev/null +++ b/examples/protocols/esp_http_client/main/howsmyssl_com_root_cert.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE-----