#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"
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;
#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 = "/";
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;
if (client->timeout_ms == 0) {
client->timeout_ms = DEFAULT_TIMEOUT_MS;
}
+ if (config->is_async) {
+ client->is_async = true;
+ }
return ESP_OK;
}
{
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;
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) {
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);
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;
#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)
{
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;
#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;
}
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;
}
}
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;
}
{
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;
}
}
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;
}