]> granicus.if.org Git - esp-idf/commitdiff
Add esp_http_client
authorTuan PM <tuanpm@live.com>
Tue, 14 Nov 2017 03:16:20 +0000 (10:16 +0700)
committerTuan PM <tuanpm@live.com>
Mon, 21 May 2018 02:35:07 +0000 (09:35 +0700)
Add error handling for http client

set ssid password correct with Example_WIFI test, and clear password before free

Fixed the CI failure due to HTTP errror names

30 files changed:
components/esp32/esp_err_to_name.c
components/esp_http_client/Kconfig [new file with mode: 0644]
components/esp_http_client/component.mk [new file with mode: 0644]
components/esp_http_client/esp_http_client.c [new file with mode: 0644]
components/esp_http_client/include/esp_http_client.h [new file with mode: 0644]
components/esp_http_client/lib/http_auth.c [new file with mode: 0644]
components/esp_http_client/lib/http_header.c [new file with mode: 0644]
components/esp_http_client/lib/http_utils.c [new file with mode: 0644]
components/esp_http_client/lib/include/http_auth.h [new file with mode: 0644]
components/esp_http_client/lib/include/http_header.h [new file with mode: 0644]
components/esp_http_client/lib/include/http_utils.h [new file with mode: 0644]
components/esp_http_client/lib/include/transport.h [new file with mode: 0644]
components/esp_http_client/lib/include/transport_ssl.h [new file with mode: 0644]
components/esp_http_client/lib/include/transport_tcp.h [new file with mode: 0644]
components/esp_http_client/lib/transport.c [new file with mode: 0644]
components/esp_http_client/lib/transport_ssl.c [new file with mode: 0644]
components/esp_http_client/lib/transport_tcp.c [new file with mode: 0644]
docs/Doxyfile
docs/en/api-reference/protocols/esp_http_client.rst [new file with mode: 0644]
docs/en/api-reference/protocols/index.rst
docs/zh_CN/api-reference/protocols/esp_http_client.rst [new file with mode: 0644]
examples/protocols/esp_http_client/Makefile [new file with mode: 0644]
examples/protocols/esp_http_client/README.md [new file with mode: 0644]
examples/protocols/esp_http_client/esp_http_client_test.py [new file with mode: 0644]
examples/protocols/esp_http_client/main/Kconfig.projbuild [new file with mode: 0644]
examples/protocols/esp_http_client/main/app_wifi.c [new file with mode: 0644]
examples/protocols/esp_http_client/main/app_wifi.h [new file with mode: 0644]
examples/protocols/esp_http_client/main/component.mk [new file with mode: 0644]
examples/protocols/esp_http_client/main/esp_http_client_example.c [new file with mode: 0644]
examples/protocols/esp_http_client/main/howsmyssl_com_root_cert.pem [new file with mode: 0644]

index 6011689271b808ce2b02ce934089d8f1facc8b2c..b6743c31eaa5f51b6e731f122c8b47e6b713952b 100644 (file)
@@ -10,6 +10,9 @@
 #if __has_include("esp_err.h")
 #include "esp_err.h"
 #endif
+#if __has_include("esp_http_client.h")
+#include "esp_http_client.h"
+#endif
 #if __has_include("esp_image_format.h")
 #include "esp_image_format.h"
 #endif
