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
This commit is contained in:
commit
dc135ed1f6
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
#include <http_parser.h>
|
#include <http_parser.h>
|
||||||
#include "esp_tls.h"
|
#include "esp_tls.h"
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
static const char *TAG = "esp-tls";
|
static const char *TAG = "esp-tls";
|
||||||
|
|
||||||
|
@ -32,8 +33,6 @@ static const char *TAG = "esp-tls";
|
||||||
#define ESP_LOGE(TAG, ...) printf(__VA_ARGS__);
|
#define ESP_LOGE(TAG, ...) printf(__VA_ARGS__);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define DEFAULT_TIMEOUT_MS -1
|
|
||||||
|
|
||||||
static struct addrinfo *resolve_host_name(const char *host, size_t hostlen)
|
static struct addrinfo *resolve_host_name(const char *host, size_t hostlen)
|
||||||
{
|
{
|
||||||
struct addrinfo hints;
|
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;
|
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);
|
struct addrinfo *res = resolve_host_name(host, hostlen);
|
||||||
if (!res) {
|
if (!res) {
|
||||||
return -1;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ret = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
|
||||||
if (ret < 0) {
|
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);
|
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;
|
goto err_freeaddr;
|
||||||
}
|
}
|
||||||
int fd = ret;
|
*sockfd = fd;
|
||||||
|
|
||||||
void *addr_ptr;
|
void *addr_ptr;
|
||||||
if (res->ai_family == AF_INET) {
|
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;
|
goto err_freesocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeout_ms >= 0) {
|
if (cfg) {
|
||||||
struct timeval tv;
|
if (cfg->timeout_ms >= 0) {
|
||||||
ms_to_timeval(timeout_ms, &tv);
|
struct timeval tv;
|
||||||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(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);
|
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);
|
ESP_LOGE(TAG, "Failed to connnect to host (errno %d)", errno);
|
||||||
goto err_freesocket;
|
goto err_freesocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
freeaddrinfo(res);
|
freeaddrinfo(res);
|
||||||
return fd;
|
return 0;
|
||||||
|
|
||||||
err_freesocket:
|
err_freesocket:
|
||||||
close(fd);
|
close(fd);
|
||||||
err_freeaddr:
|
err_freeaddr:
|
||||||
freeaddrinfo(res);
|
freeaddrinfo(res);
|
||||||
return -1;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void verify_certificate(esp_tls_t *tls)
|
static void verify_certificate(esp_tls_t *tls)
|
||||||
{
|
{
|
||||||
int flags;
|
int flags;
|
||||||
char buf[100];
|
char buf[100];
|
||||||
if ((flags = mbedtls_ssl_get_verify_result(&tls->ssl)) != 0) {
|
if ((flags = mbedtls_ssl_get_verify_result(&tls->ssl)) != 0) {
|
||||||
ESP_LOGI(TAG, "Failed to verify peer certificate!");
|
ESP_LOGI(TAG, "Failed to verify peer certificate!");
|
||||||
bzero(buf, sizeof(buf));
|
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);
|
ESP_LOGE(TAG, "mbedtls_ssl_setup returned -0x%x\n\n", -ret);
|
||||||
goto exit;
|
goto exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
mbedtls_ssl_set_bio(&tls->ssl, &tls->server_fd, mbedtls_net_send, mbedtls_net_recv, NULL);
|
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;
|
return 0;
|
||||||
exit:
|
exit:
|
||||||
mbedtls_cleanup(tls);
|
mbedtls_cleanup(tls);
|
||||||
|
@ -275,45 +270,132 @@ static ssize_t tls_write(esp_tls_t *tls, const char *data, size_t datalen)
|
||||||
return ret;
|
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
|
* @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)
|
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));
|
esp_tls_t *tls = (esp_tls_t *)calloc(1, sizeof(esp_tls_t));
|
||||||
if (!tls) {
|
if (!tls) {
|
||||||
close(sockfd);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
tls->sockfd = sockfd;
|
/* esp_tls_conn_new() API establishes connection in a blocking manner thus this loop ensures that esp_tls_conn_new()
|
||||||
tls->read = tcp_read;
|
API returns only after connection is established unless there is an error*/
|
||||||
tls->write = tcp_write;
|
while (1) {
|
||||||
|
int ret = esp_tls_low_level_conn(hostname, hostlen, port, cfg, tls);
|
||||||
if (cfg) {
|
if (ret == 1) {
|
||||||
if (create_ssl_handle(tls, hostname, hostlen, cfg) != 0) {
|
return tls;
|
||||||
|
} else if (ret == -1) {
|
||||||
esp_tls_conn_delete(tls);
|
esp_tls_conn_delete(tls);
|
||||||
|
ESP_LOGE(TAG, "Failed to open new connection");
|
||||||
return NULL;
|
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)
|
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 ESP_FAIL;
|
||||||
}
|
}
|
||||||
return mbedtls_ssl_get_bytes_avail(&tls->ssl);
|
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);
|
||||||
}
|
}
|
|
@ -31,6 +31,17 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#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
|
* @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
|
ssize_t (*write)(struct esp_tls *tls, const char *data, size_t datalen); /*!< Callback function for writing data to TLS/SSL
|
||||||
connection. */
|
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;
|
} 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] hostname Hostname of the host.
|
||||||
* @param[in] hostlen Length of hostname.
|
* @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);
|
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.
|
* 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);
|
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.
|
* @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.
|
* @param[in] datalen Length of data buffer.
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
* - >0 if read operation was successful, the return value is the number
|
* - >0 if read operation was successful, the return value is the number
|
||||||
* of bytes actually read from the TLS/SSL connection.
|
* of bytes actually read from the TLS/SSL connection.
|
||||||
* - 0 if read operation was not successful. The underlying
|
* - 0 if read operation was not successful. The underlying
|
||||||
* connection was closed.
|
* connection was closed.
|
||||||
* - <0 if read operation was not successful, because either an
|
* - <0 if read operation was not successful, because either an
|
||||||
* error occured or an action must be taken by the calling process.
|
* 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)
|
static inline ssize_t esp_tls_conn_read(esp_tls_t *tls, void *data, size_t datalen)
|
||||||
{
|
{
|
||||||
return tls->read(tls, (char *)data, 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);
|
size_t esp_tls_get_bytes_avail(esp_tls_t *tls);
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -449,6 +449,12 @@ static const esp_err_msg_t esp_err_msg_table[] = {
|
||||||
# ifdef ESP_ERR_HTTP_INVALID_TRANSPORT
|
# ifdef ESP_ERR_HTTP_INVALID_TRANSPORT
|
||||||
ERR_TBL_IT(ESP_ERR_HTTP_INVALID_TRANSPORT), /* 28677 0x7005 There are no transport support for the input
|
ERR_TBL_IT(ESP_ERR_HTTP_INVALID_TRANSPORT), /* 28677 0x7005 There are no transport support for the input
|
||||||
scheme */
|
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
|
# endif
|
||||||
// components/http_server/include/http_server.h
|
// components/http_server/include/http_server.h
|
||||||
# ifdef ESP_ERR_HTTPD_BASE
|
# ifdef ESP_ERR_HTTPD_BASE
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
#include "transport.h"
|
#include "transport.h"
|
||||||
#include "esp_http_client.h"
|
#include "esp_http_client.h"
|
||||||
|
#include "errno.h"
|
||||||
|
|
||||||
#ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS
|
#ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS
|
||||||
#include "transport_ssl.h"
|
#include "transport_ssl.h"
|
||||||
|
@ -113,6 +114,11 @@ struct esp_http_client {
|
||||||
int buffer_size;
|
int buffer_size;
|
||||||
bool disable_auto_redirect;
|
bool disable_auto_redirect;
|
||||||
esp_http_client_event_t event;
|
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;
|
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_HTTP_PORT (80)
|
||||||
#define DEFAULT_HTTPS_PORT (443)
|
#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_USER_AGENT = "ESP32 HTTP Client/1.0";
|
||||||
static const char *DEFAULT_HTTP_PROTOCOL = "HTTP/1.1";
|
static const char *DEFAULT_HTTP_PROTOCOL = "HTTP/1.1";
|
||||||
static const char *DEFAULT_HTTP_PATH = "/";
|
static const char *DEFAULT_HTTP_PATH = "/";
|
||||||
|
@ -156,6 +166,11 @@ enum HttpStatus_Code
|
||||||
HttpStatus_Unauthorized = 401
|
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)
|
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;
|
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) {
|
if (client->timeout_ms == 0) {
|
||||||
client->timeout_ms = DEFAULT_TIMEOUT_MS;
|
client->timeout_ms = DEFAULT_TIMEOUT_MS;
|
||||||
}
|
}
|
||||||
|
if (config->is_async) {
|
||||||
|
client->is_async = true;
|
||||||
|
}
|
||||||
|
|
||||||
return ESP_OK;
|
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->process_again = 0;
|
||||||
client->response->data_process = 0;
|
client->response->data_process = 0;
|
||||||
|
client->first_line_prepared = false;
|
||||||
http_parser_init(client->parser, HTTP_RESPONSE);
|
http_parser_init(client->parser, HTTP_RESPONSE);
|
||||||
if (client->connection_info.username) {
|
if (client->connection_info.username) {
|
||||||
char *auth_response = NULL;
|
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 esp_http_client_perform(esp_http_client_handle_t client)
|
||||||
{
|
{
|
||||||
esp_err_t err;
|
esp_err_t err;
|
||||||
if (client == NULL) {
|
|
||||||
return ESP_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
do {
|
do {
|
||||||
if ((err = esp_http_client_open(client, client->post_len)) != ESP_OK) {
|
if (client->process_again) {
|
||||||
return err;
|
esp_http_client_prepare(client);
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
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) {
|
if (!http_should_keep_alive(client->parser)) {
|
||||||
ESP_LOGE(TAG, "Error response");
|
ESP_LOGD(TAG, "Close connection");
|
||||||
return err;
|
esp_http_client_close(client);
|
||||||
}
|
} else {
|
||||||
while (client->response->is_chunked && !client->is_chunk_complete) {
|
if (client->state > HTTP_STATE_CONNECTED) {
|
||||||
if (esp_http_client_get_data(client) <= 0) {
|
client->state = HTTP_STATE_CONNECTED;
|
||||||
ESP_LOGD(TAG, "Read finish or server requests close");
|
client->first_line_prepared = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
default:
|
||||||
}
|
|
||||||
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;
|
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);
|
} while (client->process_again);
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int esp_http_client_fetch_headers(esp_http_client_handle_t client)
|
int esp_http_client_fetch_headers(esp_http_client_handle_t client)
|
||||||
{
|
{
|
||||||
if (client->state < HTTP_STATE_REQ_COMPLETE_HEADER) {
|
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) {
|
while (client->state < HTTP_STATE_RES_COMPLETE_HEADER) {
|
||||||
buffer->len = transport_read(client->transport, buffer->data, client->buffer_size, client->timeout_ms);
|
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;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
http_parser_execute(client->parser, client->parser_settings, buffer->data, buffer->len);
|
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;
|
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;
|
esp_err_t err;
|
||||||
|
|
||||||
if (client->state == HTTP_STATE_UNINIT) {
|
if (client->state == HTTP_STATE_UNINIT) {
|
||||||
ESP_LOGE(TAG, "Client has not been initialized");
|
ESP_LOGE(TAG, "Client has not been initialized");
|
||||||
return ESP_ERR_INVALID_STATE;
|
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
|
#endif
|
||||||
return ESP_ERR_HTTP_INVALID_TRANSPORT;
|
return ESP_ERR_HTTP_INVALID_TRANSPORT;
|
||||||
}
|
}
|
||||||
if (transport_connect(client->transport, client->connection_info.host, client->connection_info.port, client->timeout_ms) < 0) {
|
if (!client->is_async) {
|
||||||
ESP_LOGE(TAG, "Connection failed");
|
if (transport_connect(client->transport, client->connection_info.host, client->connection_info.port, client->timeout_ms) < 0) {
|
||||||
return ESP_ERR_HTTP_CONNECT;
|
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;
|
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) {
|
if (write_len >= 0) {
|
||||||
http_header_set_format(client->request->headers, "Content-Length", "%d", write_len);
|
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_header(client, "Transfer-Encoding", "chunked");
|
||||||
esp_http_client_set_method(client, HTTP_METHOD_POST);
|
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];
|
const char *method = HTTP_METHOD_MAPPING[client->connection_info.method];
|
||||||
|
|
||||||
int first_line = snprintf(client->request->buffer->data,
|
int first_line_len = snprintf(client->request->buffer->data,
|
||||||
client->buffer_size, "%s %s",
|
client->buffer_size, "%s %s",
|
||||||
method,
|
method,
|
||||||
client->connection_info.path);
|
client->connection_info.path);
|
||||||
if (first_line > client->buffer_size) {
|
if (first_line_len >= client->buffer_size) {
|
||||||
ESP_LOGE(TAG, "Out of buffer");
|
ESP_LOGE(TAG, "Out of buffer");
|
||||||
return ESP_ERR_HTTP_CONNECT;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client->connection_info.query) {
|
if (client->connection_info.query) {
|
||||||
first_line += snprintf(client->request->buffer->data + first_line,
|
first_line_len += snprintf(client->request->buffer->data + first_line_len,
|
||||||
client->buffer_size - first_line, "?%s", client->connection_info.query);
|
client->buffer_size - first_line_len, "?%s", client->connection_info.query);
|
||||||
if (first_line > client->buffer_size) {
|
if (first_line_len >= client->buffer_size) {
|
||||||
ESP_LOGE(TAG, "Out of buffer");
|
ESP_LOGE(TAG, "Out of buffer");
|
||||||
return ESP_ERR_HTTP_CONNECT;
|
return -1;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
first_line += snprintf(client->request->buffer->data + first_line,
|
first_line_len += snprintf(client->request->buffer->data + first_line_len,
|
||||||
client->buffer_size - first_line, " %s\r\n", DEFAULT_HTTP_PROTOCOL);
|
client->buffer_size - first_line_len, " %s\r\n", DEFAULT_HTTP_PROTOCOL);
|
||||||
if (first_line > client->buffer_size) {
|
if (first_line_len >= client->buffer_size) {
|
||||||
ESP_LOGE(TAG, "Out of buffer");
|
ESP_LOGE(TAG, "Out of buffer");
|
||||||
return ESP_ERR_HTTP_CONNECT;
|
return -1;
|
||||||
}
|
}
|
||||||
wlen -= first_line;
|
return first_line_len;
|
||||||
|
}
|
||||||
|
|
||||||
while ((header_index = http_header_generate_string(client->request->headers, header_index, client->request->buffer->data + first_line, &wlen))) {
|
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. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
if (wlen <= 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (first_line) {
|
if (first_line_len) {
|
||||||
wlen += first_line;
|
wlen += first_line_len;
|
||||||
first_line = 0;
|
first_line_len = 0;
|
||||||
}
|
}
|
||||||
client->request->buffer->data[wlen] = 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;
|
client->data_write_left = wlen;
|
||||||
while (wlen > 0) {
|
client->data_written_index = 0;
|
||||||
wret = transport_write(client->transport, client->request->buffer->data + widx, wlen, client->timeout_ms);
|
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) {
|
if (wret <= 0) {
|
||||||
ESP_LOGE(TAG, "Error write request");
|
ESP_LOGE(TAG, "Error write request");
|
||||||
esp_http_client_close(client);
|
esp_http_client_close(client);
|
||||||
return ESP_ERR_HTTP_WRITE_DATA;
|
return ESP_ERR_HTTP_WRITE_DATA;
|
||||||
}
|
}
|
||||||
widx += wret;
|
client->data_write_left -= wret;
|
||||||
wlen -= wret;
|
client->data_written_index += wret;
|
||||||
}
|
}
|
||||||
wlen = client->buffer_size;
|
wlen = client->buffer_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client->data_written_index = 0;
|
||||||
|
client->data_write_left = client->post_len;
|
||||||
client->state = HTTP_STATE_REQ_COMPLETE_HEADER;
|
client->state = HTTP_STATE_REQ_COMPLETE_HEADER;
|
||||||
return ESP_OK;
|
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)
|
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;
|
int wlen = 0, widx = 0;
|
||||||
while (len > 0) {
|
while (len > 0) {
|
||||||
wlen = transport_write(client->transport, buffer + widx, len, client->timeout_ms);
|
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;
|
return wlen;
|
||||||
}
|
}
|
||||||
widx += wlen;
|
widx += wlen;
|
||||||
|
|
|
@ -114,6 +114,7 @@ typedef struct {
|
||||||
esp_http_client_transport_t transport_type; /*!< HTTP transport type, see `esp_http_client_transport_t` */
|
esp_http_client_transport_t transport_type; /*!< HTTP transport type, see `esp_http_client_transport_t` */
|
||||||
int buffer_size; /*!< HTTP buffer size (both send and receive) */
|
int buffer_size; /*!< HTTP buffer size (both send and receive) */
|
||||||
void *user_data; /*!< HTTP user_data context */
|
void *user_data; /*!< HTTP user_data context */
|
||||||
|
bool is_async; /*!< Set asynchronous mode */
|
||||||
} esp_http_client_config_t;
|
} 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_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_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_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
|
* @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
|
* @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.
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
||||||
|
|
|
@ -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 (*io_read_func)(transport_handle_t t, char *buffer, int len, int timeout_ms);
|
||||||
typedef int (*trans_func)(transport_handle_t t);
|
typedef int (*trans_func)(transport_handle_t t);
|
||||||
typedef int (*poll_func)(transport_handle_t t, int timeout_ms);
|
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);
|
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);
|
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
|
* @brief Transport read function
|
||||||
*
|
*
|
||||||
|
@ -259,6 +274,20 @@ esp_err_t transport_set_func(transport_handle_t t,
|
||||||
poll_func _poll_write,
|
poll_func _poll_write,
|
||||||
trans_func _destroy,
|
trans_func _destroy,
|
||||||
payload_transfer_func _parrent_transport);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -40,6 +40,7 @@ struct transport_item_t {
|
||||||
poll_func _poll_read; /*!< Poll and read */
|
poll_func _poll_read; /*!< Poll and read */
|
||||||
poll_func _poll_write; /*!< Poll and write */
|
poll_func _poll_write; /*!< Poll and write */
|
||||||
trans_func _destroy; /*!< Destroy and free transport */
|
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 */
|
payload_transfer_func _parrent_transfer; /*!< Function returning underlying transport layer */
|
||||||
|
|
||||||
STAILQ_ENTRY(transport_item_t) next;
|
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;
|
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)
|
int transport_read(transport_handle_t t, char *buffer, int len, int timeout_ms)
|
||||||
{
|
{
|
||||||
if (t && t->_read) {
|
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_read = _poll_read;
|
||||||
t->_poll_write = _poll_write;
|
t->_poll_write = _poll_write;
|
||||||
t->_destroy = _destroy;
|
t->_destroy = _destroy;
|
||||||
|
t->_connect_async = NULL;
|
||||||
t->_parrent_transfer = _parrent_transport;
|
t->_parrent_transfer = _parrent_transport;
|
||||||
return ESP_OK;
|
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)
|
transport_handle_t transport_get_handle(transport_handle_t t)
|
||||||
{
|
{
|
||||||
return 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;
|
||||||
|
}
|
||||||
|
|
|
@ -26,33 +26,58 @@
|
||||||
#include "transport_utils.h"
|
#include "transport_utils.h"
|
||||||
|
|
||||||
static const char *TAG = "TRANS_SSL";
|
static const char *TAG = "TRANS_SSL";
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
TRANS_SSL_INIT = 0,
|
||||||
|
TRANS_SSL_CONNECTING,
|
||||||
|
} transport_ssl_conn_state_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mbedtls specific transport data
|
* mbedtls specific transport data
|
||||||
*/
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
esp_tls_t *tls;
|
esp_tls_t *tls;
|
||||||
void *cert_pem_data;
|
esp_tls_cfg_t cfg;
|
||||||
int cert_pem_len;
|
|
||||||
bool ssl_initialized;
|
bool ssl_initialized;
|
||||||
bool verify_server;
|
bool verify_server;
|
||||||
|
transport_ssl_conn_state_t conn_state;
|
||||||
} transport_ssl_t;
|
} transport_ssl_t;
|
||||||
|
|
||||||
transport_handle_t transport_get_handle(transport_handle_t t);
|
transport_handle_t transport_get_handle(transport_handle_t t);
|
||||||
|
|
||||||
static int ssl_close(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)
|
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);
|
transport_ssl_t *ssl = transport_get_context_data(t);
|
||||||
esp_tls_cfg_t cfg = { 0 };
|
if (ssl->cfg.cacert_pem_buf) {
|
||||||
if (ssl->cert_pem_data) {
|
|
||||||
ssl->verify_server = true;
|
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->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) {
|
if (!ssl->tls) {
|
||||||
ESP_LOGE(TAG, "Failed to open a new connection");
|
ESP_LOGE(TAG, "Failed to open a new connection");
|
||||||
return -1;
|
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);
|
ret = esp_tls_conn_write(ssl->tls, (const unsigned char *) buffer, len);
|
||||||
if (ret <= 0) {
|
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;
|
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);
|
ret = esp_tls_conn_read(ssl->tls, (unsigned char *)buffer, len);
|
||||||
if (ret <= 0) {
|
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;
|
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);
|
transport_ssl_t *ssl = transport_get_context_data(t);
|
||||||
if (t && ssl) {
|
if (t && ssl) {
|
||||||
ssl->cert_pem_data = (void *)data;
|
ssl->cfg.cacert_pem_buf = (void *)data;
|
||||||
ssl->cert_pem_len = len;
|
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_MEM_CHECK(TAG, ssl, return NULL);
|
||||||
transport_set_context_data(t, ssl);
|
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_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;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -342,6 +342,38 @@ static void http_perform_as_stream_reader()
|
||||||
free(buffer);
|
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)
|
static void http_test_task(void *pvParameters)
|
||||||
{
|
{
|
||||||
app_wifi_wait_connected();
|
app_wifi_wait_connected();
|
||||||
|
@ -356,6 +388,7 @@ static void http_test_task(void *pvParameters)
|
||||||
http_redirect_to_https();
|
http_redirect_to_https();
|
||||||
http_download_chunk();
|
http_download_chunk();
|
||||||
http_perform_as_stream_reader();
|
http_perform_as_stream_reader();
|
||||||
|
https_async();
|
||||||
ESP_LOGI(TAG, "Finish http example");
|
ESP_LOGI(TAG, "Finish http example");
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue