|
|
@@ -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;
|