Kaynağa Gözat

Merge branch 'feature/async_esp_http_client' into 'master'

Non Blocking `esp_http_perform()` API in esp_http_client

See merge request idf/esp-idf!3049
Angus Gratton 7 yıl önce
ebeveyn
işleme
dc135ed1f6

+ 149 - 52
components/esp-tls/esp_tls.c

@@ -22,6 +22,7 @@
 
 #include <http_parser.h>
 #include "esp_tls.h"
+#include <errno.h>
 
 static const char *TAG = "esp-tls";
 
@@ -32,8 +33,6 @@ static const char *TAG = "esp-tls";
 #define ESP_LOGE(TAG, ...) printf(__VA_ARGS__);
 #endif
 
-#define DEFAULT_TIMEOUT_MS -1
-
 static struct addrinfo *resolve_host_name(const char *host, size_t hostlen)
 {
     struct addrinfo hints;
@@ -82,19 +81,20 @@ static void ms_to_timeval(int timeout_ms, struct timeval *tv)
     tv->tv_usec = (timeout_ms % 1000) * 1000;
 }
 
-static int esp_tcp_connect(const char *host, int hostlen, int port, int timeout_ms)
+static int esp_tcp_connect(const char *host, int hostlen, int port, int *sockfd, const esp_tls_cfg_t *cfg)
 {
+    int ret = -1;
     struct addrinfo *res = resolve_host_name(host, hostlen);
     if (!res) {
-        return -1;
+        return ret;
     }
 
-    int ret = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
-    if (ret < 0) {
+    int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+    if (fd < 0) {
         ESP_LOGE(TAG, "Failed to create socket (family %d socktype %d protocol %d)", res->ai_family, res->ai_socktype, res->ai_protocol);
         goto err_freeaddr;
     }
-    int fd = ret;
+    *sockfd = fd;
 
     void *addr_ptr;
     if (res->ai_family == AF_INET) {
@@ -111,32 +111,39 @@ static int esp_tcp_connect(const char *host, int hostlen, int port, int timeout_
         goto err_freesocket;
     }
 
-    if (timeout_ms >= 0) {
-        struct timeval tv;
-        ms_to_timeval(timeout_ms, &tv);
-        setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+    if (cfg) {
+        if (cfg->timeout_ms >= 0) {
+            struct timeval tv;
+            ms_to_timeval(cfg->timeout_ms, &tv);
+            setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
+        }
+        if (cfg->non_block) {
+            int flags = fcntl(fd, F_GETFL, 0);
+            fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+        }
     }
 
     ret = connect(fd, addr_ptr, res->ai_addrlen);
-    if (ret < 0) {
+    if (ret < 0 && !(errno == EINPROGRESS && cfg->non_block)) {
+
         ESP_LOGE(TAG, "Failed to connnect to host (errno %d)", errno);
         goto err_freesocket;
     }
 
     freeaddrinfo(res);
-    return fd;
+    return 0;
 
 err_freesocket:
     close(fd);
 err_freeaddr:
     freeaddrinfo(res);
-    return -1;
+    return ret;
 }
 
 static void verify_certificate(esp_tls_t *tls)
 {
     int flags;
-    char buf[100]; 
+    char buf[100];
     if ((flags = mbedtls_ssl_get_verify_result(&tls->ssl)) != 0) {
         ESP_LOGI(TAG, "Failed to verify peer certificate!");
         bzero(buf, sizeof(buf));
@@ -225,20 +232,8 @@ static int create_ssl_handle(esp_tls_t *tls, const char *hostname, size_t hostle
         ESP_LOGE(TAG, "mbedtls_ssl_setup returned -0x%x\n\n", -ret);
         goto exit;
     }
-
     mbedtls_ssl_set_bio(&tls->ssl, &tls->server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);
 
-    while ((ret = mbedtls_ssl_handshake(&tls->ssl)) != 0) {
-        if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
-            ESP_LOGE(TAG, "mbedtls_ssl_handshake returned -0x%x", -ret);
-            if (cfg->cacert_pem_buf != NULL) {
-                /* This is to check whether handshake failed due to invalid certificate*/
-                verify_certificate(tls);
-            }   
-            goto exit;
-        }
-    }
-    
     return 0;
 exit:
     mbedtls_cleanup(tls);
@@ -275,45 +270,132 @@ static ssize_t tls_write(esp_tls_t *tls, const char *data, size_t datalen)
     return ret;
 }
 
+static int esp_tls_low_level_conn(const char *hostname, int hostlen, int port, const esp_tls_cfg_t *cfg, esp_tls_t *tls)
+{
+    if (!tls) {
+        ESP_LOGE(TAG, "empty esp_tls parameter");
+        return -1;
+    }
+    /* These states are used to keep a tab on connection progress in case of non-blocking connect,
+    and in case of blocking connect these cases will get executed one after the other */
+    switch (tls->conn_state) {
+        case ESP_TLS_INIT:
+            ;
+            int sockfd;
+            int ret = esp_tcp_connect(hostname, hostlen, port, &sockfd, cfg);
+            if (ret < 0) {
+                return -1;
+            }
+            tls->sockfd = sockfd;
+            if (!cfg) {
+                tls->read = tcp_read;
+                tls->write = tcp_write;
+                ESP_LOGD(TAG, "non-tls connection established");
+                return 1;
+            }
+            if (cfg->non_block) {
+                FD_ZERO(&tls->rset);
+                FD_SET(tls->sockfd, &tls->rset);
+                tls->wset = tls->rset;
+            }
+            tls->conn_state = ESP_TLS_CONNECTING;
+            /* falls through */
+        case ESP_TLS_CONNECTING:
+            if (cfg->non_block) {
+                ESP_LOGD(TAG, "connecting...");
+                struct timeval tv;
+                ms_to_timeval(cfg->timeout_ms, &tv);
+
+                /* In case of non-blocking I/O, we use the select() API to check whether
+                   connection has been estbalished or not*/
+                if (select(tls->sockfd + 1, &tls->rset, &tls->wset, NULL,
+                    cfg->timeout_ms ? &tv : NULL) == 0) {
+                    ESP_LOGD(TAG, "select() timed out");
+                    return 0;
+                }
+                if (FD_ISSET(tls->sockfd, &tls->rset) || FD_ISSET(tls->sockfd, &tls->wset)) {
+                    int error;
+                    unsigned int len = sizeof(error);
+                    /* pending error check */
+                    if (getsockopt(tls->sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
+                        ESP_LOGD(TAG, "Non blocking connect failed");
+                        tls->conn_state = ESP_TLS_FAIL;
+                        return -1;
+                    }
+                }
+            }
+            /* By now, the connection has been established */
+            ret = create_ssl_handle(tls, hostname, hostlen, cfg);
+            if (ret != 0) {
+                ESP_LOGD(TAG, "create_ssl_handshake failed");
+                tls->conn_state = ESP_TLS_FAIL;
+                return -1;
+            }
+            tls->read = tls_read;
+            tls->write = tls_write;
+            tls->conn_state = ESP_TLS_HANDSHAKE;
+            /* falls through */
+        case ESP_TLS_HANDSHAKE:
+            ESP_LOGD(TAG, "handshake in progress...");
+            ret = mbedtls_ssl_handshake(&tls->ssl);
+            if (ret == 0) {
+                tls->conn_state = ESP_TLS_DONE;
+                return 1;
+            } else {
+                if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
+                    ESP_LOGE(TAG, "mbedtls_ssl_handshake returned -0x%x", -ret);
+                    if (cfg->cacert_pem_buf != NULL) {
+                        /* This is to check whether handshake failed due to invalid certificate*/
+                        verify_certificate(tls);
+                    }
+                    tls->conn_state = ESP_TLS_FAIL;
+                    return -1;
+                }
+                /* Irrespective of blocking or non-blocking I/O, we return on getting MBEDTLS_ERR_SSL_WANT_READ
+                   or MBEDTLS_ERR_SSL_WANT_WRITE during handshake */
+                return 0;
+            }
+            break;
+        case ESP_TLS_FAIL:
+            ESP_LOGE(TAG, "failed to open a new connection");;
+            break;
+        default:
+            ESP_LOGE(TAG, "invalid esp-tls state");
+            break;
+    }
+    return -1;
+}
+
 /**
  * @brief      Create a new TLS/SSL connection
  */
 esp_tls_t *esp_tls_conn_new(const char *hostname, int hostlen, int port, const esp_tls_cfg_t *cfg)
 {
-    int sockfd;
-    if (cfg) {
-        sockfd = esp_tcp_connect(hostname, hostlen, port, cfg->timeout_ms);
-    } else {
-        sockfd = esp_tcp_connect(hostname, hostlen, port, DEFAULT_TIMEOUT_MS);
-    }
-
-    if (sockfd < 0) {
-        return NULL;
-    }
-
     esp_tls_t *tls = (esp_tls_t *)calloc(1, sizeof(esp_tls_t));
     if (!tls) {
-        close(sockfd);
         return NULL;
     }
-    tls->sockfd = sockfd;
-    tls->read = tcp_read;
-    tls->write = tcp_write;
-
-    if (cfg) {
-        if (create_ssl_handle(tls, hostname, hostlen, cfg) != 0) {
+    /* esp_tls_conn_new() API establishes connection in a blocking manner thus this loop ensures that esp_tls_conn_new()
+       API returns only after connection is established unless there is an error*/
+    while (1) {
+        int ret = esp_tls_low_level_conn(hostname, hostlen, port, cfg, tls);
+        if (ret == 1) {
+            return tls;
+        } else if (ret == -1) {
             esp_tls_conn_delete(tls);
+            ESP_LOGE(TAG, "Failed to open new connection");
             return NULL;
         }
-    	tls->read = tls_read;
-    	tls->write = tls_write;
-        if (cfg->non_block == true) {
-            int flags = fcntl(tls->sockfd, F_GETFL, 0);
-            fcntl(tls->sockfd, F_SETFL, flags | O_NONBLOCK);
-        }
     }
+    return NULL;
+}
 
-    return tls;
+/*
+ * @brief      Create a new TLS/SSL non-blocking connection
+ */
+int esp_tls_conn_new_async(const char *hostname, int hostlen, int port, const esp_tls_cfg_t *cfg , esp_tls_t *tls)
+{
+    return esp_tls_low_level_conn(hostname, hostlen, port, cfg, tls);
 }
 
 static int get_port(const char *url, struct http_parser_url *u)
@@ -352,4 +434,19 @@ size_t esp_tls_get_bytes_avail(esp_tls_t *tls)
         return ESP_FAIL;
     }
     return mbedtls_ssl_get_bytes_avail(&tls->ssl);
+}
+
+/**
+ * @brief      Create a new non-blocking TLS/SSL connection with a given "HTTP" url
+ */
+int esp_tls_conn_http_new_async(const char *url, const esp_tls_cfg_t *cfg, esp_tls_t *tls)
+{
+    /* Parse URI */
+    struct http_parser_url u;
+    http_parser_url_init(&u);
+    http_parser_parse_url(url, strlen(url), 0, &u);
+
+    /* Connect to host */
+    return esp_tls_conn_new_async(&url[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len,
+			    get_port(url, &u), cfg, tls);
 }

+ 62 - 10
components/esp-tls/esp_tls.h

@@ -31,6 +31,17 @@
 extern "C" {
 #endif
 
+/**
+ *  @brief ESP-TLS Connection State
+ */
+typedef enum esp_tls_conn_state {
+    ESP_TLS_INIT = 0,
+    ESP_TLS_CONNECTING,
+    ESP_TLS_HANDSHAKE,
+    ESP_TLS_FAIL,
+    ESP_TLS_DONE,
+} esp_tls_conn_state_t;
+
 /**
  * @brief      ESP-TLS configuration parameters 
  */ 
@@ -84,12 +95,18 @@ typedef struct esp_tls {
  
     ssize_t (*write)(struct esp_tls *tls, const char *data, size_t datalen);    /*!< Callback function for writing data to TLS/SSL
                                                                                      connection. */
+
+    esp_tls_conn_state_t  conn_state;                                           /*!< ESP-TLS Connection state */
+
+    fd_set rset;                                                                /*!< read file descriptors */
+
+    fd_set wset;                                                                /*!< write file descriptors */
 } esp_tls_t;
 
 /**
- * @brief      Create a new TLS/SSL connection
+ * @brief      Create a new blocking TLS/SSL connection
  *
- * This function establishes a TLS/SSL connection with the specified host.
+ * This function establishes a TLS/SSL connection with the specified host in blocking manner.
  * 
  * @param[in]  hostname  Hostname of the host.
  * @param[in]  hostlen   Length of hostname.
@@ -103,7 +120,7 @@ typedef struct esp_tls {
 esp_tls_t *esp_tls_conn_new(const char *hostname, int hostlen, int port, const esp_tls_cfg_t *cfg);
 
 /**
- * @brief      Create a new TLS/SSL connection with a given "HTTP" url    
+ * @brief      Create a new blocking TLS/SSL connection with a given "HTTP" url
  *
  * The behaviour is same as esp_tls_conn_new() API. However this API accepts host's url.
  * 
@@ -116,6 +133,40 @@ esp_tls_t *esp_tls_conn_new(const char *hostname, int hostlen, int port, const e
  */
 esp_tls_t *esp_tls_conn_http_new(const char *url, const esp_tls_cfg_t *cfg);
    
+/*
+ * @brief      Create a new non-blocking TLS/SSL connection
+ *
+ * This function initiates a non-blocking TLS/SSL connection with the specified host, but due to
+ * its non-blocking nature, it doesn't wait for the connection to get established.
+ *
+ * @param[in]  hostname  Hostname of the host.
+ * @param[in]  hostlen   Length of hostname.
+ * @param[in]  port      Port number of the host.
+ * @param[in]  cfg       TLS configuration as esp_tls_cfg_t. `non_block` member of
+ *                       this structure should be set to be true.
+ * @param[in]  tls       pointer to esp-tls as esp-tls handle.
+ *
+ * @return     - 1       If connection establishment fails.
+ *             - 0       If connection establishment is in progress.
+ *             - 1       If connection establishment is successful.
+ */
+int esp_tls_conn_new_async(const char *hostname, int hostlen, int port, const esp_tls_cfg_t *cfg, esp_tls_t *tls);
+
+/**
+ * @brief      Create a new non-blocking TLS/SSL connection with a given "HTTP" url
+ *
+ * The behaviour is same as esp_tls_conn_new() API. However this API accepts host's url.
+ *
+ * @param[in]  url     url of host.
+ * @param[in]  tls     pointer to esp-tls as esp-tls handle.
+ * @param[in]  cfg     TLS configuration as esp_tls_cfg_t.
+ *
+ * @return     - 1     If connection establishment fails.
+ *             - 0     If connection establishment is in progress.
+ *             - 1     If connection establishment is successful.
+ */
+int esp_tls_conn_http_new_async(const char *url, const esp_tls_cfg_t *cfg, esp_tls_t *tls);
+
 /**
  * @brief      Write from buffer 'data' into specified tls connection.
  * 
@@ -144,13 +195,13 @@ static inline ssize_t esp_tls_conn_write(esp_tls_t *tls, const void *data, size_
  * @param[in]  datalen  Length of data buffer. 
  *
  * @return
-*             - >0  if read operation was successful, the return value is the number
-*                   of bytes actually read from the TLS/SSL connection.
-*             -  0  if read operation was not successful. The underlying
-*                   connection was closed.
-*             - <0  if read operation was not successful, because either an
-*                   error occured or an action must be taken by the calling process.
-*/
+ *             - >0  if read operation was successful, the return value is the number
+ *                   of bytes actually read from the TLS/SSL connection.
+ *             -  0  if read operation was not successful. The underlying
+ *                   connection was closed.
+ *             - <0  if read operation was not successful, because either an
+ *                   error occured or an action must be taken by the calling process.
+ */
 static inline ssize_t esp_tls_conn_read(esp_tls_t *tls, void  *data, size_t datalen)
 {
     return tls->read(tls, (char *)data, datalen);
@@ -181,6 +232,7 @@ void esp_tls_conn_delete(esp_tls_t *tls);
  */
 size_t esp_tls_get_bytes_avail(esp_tls_t *tls);
 
+
 #ifdef __cplusplus
 }
 #endif

+ 6 - 0
components/esp32/esp_err_to_name.c

@@ -449,6 +449,12 @@ static const esp_err_msg_t esp_err_msg_table[] = {
 #   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
+#   ifdef      ESP_ERR_HTTP_CONNECTING
+    ERR_TBL_IT(ESP_ERR_HTTP_CONNECTING),                    /* 28678 0x7006 HTTP connection hasn't been established yet */
+#   endif
+#   ifdef      ESP_ERR_HTTP_EAGAIN
+    ERR_TBL_IT(ESP_ERR_HTTP_EAGAIN),                        /* 28679 0x7007 Mapping of errno EAGAIN to esp_err_t */
 #   endif
     // components/http_server/include/http_server.h
 #   ifdef      ESP_ERR_HTTPD_BASE

+ 217 - 75
components/esp_http_client/esp_http_client.c

@@ -26,6 +26,7 @@
 #include "sdkconfig.h"
 #include "transport.h"
 #include "esp_http_client.h"
+#include "errno.h"
 
 #ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS
 #include "transport_ssl.h"
@@ -113,6 +114,11 @@ struct esp_http_client {
     int                         buffer_size;
     bool                        disable_auto_redirect;
     esp_http_client_event_t     event;
+    int                         data_written_index;
+    int                         data_write_left;
+    bool                        first_line_prepared;
+    int                         header_index;
+    bool                        is_async;
 };
 
 typedef struct esp_http_client esp_http_client_t;
@@ -124,6 +130,10 @@ static esp_err_t _clear_connection_info(esp_http_client_handle_t client);
 #define DEFAULT_HTTP_PORT (80)
 #define DEFAULT_HTTPS_PORT (443)
 
+#define ASYNC_TRANS_CONNECT_FAIL -1
+#define ASYNC_TRANS_CONNECTING 0
+#define ASYNC_TRANS_CONNECT_PASS 1
+
 static const char *DEFAULT_HTTP_USER_AGENT = "ESP32 HTTP Client/1.0";
 static const char *DEFAULT_HTTP_PROTOCOL = "HTTP/1.1";
 static const char *DEFAULT_HTTP_PATH = "/";
@@ -156,6 +166,11 @@ enum HttpStatus_Code
     HttpStatus_Unauthorized      = 401
 };
 
+
+static esp_err_t esp_http_client_request_send(esp_http_client_handle_t client);
+static esp_err_t esp_http_client_connect(esp_http_client_handle_t client);
+static esp_err_t esp_http_client_send_post_data(esp_http_client_handle_t client);
+
 static esp_err_t http_dispatch_event(esp_http_client_t *client, esp_http_client_event_id_t event_id, void *data, int len)
 {
     esp_http_client_event_t *event = &client->event;
@@ -359,6 +374,9 @@ static esp_err_t _set_config(esp_http_client_handle_t client, const esp_http_cli
     if (client->timeout_ms == 0) {
         client->timeout_ms = DEFAULT_TIMEOUT_MS;
     }
+    if (config->is_async) {
+        client->is_async = true;
+    }
 
     return ESP_OK;
 }
@@ -399,6 +417,7 @@ static esp_err_t esp_http_client_prepare(esp_http_client_handle_t client)
 {
     client->process_again = 0;
     client->response->data_process = 0;
+    client->first_line_prepared = false;
     http_parser_init(client->parser, HTTP_RESPONSE);
     if (client->connection_info.username) {
         char *auth_response = NULL;
@@ -781,55 +800,89 @@ int esp_http_client_read(esp_http_client_handle_t client, char *buffer, int len)
 esp_err_t esp_http_client_perform(esp_http_client_handle_t client)
 {
     esp_err_t err;
-    if (client == NULL) {
-        return ESP_ERR_INVALID_ARG;
-    }
     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 (client->process_again) {
+            esp_http_client_prepare(client);
         }
+        switch (client->state) {
+        /* In case of blocking esp_http_client_perform(), the following states will fall through one after the after;
+           in case of non-blocking esp_http_client_perform(), if there is an error condition, like EINPROGRESS or EAGAIN,
+           then the esp_http_client_perform() API will return ESP_ERR_HTTP_EAGAIN error. The user may call
+           esp_http_client_perform API again, and for this reason, we maintain the states */
+            case HTTP_STATE_INIT:
+                if ((err = esp_http_client_connect(client)) != ESP_OK) {
+                    if (client->is_async && err == ESP_ERR_HTTP_CONNECTING) {
+                        return ESP_ERR_HTTP_EAGAIN;
+                    }
+                    return err;
+                }
+                /* falls through */
+            case HTTP_STATE_CONNECTED:
+                if ((err = esp_http_client_request_send(client)) != ESP_OK) {
+                    if (client->is_async && errno == EAGAIN) {
+                        return ESP_ERR_HTTP_EAGAIN;
+                    }
+                    return err;
+                }
+                /* falls through */
+            case HTTP_STATE_REQ_COMPLETE_HEADER:
+                if ((err = esp_http_client_send_post_data(client)) != ESP_OK) {
+                    if (client->is_async && errno == EAGAIN) {
+                        return ESP_ERR_HTTP_EAGAIN;
+                    }
+                    return err;
+                }
+                /* falls through */
+            case HTTP_STATE_REQ_COMPLETE_DATA:
+                if (esp_http_client_fetch_headers(client) < 0) {
+                    if (client->is_async && errno == EAGAIN) {
+                        return ESP_ERR_HTTP_EAGAIN;
+                    }
+                    return ESP_ERR_HTTP_FETCH_HEADER;
+                }
+                /* falls through */
+            case HTTP_STATE_RES_COMPLETE_HEADER:
+                if ((err = esp_http_check_response(client)) != ESP_OK) {
+                    ESP_LOGE(TAG, "Error response");
+                    return err;
+                }
+                while (client->response->is_chunked && !client->is_chunk_complete) {
+                    if (esp_http_client_get_data(client) <= 0) {
+                        if (client->is_async && errno == EAGAIN) {
+                            return ESP_ERR_HTTP_EAGAIN;
+                        }
+                        ESP_LOGD(TAG, "Read finish or server requests close");
+                        break;
+                    }
+                }
+                while (client->response->data_process < client->response->content_length) {
+                    if (esp_http_client_get_data(client) <= 0) {
+                        if (client->is_async && errno == EAGAIN) {
+                            return ESP_ERR_HTTP_EAGAIN;
+                        }
+                        ESP_LOGD(TAG, "Read finish or server requests close");
+                        break;
+                    }
+                }
+                http_dispatch_event(client, HTTP_EVENT_ON_FINISH, NULL, 0);
 
-        if ((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");
+                if (!http_should_keep_alive(client->parser)) {
+                    ESP_LOGD(TAG, "Close connection");
+                    esp_http_client_close(client);
+                } else {
+                    if (client->state > HTTP_STATE_CONNECTED) {
+                        client->state = HTTP_STATE_CONNECTED;
+                        client->first_line_prepared = false;
+                    }
+                }
                 break;
-            }
-        }
-        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");
+                default:
                 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) {
@@ -842,7 +895,7 @@ int esp_http_client_fetch_headers(esp_http_client_handle_t client)
 
     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) {
+        if (buffer->len < 0) {
             return ESP_FAIL;
         }
         http_parser_execute(client->parser, client->parser_settings, buffer->data, buffer->len);
@@ -855,9 +908,10 @@ int esp_http_client_fetch_headers(esp_http_client_handle_t client)
     return client->response->content_length;
 }
 
-esp_err_t esp_http_client_open(esp_http_client_handle_t client, int write_len)
+static esp_err_t esp_http_client_connect(esp_http_client_handle_t client)
 {
     esp_err_t err;
+
     if (client->state == HTTP_STATE_UNINIT) {
         ESP_LOGE(TAG, "Client has not been initialized");
         return ESP_ERR_INVALID_STATE;
@@ -881,79 +935,165 @@ esp_err_t esp_http_client_open(esp_http_client_handle_t client, int write_len)
 #endif
             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");
-            return ESP_ERR_HTTP_CONNECT;
+        if (!client->is_async) {
+            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;
+            }
+        } else {
+            int ret = transport_connect_async(client->transport, client->connection_info.host, client->connection_info.port, client->timeout_ms);
+            if (ret == ASYNC_TRANS_CONNECT_FAIL) {
+                ESP_LOGE(TAG, "Connection failed");
+                return ESP_ERR_HTTP_CONNECT;
+            } else if (ret == ASYNC_TRANS_CONNECTING) {
+                ESP_LOGD(TAG, "Connection not yet established");
+                return ESP_ERR_HTTP_CONNECTING;
+            }
         }
-        http_dispatch_event(client, HTTP_EVENT_ON_CONNECTED, NULL, 0);
         client->state = HTTP_STATE_CONNECTED;
+        http_dispatch_event(client, HTTP_EVENT_ON_CONNECTED, NULL, 0);
     }
+    return ESP_OK;
+}
 
+static int http_client_prepare_first_line(esp_http_client_handle_t client, int write_len)
+{
     if (write_len >= 0) {
         http_header_set_format(client->request->headers, "Content-Length", "%d", write_len);
-    } else if (write_len < 0) {
+    } else {
         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) {
+    int first_line_len = snprintf(client->request->buffer->data,
+                                  client->buffer_size, "%s %s",
+                                  method,
+                                  client->connection_info.path);
+    if (first_line_len >= client->buffer_size) {
         ESP_LOGE(TAG, "Out of buffer");
-        return ESP_ERR_HTTP_CONNECT;
+        return -1;
     }
 
     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) {
+        first_line_len += snprintf(client->request->buffer->data + first_line_len,
+                                   client->buffer_size - first_line_len, "?%s", client->connection_info.query);
+        if (first_line_len >= client->buffer_size) {
             ESP_LOGE(TAG, "Out of buffer");
-            return ESP_ERR_HTTP_CONNECT;
+            return -1;
+
         }
     }
-    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) {
+    first_line_len += snprintf(client->request->buffer->data + first_line_len,
+                               client->buffer_size - first_line_len, " %s\r\n", DEFAULT_HTTP_PROTOCOL);
+    if (first_line_len >= client->buffer_size) {
         ESP_LOGE(TAG, "Out of buffer");
-        return ESP_ERR_HTTP_CONNECT;
+        return -1;
+    }
+    return first_line_len;
+}
+
+static esp_err_t esp_http_client_request_send(esp_http_client_handle_t client)
+{
+    int first_line_len = 0;
+    if (!client->first_line_prepared) {
+        if ((first_line_len = http_client_prepare_first_line(client, client->post_len)) < 0) {
+            return first_line_len;
+        }
+        client->first_line_prepared = true;
+        client->header_index = 0;
+        client->data_written_index = 0;
+        client->data_write_left = 0;
+    }
+
+    if (client->data_write_left > 0) {
+        /* sending leftover data from previous call to esp_http_client_request_send() API */
+        int wret = 0;
+        if (((wret = esp_http_client_write(client, client->request->buffer->data + client->data_written_index, client->data_write_left)) < 0)) {
+            ESP_LOGE(TAG, "Error write request");
+            return ESP_ERR_HTTP_WRITE_DATA;
+        }
+        client->data_write_left -= wret;
+        client->data_written_index += wret;
+        if (client->is_async && client->data_write_left > 0) {
+            return ESP_ERR_HTTP_WRITE_DATA;      /* In case of EAGAIN error, we return ESP_ERR_HTTP_WRITE_DATA,
+                                                 and the handling of EAGAIN should be done in the higher level APIs. */
+        }
     }
-    wlen -= first_line;
 
-    while ((header_index = http_header_generate_string(client->request->headers, header_index, client->request->buffer->data + first_line, &wlen))) {
+    int wlen = client->buffer_size - first_line_len;
+    while ((client->header_index = http_header_generate_string(client->request->headers, client->header_index, client->request->buffer->data + first_line_len, &wlen))) {
         if (wlen <= 0) {
             break;
         }
-        if (first_line) {
-            wlen += first_line;
-            first_line = 0;
+        if (first_line_len) {
+            wlen += first_line_len;
+            first_line_len = 0;
         }
         client->request->buffer->data[wlen] = 0;
-        ESP_LOGD(TAG, "Write header[%d]: %s", header_index, client->request->buffer->data);
+        ESP_LOGD(TAG, "Write header[%d]: %s", client->header_index, client->request->buffer->data);
 
-        int widx = 0, wret = 0;
-        while (wlen > 0) {
-            wret = transport_write(client->transport, client->request->buffer->data + widx, wlen, client->timeout_ms);
+        client->data_write_left = wlen;
+        client->data_written_index = 0;
+        while (client->data_write_left > 0) {
+            int wret = transport_write(client->transport, client->request->buffer->data + client->data_written_index, client->data_write_left, client->timeout_ms);
             if (wret <= 0) {
                 ESP_LOGE(TAG, "Error write request");
                 esp_http_client_close(client);
                 return ESP_ERR_HTTP_WRITE_DATA;
             }
-            widx += wret;
-            wlen -= wret;
+            client->data_write_left -= wret;
+            client->data_written_index += wret;
         }
         wlen = client->buffer_size;
     }
+
+    client->data_written_index = 0;
+    client->data_write_left = client->post_len;
     client->state = HTTP_STATE_REQ_COMPLETE_HEADER;
     return ESP_OK;
 }
 
+static esp_err_t esp_http_client_send_post_data(esp_http_client_handle_t client)
+{
+    if (client->state != HTTP_STATE_REQ_COMPLETE_HEADER) {
+        ESP_LOGE(TAG, "Invalid state");
+        return ESP_ERR_INVALID_STATE;
+    }
+    if (!(client->post_data && client->post_len)) {
+        goto success;
+    }
+
+    int wret = esp_http_client_write(client, client->post_data + client->data_written_index, client->data_write_left);
+    if (wret < 0) {
+        return wret;
+    }
+    client->data_write_left -= wret;
+    client->data_written_index += wret;
+
+    if (client->data_write_left <= 0) {
+        goto success;
+    } else {
+        return ESP_ERR_HTTP_WRITE_DATA;
+    }
+
+success:
+    client->state = HTTP_STATE_REQ_COMPLETE_DATA;
+    return ESP_OK;
+}
+
+esp_err_t esp_http_client_open(esp_http_client_handle_t client, int write_len)
+{
+    esp_err_t err;
+    if ((err = esp_http_client_connect(client)) != ESP_OK) {
+        return err;
+    }
+    if ((err = esp_http_client_request_send(client)) != ESP_OK) {
+        return err; 
+    }
+    return ESP_OK;
+}
 
 int esp_http_client_write(esp_http_client_handle_t client, const char *buffer, int len)
 {
@@ -964,7 +1104,9 @@ int esp_http_client_write(esp_http_client_handle_t client, const char *buffer, i
     int wlen = 0, widx = 0;
     while (len > 0) {
         wlen = transport_write(client->transport, buffer + widx, len, client->timeout_ms);
-        if (wlen <= 0) {
+        /* client->async_block is initialised in case of non-blocking IO, and in this case we return how
+           much ever data was written by the transport_write() API. */
+        if (client->is_async || wlen <= 0) {
             return wlen;
         }
         widx += wlen;

+ 7 - 1
components/esp_http_client/include/esp_http_client.h

@@ -114,6 +114,7 @@ typedef struct {
     esp_http_client_transport_t transport_type;           /*!< HTTP transport type, see `esp_http_client_transport_t` */
     int                         buffer_size;              /*!< HTTP buffer size (both send and receive) */
     void                        *user_data;               /*!< HTTP user_data context */
+    bool                        is_async;                 /*!< Set asynchronous mode */
 } esp_http_client_config_t;
 
 
@@ -123,6 +124,8 @@ typedef struct {
 #define ESP_ERR_HTTP_WRITE_DATA         (ESP_ERR_HTTP_BASE + 3)     /*!< Error write HTTP data */
 #define ESP_ERR_HTTP_FETCH_HEADER       (ESP_ERR_HTTP_BASE + 4)     /*!< Error read HTTP header from server */
 #define ESP_ERR_HTTP_INVALID_TRANSPORT  (ESP_ERR_HTTP_BASE + 5)     /*!< There are no transport support for the input scheme */
+#define ESP_ERR_HTTP_CONNECTING         (ESP_ERR_HTTP_BASE + 6)     /*!< HTTP connection hasn't been established yet */
+#define ESP_ERR_HTTP_EAGAIN             (ESP_ERR_HTTP_BASE + 7)     /*!< Mapping of errno EAGAIN to esp_err_t */
 
 /**
  * @brief      Start a HTTP session
@@ -141,7 +144,10 @@ esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *co
 /**
  * @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.
+ *             esp_http_client_perform performs the entire request in either blocking or non-blocking manner. By default, the API performs request in a blocking manner and returns when done,
+ *             or if it failed, and in non-blocking manner, it returns if EAGAIN/EWOULDBLOCK or EINPROGRESS is encountered, or if it failed. And in case of non-blocking request,
+ *             the user may call this API multiple times unless request & response is complete or there is a failure. To enable non-blocking esp_http_client_perform(), `is_async` member of esp_http_client_config_t
+ *             must be set while making a call to esp_http_client_init() API.
  *             You can do any amount of calls to esp_http_client_perform while using the same esp_http_client_handle_t. The underlying connection may be kept open if the server allows it.
  *             If you intend to transfer more than one file, you are even encouraged to do so.
  *             esp_http_client will then attempt to re-use the same connection for the following transfers, thus making the operations faster, less CPU intense and using less network resources.

+ 29 - 0
components/tcp_transport/include/transport.h

@@ -30,6 +30,7 @@ typedef int (*io_func)(transport_handle_t t, const char *buffer, int len, int ti
 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);
+typedef int (*connect_async_func)(transport_handle_t t, const char *host, int port, int timeout_ms);
 typedef transport_handle_t (*payload_transfer_func)(transport_handle_t);
 
 /**
@@ -138,6 +139,20 @@ esp_err_t transport_set_default_port(transport_handle_t t, int port);
  */
 int transport_connect(transport_handle_t t, const char *host, int port, int timeout_ms);
 
+/**
+ * @brief      Non-blocking 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_async(transport_handle_t t, const char *host, int port, int timeout_ms);
+
 /**
  * @brief      Transport read function
  *
@@ -259,6 +274,20 @@ esp_err_t transport_set_func(transport_handle_t t,
                              poll_func _poll_write,
                              trans_func _destroy,
                              payload_transfer_func _parrent_transport);
+
+
+/**
+ * @brief      Set transport functions for the transport handle
+ *
+ * @param[in]  t                    The transport handle
+ * @param[in]  _connect_async_func  The connect_async function pointer
+ *
+ * @return
+ *     - ESP_OK
+ *     - ESP_FAIL
+ */
+esp_err_t transport_set_async_connect_func(transport_handle_t t, connect_async_func _connect_async_func);
+
 #ifdef __cplusplus
 }
 #endif

+ 21 - 1
components/tcp_transport/transport.c

@@ -40,6 +40,7 @@ struct transport_item_t {
     poll_func       _poll_read;     /*!< Poll and read */
     poll_func       _poll_write;    /*!< Poll and write */
     trans_func      _destroy;       /*!< Destroy and free transport */
+    connect_async_func _connect_async;      /*!< non-blocking connect function of this transport */
     payload_transfer_func  _parrent_transfer;       /*!< Function returning underlying transport layer */
 
     STAILQ_ENTRY(transport_item_t) next;
@@ -145,6 +146,15 @@ int transport_connect(transport_handle_t t, const char *host, int port, int time
     return ret;
 }
 
+int transport_connect_async(transport_handle_t t, const char *host, int port, int timeout_ms)
+{
+    int ret = -1;
+    if (t && t->_connect) {
+        return t->_connect_async(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) {
@@ -222,6 +232,7 @@ esp_err_t transport_set_func(transport_handle_t t,
     t->_poll_read = _poll_read;
     t->_poll_write = _poll_write;
     t->_destroy = _destroy;
+    t->_connect_async = NULL;
     t->_parrent_transfer = _parrent_transport;
     return ESP_OK;
 }
@@ -246,4 +257,13 @@ esp_err_t transport_set_default_port(transport_handle_t t, int port)
 transport_handle_t transport_get_handle(transport_handle_t t)
 {
     return t;
-}
+}
+
+esp_err_t transport_set_async_connect_func(transport_handle_t t, connect_async_func _connect_async_func)
+{
+    if (t == NULL) {
+        return ESP_FAIL;
+    }
+    t->_connect_async = _connect_async_func;
+    return ESP_OK;
+}

+ 38 - 12
components/tcp_transport/transport_ssl.c

@@ -26,33 +26,58 @@
 #include "transport_utils.h"
 
 static const char *TAG = "TRANS_SSL";
+
+typedef enum {
+    TRANS_SSL_INIT = 0,
+    TRANS_SSL_CONNECTING,
+} transport_ssl_conn_state_t;
+
 /**
  *  mbedtls specific transport data
  */
 typedef struct {
     esp_tls_t                *tls;
-    void                     *cert_pem_data;
-    int                      cert_pem_len;
+    esp_tls_cfg_t            cfg;
     bool                     ssl_initialized;
     bool                     verify_server;
+    transport_ssl_conn_state_t conn_state;
 } transport_ssl_t;
 
 transport_handle_t transport_get_handle(transport_handle_t t);
 
 static int ssl_close(transport_handle_t t);
 
+static int ssl_connect_async(transport_handle_t t, const char *host, int port, int timeout_ms)
+{
+    transport_ssl_t *ssl = transport_get_context_data(t);
+    if (ssl->conn_state == TRANS_SSL_INIT) {
+        if (ssl->cfg.cacert_pem_buf) {
+            ssl->verify_server = true;
+        }
+        ssl->cfg.timeout_ms = timeout_ms;
+        ssl->cfg.non_block = true;
+        ssl->ssl_initialized = true;
+        ssl->tls = calloc(1, sizeof(esp_tls_t));
+        if (!ssl->tls) {
+            return -1;
+        }
+        ssl->conn_state = TRANS_SSL_CONNECTING;
+    }
+    if (ssl->conn_state == TRANS_SSL_CONNECTING) {
+        return esp_tls_conn_new_async(host, strlen(host), port, &ssl->cfg, ssl->tls);
+    }
+    return 0;
+}
+
 static int ssl_connect(transport_handle_t t, const char *host, int port, int timeout_ms)
 {
     transport_ssl_t *ssl = transport_get_context_data(t);
-    esp_tls_cfg_t cfg = { 0 };
-    if (ssl->cert_pem_data) {
+    if (ssl->cfg.cacert_pem_buf) {
         ssl->verify_server = true;
-        cfg.cacert_pem_buf = ssl->cert_pem_data;
-        cfg.cacert_pem_bytes = ssl->cert_pem_len + 1;
     }
-    cfg.timeout_ms = timeout_ms;
+    ssl->cfg.timeout_ms = timeout_ms;
     ssl->ssl_initialized = true;
-    ssl->tls = esp_tls_conn_new(host, strlen(host), port, &cfg);
+    ssl->tls = esp_tls_conn_new(host, strlen(host), port, &ssl->cfg);
     if (!ssl->tls) {
         ESP_LOGE(TAG, "Failed to open a new connection");
         return -1;
@@ -94,7 +119,7 @@ static int ssl_write(transport_handle_t t, const char *buffer, int len, int time
     }
     ret = esp_tls_conn_write(ssl->tls, (const unsigned char *) buffer, len);
     if (ret <= 0) {
-        ESP_LOGE(TAG, "mbedtls_ssl_write error, errno=%s", strerror(errno));
+        ESP_LOGE(TAG, "esp_tls_conn_write error, errno=%s", strerror(errno));
     }
     return ret;
 }
@@ -111,7 +136,7 @@ static int ssl_read(transport_handle_t t, char *buffer, int len, int timeout_ms)
     }
     ret = esp_tls_conn_read(ssl->tls, (unsigned char *)buffer, len);
     if (ret <= 0) {
-        ESP_LOGE(TAG, "mbedtls_ssl_read error, errno=%s", strerror(errno));
+        ESP_LOGE(TAG, "esp_tls_conn_read error, errno=%s", strerror(errno));
     }
     return ret;
 }
@@ -140,8 +165,8 @@ 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;
+        ssl->cfg.cacert_pem_buf = (void *)data;
+        ssl->cfg.cacert_pem_bytes = len + 1;
     }
 }
 
@@ -152,6 +177,7 @@ transport_handle_t transport_ssl_init()
     TRANSPORT_MEM_CHECK(TAG, ssl, return NULL);
     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, transport_get_handle);
+    transport_set_async_connect_func(t, ssl_connect_async);
     return t;
 }
 

+ 33 - 0
examples/protocols/esp_http_client/main/esp_http_client_example.c

@@ -342,6 +342,38 @@ static void http_perform_as_stream_reader()
     free(buffer);
 }
 
+static void https_async()
+{
+    esp_http_client_config_t config = {
+        .url = "https://postman-echo.com/post",
+        .event_handler = _http_event_handler,
+        .is_async = true,
+        .timeout_ms = 5000,
+    };
+    esp_http_client_handle_t client = esp_http_client_init(&config);
+    esp_err_t err;
+    const char *post_data = "Using a Palantír requires a person with great strength of will and wisdom. The Palantíri were meant to "
+                            "be used by the Dúnedain to communicate throughout the Realms in Exile. During the War of the Ring, "
+                            "the Palantíri were used by many individuals. Sauron used the Ithil-stone to take advantage of the users "
+                            "of the other two stones, the Orthanc-stone and Anor-stone, but was also susceptible to deception himself.";
+    esp_http_client_set_method(client, HTTP_METHOD_POST);
+    esp_http_client_set_post_field(client, post_data, strlen(post_data));
+    while (1) {
+        err = esp_http_client_perform(client);
+        if (err != ESP_ERR_HTTP_EAGAIN) {
+            break;
+        }
+    }
+    if (err == ESP_OK) {
+        ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %d",
+                esp_http_client_get_status_code(client),
+                esp_http_client_get_content_length(client));
+    } else {
+        ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err));
+    }
+    esp_http_client_cleanup(client);
+}
+
 static void http_test_task(void *pvParameters)
 {
     app_wifi_wait_connected();
@@ -356,6 +388,7 @@ static void http_test_task(void *pvParameters)
     http_redirect_to_https();
     http_download_chunk();
     http_perform_as_stream_reader();
+    https_async();
     ESP_LOGI(TAG, "Finish http example");
     vTaskDelete(NULL);
 }