@@ -397,6 +400,26 @@ static const esp_err_msg_t esp_err_msg_table[] = {
 #   endif
 #   ifdef      ESP_ERR_PING_NO_MEM
     ERR_TBL_IT(ESP_ERR_PING_NO_MEM),                        /* 24578 0x6002 */
+#   endif
+    // components/esp_http_client/include/esp_http_client.h
+#   ifdef      ESP_ERR_HTTP_BASE
+    ERR_TBL_IT(ESP_ERR_HTTP_BASE),                          /* 28672 0x7000 Starting number of HTTP error codes */
+#   endif
+#   ifdef      ESP_ERR_HTTP_MAX_REDIRECT
+    ERR_TBL_IT(ESP_ERR_HTTP_MAX_REDIRECT),                  /* 28673 0x7001 The error exceeds the number of HTTP redirects */
+#   endif
+#   ifdef      ESP_ERR_HTTP_CONNECT
+    ERR_TBL_IT(ESP_ERR_HTTP_CONNECT),                       /* 28674 0x7002 Error open the HTTP connection */
+#   endif
+#   ifdef      ESP_ERR_HTTP_WRITE_DATA
+    ERR_TBL_IT(ESP_ERR_HTTP_WRITE_DATA),                    /* 28675 0x7003 Error write HTTP data */
+#   endif
+#   ifdef      ESP_ERR_HTTP_FETCH_HEADER
+    ERR_TBL_IT(ESP_ERR_HTTP_FETCH_HEADER),                  /* 28676 0x7004 Error read HTTP header from server */
+#   endif
+#   ifdef      ESP_ERR_HTTP_INVALID_TRANSPORT
+    ERR_TBL_IT(ESP_ERR_HTTP_INVALID_TRANSPORT),             /* 28677 0x7005 There are no transport support for the input
+                                                                            scheme */
 #   endif
     // components/spi_flash/include/esp_spi_flash.h
 #   ifdef      ESP_ERR_FLASH_BASE
diff --git a/components/esp_http_client/Kconfig b/components/esp_http_client/Kconfig
new file mode 100644 (file)
index 0000000..f4e1d1b
--- /dev/null
@@ -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 (file)
index 0000000..96b5b6c
--- /dev/null
@@ -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 (file)
index 0000000..60d8857
--- /dev/null
@@ -0,0 +1,972 @@
+// 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 <string.h>
+
+#include "esp_system.h"
+#include "esp_log.h"
+
+#include "http_header.h"
+#include "transport.h"
+#include "transport_tcp.h"
+#include "http_utils.h"
+#include "http_auth.h"
+#include "sdkconfig.h"
+#include "transport.h"
+#include "esp_http_client.h"
+
+#ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS
+#include "transport_ssl.h"
+#endif
+
+static const char *TAG = "HTTP_CLIENT";
+
+typedef struct {
+    char *data;
+    int len;
+    char *raw_data;
+    int raw_len;
+} 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;
+    transport_list_handle_t     transport_list;
+    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;
+};
+
+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)
+
+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"
+};
+
+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;
+    client->response->buffer->raw_len = length;
+    client->response->data_process += 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_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, 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;
+    }
+
+    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;
+    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(esp_http_client_config_t *config)
+{
+
+    esp_http_client_handle_t client;
+    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 = transport_list_init()) &&
+                   (tcp = transport_tcp_init()) &&
+                   (transport_set_default_port(tcp, DEFAULT_HTTP_PORT) == ESP_OK) &&
+                   (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
+    transport_handle_t ssl;
+    _success = (
+                   (ssl = transport_ssl_init()) &&
+                   (transport_set_default_port(ssl, DEFAULT_HTTPS_PORT) == ESP_OK) &&
+                   (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) {
+        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);
+    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 301:
+        case 302:
+            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 401:
+            auth_header = client->auth_header;
+            http_utils_trim_whitespace(&auth_header);
+            ESP_LOGI(TAG, "UNAUTHORIZED: %s", auth_header);
+            client->redirect_counter ++;
+            if (auth_header) {
+                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, "Unsupport Auth Type");
+                    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;
+            }
+    }
+    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 -1;
+    }
+    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 = 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 = -1, 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 = 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;
+        }
+        http_parser_execute(client->parser, client->parser_settings, res_buffer->data, rlen);
+
+        if (res_buffer->raw_len) {
+            memcpy(buffer + ridx, res_buffer->raw_data, res_buffer->raw_len);
+            ridx += res_buffer->raw_len;
+            need_read -= res_buffer->raw_len;
+        }
+        res_buffer->raw_len = 0; //clear
+    }
+
+    return ridx;
+}
+
+esp_err_t esp_http_client_perform(esp_http_client_handle_t client)
+{
+    esp_err_t err;
+    do {
+        if ((err = esp_http_client_open(client, client->post_len)) != ESP_OK) {
+            return err;
+        }
+        if (client->post_data && client->post_len) {
+            if (esp_http_client_write(client, client->post_data, client->post_len) <= 0) {
+                ESP_LOGE(TAG, "Error upload data");
+                return ESP_ERR_HTTP_WRITE_DATA;
+            }
+        }
+        if (esp_http_client_fetch_headers(client) < 0) {
+            return ESP_ERR_HTTP_FETCH_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) {
+                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) {
+                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;
+            }
+        }
+    } 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 -1;
+    }
+
+    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 = transport_read(client->transport, buffer->data, client->buffer_size, client->timeout_ms);
+        if (buffer->len <= 0) {
+            return -1;
+        }
+        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;
+}
+
+esp_err_t esp_http_client_open(esp_http_client_handle_t client, int write_len)
+{
+    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 = transport_list_get_transport(client->transport_list, client->connection_info.scheme);
+        if (client->transport == NULL) {
+            ESP_LOGE(TAG, "No transport found");
+            return ESP_ERR_HTTP_INVALID_TRANSPORT;
+        }
+        if (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;
+        }
+        http_dispatch_event(client, HTTP_EVENT_ON_CONNECTED, NULL, 0);
+        client->state = HTTP_STATE_CONNECTED;
+    }
+
+    if (write_len >= 0) {
+        http_header_set_format(client->request->headers, "Content-Length", "%d", write_len);
+    } else if (write_len < 0) {
+        esp_http_client_set_header(client, "Transfer-Encoding", "chunked");
+        esp_http_client_set_method(client, HTTP_METHOD_POST);
+    }
+
+    int header_index = 0;
+    int wlen = client->buffer_size;
+
+    const char *method = HTTP_METHOD_MAPPING[client->connection_info.method];
+
+    int first_line = snprintf(client->request->buffer->data,
+                              client->buffer_size, "%s %s",
+                              method,
+                              client->connection_info.path);
+    if (first_line > client->buffer_size) {
+        ESP_LOGE(TAG, "Out of buffer");
+        return ESP_ERR_HTTP_CONNECT;
+    }
+
+    if (client->connection_info.query) {
+        first_line += snprintf(client->request->buffer->data + first_line,
+                               client->buffer_size - first_line, "?%s", client->connection_info.query);
+        if (first_line > client->buffer_size) {
+            ESP_LOGE(TAG, "Out of buffer");
+            return ESP_ERR_HTTP_CONNECT;
+        }
+    }
+    first_line += snprintf(client->request->buffer->data + first_line,
+                           client->buffer_size - first_line, " %s\r\n", DEFAULT_HTTP_PROTOCOL);
+    if (first_line > client->buffer_size) {
+        ESP_LOGE(TAG, "Out of buffer");
+        return ESP_ERR_HTTP_CONNECT;
+    }
+    wlen -= first_line;
+
+    while ((header_index = http_header_generate_string(client->request->headers, header_index, client->request->buffer->data + first_line, &wlen))) {
+        if (wlen <= 0) {
+            break;
+        }
+        if (first_line) {
+            wlen += first_line;
+            first_line = 0;
+        }
+        client->request->buffer->data[wlen] = 0;
+        ESP_LOGD(TAG, "Write header[%d]: %s", header_index, client->request->buffer->data);
+        if (transport_write(client->transport, client->request->buffer->data, wlen, client->timeout_ms) <= 0) {
+            ESP_LOGE(TAG, "Error write request");
+            esp_http_client_close(client);
+            return ESP_ERR_HTTP_WRITE_DATA;
+        }
+        wlen = client->buffer_size;
+    }
+    client->state = HTTP_STATE_REQ_COMPLETE_HEADER;
+    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 -1;
+    }
+    int need_write;
+    int wlen = 0, widx = 0;
+    while (len > 0) {
+        need_write = len;
+        if (need_write > client->buffer_size) {
+            need_write = client->buffer_size;
+        }
+        wlen = transport_write(client->transport, buffer + widx, need_write, client->timeout_ms);
+        if (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 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) {
+        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;
+}
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 (file)
index 0000000..2690cfe
--- /dev/null
@@ -0,0 +1,343 @@
+// 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_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 */
+} 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 */
+
+/**
+ * @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(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 a blocking manner and returns when done, or if it failed.
+ *             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_finalize_open` or 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      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
+ *     - (-1) if stream doesn't contain content-length header, or chunked encoding (checked by `esp_http_client_is_chunked` response)
+ *     - 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, must call after `esp_http_client_finalize_open`
+ *
+ * @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` or `esp_http_client_finalize_open`
+ *
+ * @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` or `esp_http_client_finalize_open`
+ *
+ * @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);
+
+
+
+#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 (file)
index 0000000..4e07ce5
--- /dev/null
@@ -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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#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 -1;
+    }
+
+    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 = calloc(1, MD5_MAX_LEN + 7);
+    HTTP_MEM_CHECK(TAG, digest, goto _basic_exit);
+    asprintf(&user_info, "%s:%s", username, password);
+    HTTP_MEM_CHECK(TAG, user_info, goto _basic_exit);
+    if (user_info == NULL) {
+        goto _basic_exit;
+    }
+    strcpy(digest, "Basic ");
+    mbedtls_base64_encode((unsigned char *)digest + 6, MD5_MAX_LEN, (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 (file)
index 0000000..b771f6f
--- /dev/null
@@ -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 <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdarg.h>
+#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 (file)
index 0000000..2ccc501
--- /dev/null
@@ -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 <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+
+#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;
+    char *start = *str;
+    // 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;
+}
+
+void http_utils_ms_to_timeval(int timeout_ms, struct timeval *tv)
+{
+    tv->tv_sec = timeout_ms / 1000;
+    tv->tv_usec = (timeout_ms - (tv->tv_sec * 1000)) * 1000;
+}
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 (file)
index 0000000..8835666
--- /dev/null
@@ -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 (file)
index 0000000..7c680da
--- /dev/null
@@ -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 (file)
index 0000000..9212cac
--- /dev/null
@@ -0,0 +1,95 @@
+// 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 <sys/time.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);
+
+/**
+ * @brief      Convert milliseconds to timeval struct
+ *
+ * @param[in]  timeout_ms  The timeout milliseconds
+ * @param[out] tv          Timeval struct
+ */
+void http_utils_ms_to_timeval(int timeout_ms, struct timeval *tv);
+
+#define HTTP_MEM_CHECK(TAG, a, action) if (!(a)) {                                                      \
+        ESP_LOGE(TAG,"%s:%d (%s): %s", __FILE__, __LINE__, __FUNCTION__, "Memory exhausted");       \
+        action;                                                                                         \
+        }
+
+
+#endif
diff --git a/components/esp_http_client/lib/include/transport.h b/components/esp_http_client/lib/include/transport.h
new file mode 100644 (file)
index 0000000..0509d77
--- /dev/null
@@ -0,0 +1,251 @@
+// 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 _TRANSPORT_H_
+#define _TRANSPORT_H_
+
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+typedef struct transport_list_t* transport_list_handle_t;
+typedef struct transport_item_t* transport_handle_t;
+
+typedef int (*connect_func)(transport_handle_t t, const char *host, int port, int timeout_ms);
+typedef int (*io_func)(transport_handle_t t, const char *buffer, int len, int timeout_ms);
+typedef int (*io_read_func)(transport_handle_t t, char *buffer, int len, int timeout_ms);
+typedef int (*trans_func)(transport_handle_t t);
+typedef int (*poll_func)(transport_handle_t t, int timeout_ms);
+
+/**
+ * @brief      Create transport list
+ *
+ * @return     A handle can hold all transports
+ */
+transport_list_handle_t transport_list_init();
+
+/**
+ * @brief      Cleanup and free all transports, include itself,
+ *             this function will invoke transport_destroy of every transport have added this the list
+ *
+ * @param[in]  list  The list
+ *
+ * @return
+ *     - ESP_OK
+ *     - ESP_FAIL
+ */
+esp_err_t transport_list_destroy(transport_list_handle_t list);
+
+/**
+ * @brief      Add a transport to the list, and define a scheme to indentify this transport in the list
+ *
+ * @param[in]  list    The list
+ * @param[in]  t       The Transport
+ * @param[in]  scheme  The scheme
+ *
+ * @return
+ *     - ESP_OK
+ */
+esp_err_t transport_list_add(transport_list_handle_t list, transport_handle_t t, const char *scheme);
+
+/**
+ * @brief      This function will remove all transport from the list,
+ *             invoke transport_destroy of every transport have added this the list
+ *
+ * @param[in]  list  The list
+ *
+ * @return
+ *     - ESP_OK
+ *     - ESP_ERR_INVALID_ARG
+ */
+esp_err_t transport_list_clean(transport_list_handle_t list);
+
+/**
+ * @brief      Get the transport by scheme, which has been defined when calling function `transport_list_add`
+ *
+ * @param[in]  list  The list
+ * @param[in]  tag   The tag
+ *
+ * @return     The transport handle
+ */
+transport_handle_t transport_list_get_transport(transport_list_handle_t list, const char *scheme);
+
+/**
+ * @brief      Initialize a transport handle object
+ *
+ * @return     The transport handle
+ */
+transport_handle_t transport_init();
+
+/**
+ * @brief      Cleanup and free memory the transport
+ *
+ * @param[in]  t     The transport handle
+ *
+ * @return
+ *     - ESP_OK
+ *     - ESP_FAIL
+ */
+esp_err_t transport_destroy(transport_handle_t t);
+
+/**
+ * @brief      Get default port number used by this transport
+ *
+ * @param[in]  t     The transport handle
+ *
+ * @return     the port number
+ */
+int transport_get_default_port(transport_handle_t t);
+
+/**
+ * @brief      Set default port number that can be used by this transport
+ *
+ * @param[in]  t     The transport handle
+ * @param[in]  port  The port number
+ *
+ * @return
+ *     - ESP_OK
+ *     - ESP_FAIL
+ */
+esp_err_t transport_set_default_port(transport_handle_t t, int port);
+
+/**
+ * @brief      Transport connection function, to make a connection to server
+ *
+ * @param      t           The transport handle
+ * @param[in]  host        Hostname
+ * @param[in]  port        Port
+ * @param[in]  timeout_ms  The timeout milliseconds
+ *
+ * @return
+ * - socket for will use by this transport
+ * - (-1) if there are any errors, should check errno
+ */
+int transport_connect(transport_handle_t t, const char *host, int port, int timeout_ms);
+
+/**
+ * @brief      Transport read function
+ *
+ * @param      t           The transport handle
+ * @param      buffer      The buffer
+ * @param[in]  len         The length
+ * @param[in]  timeout_ms  The timeout milliseconds
+ *
+ * @return
+ *  - Number of bytes was read
+ *  - (-1) if there are any errors, should check errno
+ */
+int transport_read(transport_handle_t t, char *buffer, int len, int timeout_ms);
+
+/**
+ * @brief      Poll the transport until readable or timeout
+ *
+ * @param[in]  t           The transport handle
+ * @param[in]  timeout_ms  The timeout milliseconds
+ *
+ * @return
+ *     - 0      Timeout
+ *     - (-1)   If there are any errors, should check errno
+ *     - other  The transport can read
+ */
+int transport_poll_read(transport_handle_t t, int timeout_ms);
+
+/**
+ * @brief      Transport write function
+ *
+ * @param      t           The transport handle
+ * @param      buffer      The buffer
+ * @param[in]  len         The length
+ * @param[in]  timeout_ms  The timeout milliseconds
+ *
+ * @return
+ *  - Number of bytes was written
+ *  - (-1) if there are any errors, should check errno
+ */
+int transport_write(transport_handle_t t, const char *buffer, int len, int timeout_ms);
+
+/**
+ * @brief      Poll the transport until writeable or timeout
+ *
+ * @param[in]  t           The transport handle
+ * @param[in]  timeout_ms  The timeout milliseconds
+ *
+ * @return
+ *     - 0      Timeout
+ *     - (-1)   If there are any errors, should check errno
+ *     - other  The transport can write
+ */
+int transport_poll_write(transport_handle_t t, int timeout_ms);
+
+/**
+ * @brief      Transport close
+ *
+ * @param      t     The transport handle
+ *
+ * @return
+ * - 0 if ok
+ * - (-1) if there are any errors, should check errno
+ */
+int transport_close(transport_handle_t t);
+
+/**
+ * @brief      Get user data context of this transport
+ *
+ * @param[in]  t        The transport handle
+ *
+ * @return     The user data context
+ */
+void *transport_get_context_data(transport_handle_t t);
+
+/**
+ * @brief      Set the user context data for this transport
+ *
+ * @param[in]  t        The transport handle
+ * @param      data     The user data context
+ *
+ * @return
+ *     - ESP_OK
+ */
+esp_err_t transport_set_context_data(transport_handle_t t, void *data);
+
+/**
+ * @brief      Set transport functions for the transport handle
+ *
+ * @param[in]  t            The transport handle
+ * @param[in]  _connect     The connect function pointer
+ * @param[in]  _read        The read function pointer
+ * @param[in]  _write       The write function pointer
+ * @param[in]  _close       The close function pointer
+ * @param[in]  _poll_read   The poll read function pointer
+ * @param[in]  _poll_write  The poll write function pointer
+ * @param[in]  _destroy     The destroy function pointer
+ *
+ * @return
+ *     - ESP_OK
+ */
+esp_err_t transport_set_func(transport_handle_t t,
+                             connect_func _connect,
+                             io_read_func _read,
+                             io_func _write,
+                             trans_func _close,
+                             poll_func _poll_read,
+                             poll_func _poll_write,
+                             trans_func _destroy);
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/components/esp_http_client/lib/include/transport_ssl.h b/components/esp_http_client/lib/include/transport_ssl.h
new file mode 100644 (file)
index 0000000..a00b36b
--- /dev/null
@@ -0,0 +1,48 @@
+// 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 _TRANSPORT_SSL_H_
+#define _TRANSPORT_SSL_H_
+
+#include "transport.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * @brief       Create new SSL transport, the transport handle must be release transport_destroy callback
+ *
+ * @return      the allocated transport_handle_t, or NULL if the handle can not be allocated
+ */
+transport_handle_t transport_ssl_init();
+
+/**
+ * @brief      Set SSL certificate data (as PEM format).
+ *             Note that, this function stores the pointer to data, rather than making a copy.
+ *             So we need to make sure to keep the data lifetime before cleanup the connection
+ *
+ * @param      t     ssl transport
+ * @param[in]  data  The pem data
+ * @param[in]  len   The length
+ */
+void transport_ssl_set_cert_data(transport_handle_t t, const char *data, int len);
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif
+
diff --git a/components/esp_http_client/lib/include/transport_tcp.h b/components/esp_http_client/lib/include/transport_tcp.h
new file mode 100644 (file)
index 0000000..e1cc1d3
--- /dev/null
@@ -0,0 +1,36 @@
+// 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 _TRANSPORT_TCP_H_
+#define _TRANSPORT_TCP_H_
+
+#include "transport.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief      Create TCP transport, the transport handle must be release transport_destroy callback
+ *
+ * @return  the allocated transport_handle_t, or NULL if the handle can not be allocated
+ */
+transport_handle_t transport_tcp_init();
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/components/esp_http_client/lib/transport.c b/components/esp_http_client/lib/transport.c
new file mode 100644 (file)
index 0000000..9351d83
--- /dev/null
@@ -0,0 +1,232 @@
+// 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 <stdlib.h>
+#include <string.h>
+
+#include "rom/queue.h"
+#include "esp_log.h"
+
+#include "transport.h"
+#include "http_utils.h"
+
+static const char *TAG = "TRANSPORT";
+
+/**
+ * Transport layer structure, which will provide functions, basic properties for transport types
+ */
+struct transport_item_t {
+    int             port;
+    int             socket;         /*!< Socket to use in this transport */
+    char            *scheme;        /*!< Tag name */
+    void            *context;       /*!< Context data */
+    void            *data;          /*!< Additional transport data */
+    connect_func    _connect;       /*!< Connect function of this transport */
+    io_read_func    _read;          /*!< Read */
+    io_func         _write;         /*!< Write */
+    trans_func      _close;         /*!< Close */
+    poll_func       _poll_read;     /*!< Poll and read */
+    poll_func       _poll_write;    /*!< Poll and write */
+    trans_func      _destroy;       /*!< Destroy and free transport */
+    STAILQ_ENTRY(transport_item_t) next;
+};
+
+
+/**
+ * This list will hold all transport available
+ */
+STAILQ_HEAD(transport_list_t, transport_item_t);
+
+
+transport_list_handle_t transport_list_init()
+{
+    transport_list_handle_t list = calloc(1, sizeof(struct transport_list_t));
+    HTTP_MEM_CHECK(TAG, list, return NULL);
+    STAILQ_INIT(list);
+    return list;
+}
+
+esp_err_t transport_list_add(transport_list_handle_t list, transport_handle_t t, const char *scheme)
+{
+    if (list == NULL || t == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    t->scheme = calloc(1, strlen(scheme) + 1);
+    HTTP_MEM_CHECK(TAG, t->scheme, return ESP_ERR_NO_MEM);
+    strcpy(t->scheme, scheme);
+    STAILQ_INSERT_TAIL(list, t, next);
+    return ESP_OK;
+}
+
+transport_handle_t transport_list_get_transport(transport_list_handle_t list, const char *scheme)
+{
+    if (!list) {
+        return NULL;
+    }
+    if (scheme == NULL) {
+        return STAILQ_FIRST(list);
+    }
+    transport_handle_t item;
+    STAILQ_FOREACH(item, list, next) {
+        if (strcasecmp(item->scheme, scheme) == 0) {
+            return item;
+        }
+    }
+    return NULL;
+}
+
+esp_err_t transport_list_destroy(transport_list_handle_t list)
+{
+    transport_list_clean(list);
+    free(list);
+    return ESP_OK;
+}
+
+esp_err_t transport_list_clean(transport_list_handle_t list)
+{
+    transport_handle_t item = STAILQ_FIRST(list);
+    transport_handle_t tmp;
+    while (item != NULL) {
+        tmp = STAILQ_NEXT(item, next);
+        if (item->_destroy) {
+            item->_destroy(item);
+        }
+        transport_destroy(item);
+        item = tmp;
+    }
+    STAILQ_INIT(list);
+    return ESP_OK;
+}
+
+transport_handle_t transport_init()
+{
+    transport_handle_t t = calloc(1, sizeof(struct transport_item_t));
+    HTTP_MEM_CHECK(TAG, t, return NULL);
+    return t;
+}
+
+esp_err_t transport_destroy(transport_handle_t t)
+{
+    if (t->scheme) {
+        free(t->scheme);
+    }
+    free(t);
+    return ESP_OK;
+}
+
+int transport_connect(transport_handle_t t, const char *host, int port, int timeout_ms)
+{
+    int ret = -1;
+    if (t && t->_connect) {
+        return t->_connect(t, host, port, timeout_ms);
+    }
+    return ret;
+}
+
+int transport_read(transport_handle_t t, char *buffer, int len, int timeout_ms)
+{
+    if (t && t->_read) {
+        return t->_read(t, buffer, len, timeout_ms);
+    }
+    return -1;
+}
+
+int transport_write(transport_handle_t t, const char *buffer, int len, int timeout_ms)
+{
+    if (t && t->_write) {
+        return t->_write(t, buffer, len, timeout_ms);
+    }
+    return -1;
+}
+
+int transport_poll_read(transport_handle_t t, int timeout_ms)
+{
+    if (t && t->_poll_read) {
+        return t->_poll_read(t, timeout_ms);
+    }
+    return -1;
+}
+
+int transport_poll_write(transport_handle_t t, int timeout_ms)
+{
+    if (t && t->_poll_write) {
+        return t->_poll_write(t, timeout_ms);
+    }
+    return -1;
+}
+
+int transport_close(transport_handle_t t)
+{
+    if (t && t->_close) {
+        return t->_close(t);
+    }
+    return 0;
+}
+
+void *transport_get_context_data(transport_handle_t t)
+{
+    if (t) {
+        return t->data;
+    }
+    return NULL;
+}
+
+esp_err_t transport_set_context_data(transport_handle_t t, void *data)
+{
+    if (t) {
+        t->data = data;
+        return ESP_OK;
+    }
+    return ESP_FAIL;
+}
+
+esp_err_t transport_set_func(transport_handle_t t,
+                             connect_func _connect,
+                             io_read_func _read,
+                             io_func _write,
+                             trans_func _close,
+                             poll_func _poll_read,
+                             poll_func _poll_write,
+                             trans_func _destroy)
+{
+    if (t == NULL) {
+        return ESP_FAIL;
+    }
+    t->_connect = _connect;
+    t->_read = _read;
+    t->_write = _write;
+    t->_close = _close;
+    t->_poll_read = _poll_read;
+    t->_poll_write = _poll_write;
+    t->_destroy = _destroy;
+    return ESP_OK;
+}
+
+int transport_get_default_port(transport_handle_t t)
+{
+    if (t == NULL) {
+        return -1;
+    }
+    return t->port;
+}
+
+esp_err_t transport_set_default_port(transport_handle_t t, int port)
+{
+    if (t == NULL) {
+        return ESP_FAIL;
+    }
+    t->port = port;
+    return ESP_OK;
+}
diff --git a/components/esp_http_client/lib/transport_ssl.c b/components/esp_http_client/lib/transport_ssl.c
new file mode 100644 (file)
index 0000000..8a3c49c
--- /dev/null
@@ -0,0 +1,267 @@
+// 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 <string.h>
+#include <stdlib.h>
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "lwip/err.h"
+#include "lwip/sockets.h"
+#include "lwip/sys.h"
+#include "lwip/netdb.h"
+#include "lwip/dns.h"
+
+#include "mbedtls/platform.h"
+#include "mbedtls/net_sockets.h"
+#include "mbedtls/esp_debug.h"
+#include "mbedtls/ssl.h"
+#include "mbedtls/entropy.h"
+#include "mbedtls/ctr_drbg.h"
+#include "mbedtls/error.h"
+#include "mbedtls/certs.h"
+
+
+#include "esp_log.h"
+#include "esp_system.h"
+
+#include "transport.h"
+#include "transport_ssl.h"
+#include "http_utils.h"
+
+static const char *TAG = "TRANS_SSL";
+/**
+ *  mbedtls specific transport data
+ */
+typedef struct {
+    mbedtls_entropy_context  entropy;
+    mbedtls_ctr_drbg_context ctr_drbg;
+    mbedtls_ssl_context      ctx;
+    mbedtls_x509_crt         cacert;
+    mbedtls_ssl_config       conf;
+    mbedtls_net_context      client_fd;
+    void                     *cert_pem_data;
+    int                      cert_pem_len;
+    bool                     ssl_initialized;
+    bool                     verify_server;
+} transport_ssl_t;
+
+static int ssl_close(transport_handle_t t);
+
+static int ssl_connect(transport_handle_t t, const char *host, int port, int timeout_ms)
+{
+    int ret = -1, flags;
+    struct timeval tv;
+    transport_ssl_t *ssl = transport_get_context_data(t);
+
+    if (!ssl) {
+        return -1;
+    }
+    ssl->ssl_initialized = true;
+    mbedtls_ssl_init(&ssl->ctx);
+    mbedtls_ctr_drbg_init(&ssl->ctr_drbg);
+    mbedtls_ssl_config_init(&ssl->conf);
+    mbedtls_entropy_init(&ssl->entropy);
+
+    if ((ret = mbedtls_ssl_config_defaults(&ssl->conf,
+                                           MBEDTLS_SSL_IS_CLIENT,
+                                           MBEDTLS_SSL_TRANSPORT_STREAM,
+                                           MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
+        ESP_LOGE(TAG, "mbedtls_ssl_config_defaults returned %d", ret);
+        goto exit;
+    }
+
+    if ((ret = mbedtls_ctr_drbg_seed(&ssl->ctr_drbg, mbedtls_entropy_func, &ssl->entropy, NULL, 0)) != 0) {
+        ESP_LOGE(TAG, "mbedtls_ctr_drbg_seed returned %d", ret);
+        goto exit;
+    }
+
+    if (ssl->cert_pem_data) {
+        mbedtls_x509_crt_init(&ssl->cacert);
+        ssl->verify_server = true;
+        if ((ret = mbedtls_x509_crt_parse(&ssl->cacert, ssl->cert_pem_data, ssl->cert_pem_len + 1)) < 0) {
+            ESP_LOGE(TAG, "mbedtls_x509_crt_parse returned -0x%x\n\nDATA=%s,len=%d", -ret, (char*)ssl->cert_pem_data, ssl->cert_pem_len);
+            goto exit;
+        }
+        mbedtls_ssl_conf_ca_chain(&ssl->conf, &ssl->cacert, NULL);
+        mbedtls_ssl_conf_authmode(&ssl->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
+
+        if ((ret = mbedtls_ssl_set_hostname(&ssl->ctx, host)) != 0) {
+            ESP_LOGE(TAG, "mbedtls_ssl_set_hostname returned -0x%x", -ret);
+            goto exit;
+        }
+    } else {
+        mbedtls_ssl_conf_authmode(&ssl->conf, MBEDTLS_SSL_VERIFY_NONE);
+    }
+
+
+    mbedtls_ssl_conf_rng(&ssl->conf, mbedtls_ctr_drbg_random, &ssl->ctr_drbg);
+
+#ifdef CONFIG_MBEDTLS_DEBUG
+    mbedtls_esp_enable_debug_log(&ssl->conf, 4);
+#endif
+
+    if ((ret = mbedtls_ssl_setup(&ssl->ctx, &ssl->conf)) != 0) {
+        ESP_LOGE(TAG, "mbedtls_ssl_setup returned -0x%x\n\n", -ret);
+        goto exit;
+    }
+
+    mbedtls_net_init(&ssl->client_fd);
+
+    http_utils_ms_to_timeval(timeout_ms, &tv);
+
+    setsockopt(ssl->client_fd.fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+    ESP_LOGD(TAG, "Connect to %s:%d", host, port);
+    char port_str[8] = {0};
+    sprintf(port_str, "%d", port);
+    if ((ret = mbedtls_net_connect(&ssl->client_fd, host, port_str, MBEDTLS_NET_PROTO_TCP)) != 0) {
+        ESP_LOGE(TAG, "mbedtls_net_connect returned -%x", -ret);
+        goto exit;
+    }
+
+    mbedtls_ssl_set_bio(&ssl->ctx, &ssl->client_fd, mbedtls_net_send, mbedtls_net_recv, NULL);
+
+    if((ret = mbedtls_ssl_set_hostname(&ssl->ctx, host)) != 0) {
+        ESP_LOGE(TAG, " failed\n  ! mbedtls_ssl_set_hostname returned %d\n\n", ret);
+        goto exit;
+    }
+
+    ESP_LOGD(TAG, "Performing the SSL/TLS handshake...");
+
+    while ((ret = mbedtls_ssl_handshake(&ssl->ctx)) != 0) {
+        if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
+            ESP_LOGE(TAG, "mbedtls_ssl_handshake returned -0x%x", -ret);
+            goto exit;
+        }
+    }
+
+    ESP_LOGD(TAG, "Verifying peer X.509 certificate...");
+
+    if ((flags = mbedtls_ssl_get_verify_result(&ssl->ctx)) != 0) {
+        /* In real life, we probably want to close connection if ret != 0 */
+        ESP_LOGW(TAG, "Failed to verify peer certificate!");
+        if (ssl->cert_pem_data) {
+            goto exit;
+        }
+    } else {
+        ESP_LOGD(TAG, "Certificate verified.");
+    }
+
+    ESP_LOGD(TAG, "Cipher suite is %s", mbedtls_ssl_get_ciphersuite(&ssl->ctx));
+    return ret;
+exit:
+    ssl_close(t);
+    return ret;
+}
+
+static int ssl_poll_read(transport_handle_t t, int timeout_ms)
+{
+    transport_ssl_t *ssl = transport_get_context_data(t);
+    fd_set readset;
+    FD_ZERO(&readset);
+    FD_SET(ssl->client_fd.fd, &readset);
+    struct timeval timeout;
+    http_utils_ms_to_timeval(timeout_ms, &timeout);
+
+    return select(ssl->client_fd.fd + 1, &readset, NULL, NULL, &timeout);
+}
+
+static int ssl_poll_write(transport_handle_t t, int timeout_ms)
+{
+    transport_ssl_t *ssl = transport_get_context_data(t);
+    fd_set writeset;
+    FD_ZERO(&writeset);
+    FD_SET(ssl->client_fd.fd, &writeset);
+    struct timeval timeout;
+    http_utils_ms_to_timeval(timeout_ms, &timeout);
+    return select(ssl->client_fd.fd + 1, NULL, &writeset, NULL, &timeout);
+}
+
+static int ssl_write(transport_handle_t t, const char *buffer, int len, int timeout_ms)
+{
+    int poll, ret;
+    transport_ssl_t *ssl = transport_get_context_data(t);
+
+    if ((poll = transport_poll_write(t, timeout_ms)) <= 0) {
+        ESP_LOGW(TAG, "Poll timeout or error, errno=%s, fd=%d, timeout_ms=%d", strerror(errno), ssl->client_fd.fd, timeout_ms);
+        return poll;
+    }
+    ret = mbedtls_ssl_write(&ssl->ctx, (const unsigned char *) buffer, len);
+    if (ret <= 0) {
+        ESP_LOGE(TAG, "mbedtls_ssl_write error, errno=%s", strerror(errno));
+    }
+    return ret;
+}
+
+static int ssl_read(transport_handle_t t, char *buffer, int len, int timeout_ms)
+{
+    int ret;
+    transport_ssl_t *ssl = transport_get_context_data(t);
+    ret = mbedtls_ssl_read(&ssl->ctx, (unsigned char *)buffer, len);
+    if (ret == 0) {
+        return -1;
+    }
+    return ret;
+}
+
+static int ssl_close(transport_handle_t t)
+{
+    int ret = -1;
+    transport_ssl_t *ssl = transport_get_context_data(t);
+    if (ssl->ssl_initialized) {
+        ESP_LOGD(TAG, "Cleanup mbedtls");
+        mbedtls_ssl_close_notify(&ssl->ctx);
+        mbedtls_ssl_session_reset(&ssl->ctx);
+        mbedtls_net_free(&ssl->client_fd);
+        mbedtls_ssl_config_free(&ssl->conf);
+        if (ssl->verify_server) {
+            mbedtls_x509_crt_free(&ssl->cacert);
+        }
+        mbedtls_ctr_drbg_free(&ssl->ctr_drbg);
+        mbedtls_entropy_free(&ssl->entropy);
+        mbedtls_ssl_free(&ssl->ctx);
+        ssl->ssl_initialized = false;
+        ssl->verify_server = false;
+    }
+    return ret;
+}
+
+static int ssl_destroy(transport_handle_t t)
+{
+    transport_ssl_t *ssl = transport_get_context_data(t);
+    transport_close(t);
+    free(ssl);
+    return 0;
+}
+
+void transport_ssl_set_cert_data(transport_handle_t t, const char *data, int len)
+{
+    transport_ssl_t *ssl = transport_get_context_data(t);
+    if (t && ssl) {
+        ssl->cert_pem_data = (void *)data;
+        ssl->cert_pem_len = len;
+    }
+}
+
+transport_handle_t transport_ssl_init()
+{
+    transport_handle_t t = transport_init();
+    transport_ssl_t *ssl = calloc(1, sizeof(transport_ssl_t));
+    HTTP_MEM_CHECK(TAG, ssl, return NULL);
+    mbedtls_net_init(&ssl->client_fd);
+    transport_set_context_data(t, ssl);
+    transport_set_func(t, ssl_connect, ssl_read, ssl_write, ssl_close, ssl_poll_read, ssl_poll_write, ssl_destroy);
+    return t;
+}
+
diff --git a/components/esp_http_client/lib/transport_tcp.c b/components/esp_http_client/lib/transport_tcp.c
new file mode 100644 (file)
index 0000000..058fc9f
--- /dev/null
@@ -0,0 +1,166 @@
+// 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 <stdlib.h>
+#include <string.h>
+
+#include "lwip/sockets.h"
+#include "lwip/dns.h"
+#include "lwip/netdb.h"
+
+#include "esp_log.h"
+#include "esp_system.h"
+#include "esp_err.h"
+
+#include "http_utils.h"
+#include "transport.h"
+
+static const char *TAG = "TRANS_TCP";
+
+typedef struct {
+    int sock;
+} transport_tcp_t;
+
+static int resolve_dns(const char *host, struct sockaddr_in *ip) {
+
+    struct hostent *he;
+    struct in_addr **addr_list;
+    he = gethostbyname(host);
+    if (he == NULL) {
+        return ESP_FAIL;
+    }
+    addr_list = (struct in_addr **)he->h_addr_list;
+    if (addr_list[0] == NULL) {
+        return ESP_FAIL;
+    }
+    ip->sin_family = AF_INET;
+    memcpy(&ip->sin_addr, addr_list[0], sizeof(ip->sin_addr));
+    return ESP_OK;
+}
+
+static int tcp_connect(transport_handle_t t, const char *host, int port, int timeout_ms)
+{
+    struct sockaddr_in remote_ip;
+    struct timeval tv;
+    transport_tcp_t *tcp = transport_get_context_data(t);
+
+    bzero(&remote_ip, sizeof(struct sockaddr_in));
+
+    //if stream_host is not ip address, resolve it AF_INET,servername,&serveraddr.sin_addr
+    if (inet_pton(AF_INET, host, &remote_ip.sin_addr) != 1) {
+        if (resolve_dns(host, &remote_ip) < 0) {
+            return -1;
+        }
+    }
+
+    tcp->sock = socket(PF_INET, SOCK_STREAM, 0);
+
+    if (tcp->sock < 0) {
+        ESP_LOGE(TAG, "Error create socket");
+        return -1;
+    }
+
+    remote_ip.sin_family = AF_INET;
+    remote_ip.sin_port = htons(port);
+
+    http_utils_ms_to_timeval(timeout_ms, &tv);
+
+    setsockopt(tcp->sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+
+    ESP_LOGD(TAG, "[sock=%d],connecting to server IP:%s,Port:%d...",
+             tcp->sock, ipaddr_ntoa((const ip_addr_t*)&remote_ip.sin_addr.s_addr), port);
+    if (connect(tcp->sock, (struct sockaddr *)(&remote_ip), sizeof(struct sockaddr)) != 0) {
+        close(tcp->sock);
+        tcp->sock = -1;
+        return -1;
+    }
+    return tcp->sock;
+}
+
+static int tcp_write(transport_handle_t t, const char *buffer, int len, int timeout_ms)
+{
+    int poll;
+    transport_tcp_t *tcp = transport_get_context_data(t);
+    if ((poll = transport_poll_write(t, timeout_ms)) <= 0) {
+        return poll;
+    }
+    return write(tcp->sock, buffer, len);
+}
+
+static int tcp_read(transport_handle_t t, char *buffer, int len, int timeout_ms)
+{
+    transport_tcp_t *tcp = transport_get_context_data(t);
+    int poll = -1;
+    if ((poll = transport_poll_read(t, timeout_ms)) <= 0) {
+        return poll;
+    }
+    int read_len = read(tcp->sock, buffer, len);
+    if (read_len == 0) {
+        return -1;
+    }
+    return read_len;
+}
+
+static int tcp_poll_read(transport_handle_t t, int timeout_ms)
+{
+    transport_tcp_t *tcp = transport_get_context_data(t);
+    fd_set readset;
+    FD_ZERO(&readset);
+    FD_SET(tcp->sock, &readset);
+    struct timeval timeout;
+    http_utils_ms_to_timeval(timeout_ms, &timeout);
+    return select(tcp->sock + 1, &readset, NULL, NULL, &timeout);
+}
+
+static int tcp_poll_write(transport_handle_t t, int timeout_ms)
+{
+    transport_tcp_t *tcp = transport_get_context_data(t);
+    fd_set writeset;
+    FD_ZERO(&writeset);
+    FD_SET(tcp->sock, &writeset);
+    struct timeval timeout;
+    http_utils_ms_to_timeval(timeout_ms, &timeout);
+    return select(tcp->sock + 1, NULL, &writeset, NULL, &timeout);
+}
+
+static int tcp_close(transport_handle_t t)
+{
+    transport_tcp_t *tcp = transport_get_context_data(t);
+    int ret = -1;
+    if (tcp->sock >= 0) {
+        ret = close(tcp->sock);
+        tcp->sock = -1;
+    }
+    return ret;
+}
+
+static esp_err_t tcp_destroy(transport_handle_t t)
+{
+    transport_tcp_t *tcp = transport_get_context_data(t);
+    transport_close(t);
+    free(tcp);
+    return 0;
+}
+
+transport_handle_t transport_tcp_init()
+{
+    transport_handle_t t = transport_init();
+    transport_tcp_t *tcp = calloc(1, sizeof(transport_tcp_t));
+    HTTP_MEM_CHECK(TAG, tcp, return NULL);
+    tcp->sock = -1;
+    transport_set_func(t, tcp_connect, tcp_read, tcp_write, tcp_close, tcp_poll_read, tcp_poll_write, tcp_destroy);
+    transport_set_context_data(t, tcp);
+
+    return t;
+}
index 7c1ac334d31552ecad61efce5e0568401883e4fa..fe5a3e1fd8f69c4a117fb8cd0fb322d48c75b11e 100644 (file)
@@ -91,6 +91,7 @@ INPUT = \
     ../../components/esp-tls/esp_tls.h \
     ## mDNS    
     ../../components/mdns/include/mdns.h \
+    ../../components/esp_http_client/include/esp_http_client.h \
     ##
     ## Storage - API Reference
     ##
diff --git a/docs/en/api-reference/protocols/esp_http_client.rst b/docs/en/api-reference/protocols/esp_http_client.rst
new file mode 100644 (file)
index 0000000..b54078a
--- /dev/null
@@ -0,0 +1,191 @@
+ESP HTTP Client
+===============
+
+Overview
+--------
+
+``esp_http_client`` provides an API for making HTTP/S requests from ESP-IDF programs. The steps to use this API for an HTTP request are:
+
+    * :cpp:func:`esp_http_client_init`: To use the HTTP client, the first thing we must do is create an :cpp:class:`esp_http_client` by pass into this function with the :cpp:class:`esp_http_client_config_t` configurations. Which configuration values we do not define, the library will use default.
+    * :cpp:func:`esp_http_client_perform`: The :cpp:class:`esp_http_client` argument created from the init function is needed. This function performs all operations of the esp_http_client, from opening the connection, sending data, downloading data and closing the connection if necessary. All related events will be invoked in the event_handle (defined by :cpp:class:`esp_http_client_config_t`). This function performs its job and blocks the current task until it's done
+    * :cpp:func:`esp_http_client_cleanup`: After completing our **esp_http_client's** task, this is the last function to be called. It will close the connection (if any) and free up all the memory allocated to the HTTP client
+
+
+Application Example
+-------------------
+
+    .. highlight:: c
+
+    ::
+
+        esp_err_t _http_event_handle(esp_http_client_event_t *evt)
+        {
+            switch(evt->event_id) {
+                case HTTP_EVENT_ERROR:
+                    ESP_LOGI(TAG, "HTTP_EVENT_ERROR");
+                    break;
+                case HTTP_EVENT_ON_CONNECTED:
+                    ESP_LOGI(TAG, "HTTP_EVENT_ON_CONNECTED");
+                    break;
+                case HTTP_EVENT_HEADER_SENT:
+                    ESP_LOGI(TAG, "HTTP_EVENT_HEADER_SENT");
+                    break;
+                case HTTP_EVENT_ON_HEADER:
+                    ESP_LOGI(TAG, "HTTP_EVENT_ON_HEADER");
+                    printf("%.*s", evt->data_len, (char*)evt->data);
+                    break;
+                case HTTP_EVENT_ON_DATA:
+                    ESP_LOGI(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
+                    if (!esp_http_client_is_chunked_response(evt->client)) {
+                        printf("%.*s", evt->data_len, (char*)evt->data);
+                    }
+
+                    break;
+                case HTTP_EVENT_ON_FINISH:
+                    ESP_LOGI(TAG, "HTTP_EVENT_ON_FINISH");
+                    break;
+                case HTTP_EVENT_DISCONNECTED:
+                    ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
+                    break;
+            }
+            return ESP_OK;
+        }
+
+        esp_http_client_config_t config = {
+           .url = "http://httpbin.org/redirect/2",
+           .event_handle = _http_event_handle,
+        };
+        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, "Status = %d, content_length = %d",
+                   esp_http_client_get_status_code(client),
+                   esp_http_client_get_content_length(client));
+        }
+        esp_http_client_cleanup(client);
+
+
+Persistent Connections
+----------------------
+
+Persistent connections means that the HTTP client can re-use the same connection for several transfers. If the server does not request to close the connection with the ``Connection: close`` header, the new transfer with sample ip address, port, and protocol.
+
+To allow the HTTP client to take full advantage of persistent connections, you should do as many of your file transfers as possible using the same handle.
+
+Persistent Connections example
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. highlight:: c
+
+::
+
+    esp_err_t err;
+    esp_http_client_config_t config = {
+        .url = "http://httpbin.org/get",
+    };
+    esp_http_client_handle_t client = esp_http_client_init(&config);
+    // first request
+    err = esp_http_client_perform(client);
+
+    // second request
+    esp_http_client_set_url(client, "http://httpbin.org/anything")
+    esp_http_client_set_method(client, HTTP_METHOD_DELETE);
+    esp_http_client_set_header(client, "HeaderKey", "HeaderValue");
+    err = esp_http_client_perform(client);
+
+    esp_http_client_cleanup(client);
+
+
+HTTPS
+-----
+
+The HTTP client supports SSL connections using **mbedtls**, with the **url** configuration starting with ``https`` scheme (or ``transport_type = HTTP_TRANSPORT_OVER_SSL``). HTTPS support can be configured via :ref:CONFIG_ENABLE_HTTPS (enabled by default)..
+
+.. note:: By providing information using HTTPS, the library will use the SSL transport type to connect to the server. If you want to verify server, then need to provide additional certificate in PEM format, and provide to ``cert_pem`` in ``esp_http_client_config_t``
+
+HTTPS example
+^^^^^^^^^^^^^
+
+.. highlight:: c
+
+::
+
+    static void https()
+    {
+        esp_http_client_config_t config = {
+            .url = "https://www.howsmyssl.com",
+            .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, "Status = %d, content_length = %d",
+                    esp_http_client_get_status_code(client),
+                    esp_http_client_get_content_length(client));
+        }
+        esp_http_client_cleanup(client);
+    }
+
+HTTP Stream
+-----------
+
+Some applications need to open the connection and control the reading of the data in an active manner. the HTTP client supports some functions to make this easier, of course, once you use these functions you should not use the :cpp:func:`esp_http_client_perform` function with that handle, and :cpp:func:`esp_http_client_init` alway to called first to get the handle. Perform that functions in the order below:
+
+    * :cpp:func:`esp_http_client_init`: to create and handle
+    * ``esp_http_client_set_*`` or ``esp_http_client_delete_*``: to modify the http connection information (optional)
+    * :cpp:func:`esp_http_client_open`: Open the http connection with ``write_len`` parameter, ``write_len=0`` if we only need read
+    * :cpp:func:`esp_http_client_write`: Upload data, max length equal to ``write_len`` of :cpp:func:`esp_http_client_open` function. We may not need to call it if ``write_len=0``
+    * :cpp:func:`esp_http_client_fetch_headers`: After sending the headers and write data (if any) to the server, this function will read the HTTP Server response headers. Calling this function will return the ``content-length`` from the Server, and we can call :cpp:func:`esp_http_client_get_status_code` for the HTTP status of the connection.
+    * :cpp:func:`esp_http_client_read`: Now, we can read the HTTP stream by this function. 
+    * :cpp:func:`esp_http_client_close`: We should the connection after finish
+    * :cpp:func:`esp_http_client_cleanup`: And release the resources
+
+Perform HTTP request as Stream reader
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Check the example function ``http_perform_as_stream_reader`` at :example:`protocols/esp_http_client`.
+
+
+HTTP Authentication
+-------------------
+
+The HTTP client supports both **Basic** and **Digest** Authentication. By providing usernames and passwords in ``url`` or in the ``username``, ``password`` of config entry. And with ``auth_type = HTTP_AUTH_TYPE_BASIC``, the HTTP client takes only 1 perform to pass the authentication process. If ``auth_type = HTTP_AUTH_TYPE_NONE``, but there are ``username`` and ``password`` in the configuration, the HTTP client takes 2 performs. The first time it connects to the server and receives the UNAUTHORIZED header. Based on this information, it will know which authentication method to choose, and perform it on the second.
+
+
+Config authentication example with URI
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. highlight:: c
+
+::
+
+    esp_http_client_config_t config = {
+        .url = "http://user:passwd@httpbin.org/basic-auth/user/passwd",
+        .auth_type = HTTP_AUTH_TYPE_BASIC,
+    };
+
+
+Config authentication example with username, password entry
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. highlight:: c
+
+::
+
+    esp_http_client_config_t config = {
+        .url = "http://httpbin.org/basic-auth/user/passwd",
+        .username = "user",
+        .password = "passwd",
+        .auth_type = HTTP_AUTH_TYPE_BASIC,
+    };
+    
+
+HTTP Client example: :example:`protocols/esp_http_client`.
+
+
+API Reference
+-------------
+
+.. include:: /_build/inc/esp_http_client.inc
index fb9ca36a767fadffa0d13a4d231817bbe9445c2c..165fd0ca65056e488edbc0144a727a2c522eb27e 100644 (file)
@@ -6,5 +6,6 @@ Protocols API
 
    mDNS <mdns>
    ESP-TLS <esp_tls>
+   HTTP Client <esp_http_client>
 
 Example code for this API section is provided in :example:`protocols` directory of ESP-IDF examples.
diff --git a/docs/zh_CN/api-reference/protocols/esp_http_client.rst b/docs/zh_CN/api-reference/protocols/esp_http_client.rst
new file mode 100644 (file)
index 0000000..4876be0
--- /dev/null
@@ -0,0 +1 @@
+.. include:: ../../../en/api-reference/protocols/esp_http_client.rst
diff --git a/examples/protocols/esp_http_client/Makefile b/examples/protocols/esp_http_client/Makefile
new file mode 100644 (file)
index 0000000..851c347
--- /dev/null
@@ -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 (file)
index 0000000..c4225fd
--- /dev/null
@@ -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 (file)
index 0000000..c982bb6
--- /dev/null
@@ -0,0 +1,51 @@
+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")
+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 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)"))
+    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/Kconfig.projbuild b/examples/protocols/esp_http_client/main/Kconfig.projbuild
new file mode 100644 (file)
index 0000000..1c7241d
--- /dev/null
@@ -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 (file)
index 0000000..da5ac44
--- /dev/null
@@ -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 (file)
index 0000000..91c886d
--- /dev/null
@@ -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 (file)
index 0000000..cb97ca0
--- /dev/null
@@ -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 (file)
index 0000000..66abb86
--- /dev/null
@@ -0,0 +1,362 @@
+/* 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 <string.h>
+#include <stdlib.h>
+#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 </dev/null
+
+   The CA root cert is the last cert given in the chain of certs.
+
+   To embed it in the app binary, the PEM file is named
+   in the component.mk COMPONENT_EMBED_TXTFILES variable.
+*/
+extern const char howsmyssl_com_root_cert_pem_start[] asm("_binary_howsmyssl_com_root_cert_pem_start");
+extern const char howsmyssl_com_root_cert_pem_end[]   asm("_binary_howsmyssl_com_root_cert_pem_end");
+
+esp_err_t _http_event_handler(esp_http_client_event_t *evt)
+{
+    switch(evt->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));
+    }
+
+    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);
+    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 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();
+    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) {
+      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 (file)
index 0000000..0002462
--- /dev/null
@@ -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-----