1、背景
写的HTTP数据流下载不好用,特别在网速慢,任务多、中断切换频繁,出现下载中断,任务出错等等问题。ESP-IDF提供了线程HTTPS_OTA,其也是基于ESP HTTP Client 的API接口。
1.1 参考资料
https://jverson.com/2018/03/08/https-certificate/
2、ESP HTTP 请求步骤
ESP-HTTP请求有两种方式的步骤
2.1 init--perform-cleanup
看代码注释的步骤
esp_http_client_config_t config = {
.url = "http://httpbin.org/redirect/2",
.event_handler = _http_event_handle,
.user_data = &data,
};
/*1.初始化http client*/
esp_http_client_handle_t client = esp_http_client_init(&config);
/*2.执行http client的请求接收解析全过程*/
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "Status = %d, content_length = %d",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
}
/*3.关闭清理http client*/
esp_http_client_cleanup(client);
看这很简单,主要还是要看你配置的.event_handler 回调函数来处理perform过程中各种事件。看有哪些事件。报错、连接上、发送请求头、接收到应答头、接收到数据、结束HTTP会话、断开连接。对于OTA来说特别关心数据和会话结束。
esp_err_t _http_event_handle(esp_http_client_event_t *evt)
{
switch(evt->event_id) {
case HTTP_EVENT_ERROR:
ESP_LOGI(TAG, "HTTP_EVENT_ERROR");
break;
case HTTP_EVENT_ON_CONNECTED:
ESP_LOGI(TAG, "HTTP_EVENT_ON_CONNECTED");
break;
case HTTP_EVENT_HEADER_SENT:
ESP_LOGI(TAG, "HTTP_EVENT_HEADER_SENT");
break;
case HTTP_EVENT_ON_HEADER:
ESP_LOGI(TAG, "HTTP_EVENT_ON_HEADER");
printf("%.*s", evt->data_len, (char*)evt->data);
break;
case HTTP_EVENT_ON_DATA:
ESP_LOGI(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
if (!esp_http_client_is_chunked_response(evt->client)) {
printf("%.*s", evt->data_len, (char*)evt->data);
}
break;
case HTTP_EVENT_ON_FINISH:
ESP_LOGI(TAG, "HTTP_EVENT_ON_FINISH");
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
break;
}
return ESP_OK;
}
2.2 init--open--fetch_head--read--cleanup
与init--perform--cleanup不同之处在于,open时就发出了HTTP请求,在fetch_head上处理了应答头,在read中处理了body数据。
esp_http_client_config_t config = {
.url = tem_url,
.event_handler = ota_http_event_handler,
.cert_pem = (char *)iotx_ca_crt,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
if (client == NULL)
{
ESP_LOGE(TAG, "Failed to initialise HTTP connection");
task_fatal_error();
}
err = esp_http_client_open(client, 0);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
esp_http_client_cleanup(client);
task_fatal_error();
}
esp_http_client_fetch_headers(client);
while (1)
{
int data_read = esp_http_client_read(client, ota_write_data, BUFFSIZE);
}
esp_http_client_cleanup(client);
2.3 两个方式的不同之处
为啥init-perform-cleanup要event_handler回调函数中处理数据,而init-open-fetch_head-read-cleanup要再read后处理数据
区别还是在于perform中的从缓冲区读数据处理方法的不同,直接上代码
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;
}
}
/*这里数据处理,处理的数据没有处理完(body数据全部处理好)*/
while (client->response->data_process < client->response->content_length) {
ESP_LOGI(TAG, "data_process < content_length, contiue");
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 (!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;
default:
break;
看注释处,一直处理body数据,直到处理完,最后才发出HTTP_EVENT_ON_FINSH事件,其中的核心就是esp_http_client_get_data,展开这个函数,再看解析器http_on_body
static int esp_http_client_get_data(esp_http_client_handle_t client)
{
if (client->state < HTTP_STATE_RES_COMPLETE_HEADER) {
return ESP_FAIL;
}
if (client->connection_info.method == HTTP_METHOD_HEAD) {
return 0;
}
esp_http_buffer_t *res_buffer = client->response->buffer;
ESP_LOGD(TAG, "data_process=%d, content_length=%d", client->response->data_process, client->response->content_length);
int rlen = esp_transport_read(client->transport, res_buffer->data, client->buffer_size, client->timeout_ms);
if (rlen >= 0) {
http_parser_execute(client->parser, client->parser_settings, res_buffer->data, rlen);
}
return rlen;
}
static int http_on_body(http_parser *parser, const char *at, size_t length)
{
esp_http_client_t *client = parser->data;
ESP_LOGI(TAG, "http_on_body %d, first char 0x%02x", length, at[0]);
client->response->buffer->raw_data = (char *)at;
/*这里很关键,如果没有指定oupt_ptr,那么数据就不会被复制到用户buffer了,
上面还有一个指针可以用,raw_data*/
if (client->response->buffer->output_ptr) {
memcpy(client->response->buffer->output_ptr, (char *)at, length);
client->response->buffer->output_ptr += length;
}
client->response->data_process += length;
client->response->buffer->raw_len += length;
/*发出数据事件*/
http_dispatch_event(client, HTTP_EVENT_ON_DATA, (void *)at, length);
return 0;
}
如代码上注释而言,结合循环,那么在不指定out_ptr的前提下,只会得到最后一包数据。
而init-perform-cleanup没有指定out_ptr,只能在发出数据事件后在事件回调函数处理。
3、常用API浅析
ESP-IDF对HTTP/S请求的API封装实在是太好了,单看一些API的说明,很难理清它的运行逻辑。因此还是阅读源码最能理解。
常用的API有esp_http_client_init()、esp_http_client_perform()、esp_http_client_open()、esp_http_client_write()、esp_http_client_fetch_header()、esp_http_client_read();
/**
* @brief HTTP configuration
*/
typedef struct {
const char *url; /*!< HTTP URL, the information on the URL is most important, it overrides the other fields below, if any */
const char *host; /*!< Domain or IP as string */
int port; /*!< Port to connect, default depend on esp_http_client_transport_t (80 or 443) */
const char *username; /*!< Using for Http authentication */
const char *password; /*!< Using for Http authentication */
esp_http_client_auth_type_t auth_type; /*!< Http authentication type, see `esp_http_client_auth_type_t` */
const char *path; /*!< HTTP Path, if not set, default is `/` */
const char *query; /*!< HTTP query */
const char *cert_pem; /*!< SSL Certification, PEM format as string, if the client requires to verify server */
esp_http_client_method_t method; /*!< HTTP Method */
int timeout_ms; /*!< Network timeout in milliseconds */
bool disable_auto_redirect; /*!< Disable HTTP automatic redirects */
int max_redirection_count; /*!< Max redirection number, using default value if zero*/
http_event_handle_cb event_handler; /*!< HTTP Event Handle */
esp_http_client_transport_t transport_type; /*!< HTTP transport type, see `esp_http_client_transport_t` */
int buffer_size; /*!< HTTP buffer size (both send and receive) */
void *user_data; /*!< HTTP user_data context */
bool is_async; /*!< Set asynchronous mode, only supported with HTTPS for now */
} esp_http_client_config_t;
先看初始化API接口
2.1 esp_http_client_handle_t esp_http_client_init
(const esp_http_client_config_t *config)
开始HTTP会话之前必须首先调用这个初始化函数。它的返回值esp_http_client_handle_t型变量作为其他HTTP API的实参用。
在所有操作完成后用esp_http_client_cleanup()来清除。
esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *config)
{
esp_http_client_handle_t client;
esp_transport_handle_t tcp;
bool _success;
//1、分配空间
_success = (
(client = calloc(1, sizeof(esp_http_client_t))) &&
(client->parser = calloc(1, sizeof(struct http_parser))) &&
(client->parser_settings = calloc(1, sizeof(struct http_parser_settings))) &&
(client->auth_data = calloc(1, sizeof(esp_http_auth_data_t))) &&
(client->request = calloc(1, sizeof(esp_http_data_t))) &&
(client->request->headers = http_header_init()) &&
(client->request->buffer = calloc(1, sizeof(esp_http_buffer_t))) &&
(client->response = calloc(1, sizeof(esp_http_data_t))) &&
(client->response->headers = http_header_init()) &&
(client->response->buffer = calloc(1, sizeof(esp_http_buffer_t)))
);
if (!_success) {
ESP_LOGE(TAG, "Error allocate memory");
esp_http_client_cleanup(client);
return NULL;
}
//2、初始化transport 普通tcp,ssl_tcp
_success = (
(client->transport_list = esp_transport_list_init()) &&
(tcp = esp_transport_tcp_init()) &&
(esp_transport_set_default_port(tcp, DEFAULT_HTTP_PORT) == ESP_OK) &&
(esp_transport_list_add(client->transport_list, tcp, "http") == ESP_OK)
);
if (!_success) {
ESP_LOGE(TAG, "Error initialize transport");
esp_http_client_cleanup(client);
return NULL;
}
#ifdef CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS
esp_transport_handle_t ssl;
_success = (
(ssl = esp_transport_ssl_init()) &&
(esp_transport_set_default_port(ssl, DEFAULT_HTTPS_PORT) == ESP_OK) &&
(esp_transport_list_add(client->transport_list, ssl, "https") == ESP_OK)
);
if (!_success) {
ESP_LOGE(TAG, "Error initialize SSL Transport");
esp_http_client_cleanup(client);
return NULL;
}
if (config->cert_pem) {
esp_transport_ssl_set_cert_data(ssl, config->cert_pem, strlen(config->cert_pem));
}
#endif
//3、按照形参config来设置client
if (_set_config(client, config) != ESP_OK) {
ESP_LOGE(TAG, "Error set configurations");
esp_http_client_cleanup(client);
return NULL;
}
//4、分配请求和应答的buffer---默认是512字节
_success = (
(client->request->buffer->data = malloc(client->buffer_size)) &&
(client->response->buffer->data = malloc(client->buffer_size))
);
if (!_success) {
ESP_LOGE(TAG, "Allocation failed");
esp_http_client_cleanup(client);
return NULL;
}
//5、设置URL和请求头信息
_success = (
(esp_http_client_set_url(client, config->url) == ESP_OK) &&
(esp_http_client_set_header(client, "User-Agent", DEFAULT_HTTP_USER_AGENT) == ESP_OK) &&
(esp_http_client_set_header(client, "Host", client->connection_info.host) == ESP_OK)
);
if (!_success) {
ESP_LOGE(TAG, "Error set default configurations");
esp_http_client_cleanup(client);
return NULL;
}
//6、设置解析器
client->parser_settings->on_message_begin = http_on_message_begin;
client->parser_settings->on_url = http_on_url;
client->parser_settings->on_status = http_on_status;
client->parser_settings->on_header_field = http_on_header_field;
client->parser_settings->on_header_value = http_on_header_value;
client->parser_settings->on_headers_complete = http_on_headers_complete;
client->parser_settings->on_body = http_on_body;
client->parser_settings->on_message_complete = http_on_message_complete;
client->parser_settings->on_chunk_complete = http_on_chunk_complete;
client->parser->data = client;
client->event.client = client;
//7、设置HTTP状态为HTTP_STATE_INIT
client->state = HTTP_STATE_INIT;
return client;
}
申请空间,构造esp_http_client_handle_t,包括设置解析器、请求和应答buffer、初始化transport等等。
其中:
client->state记录HTTP会话的状态。下面是这个状态变化的一个枚举类型
一般来说:是一个fall through的从0到7过程。
typedef enum {
HTTP_STATE_UNINIT = 0,
HTTP_STATE_INIT,
HTTP_STATE_CONNECTED,
HTTP_STATE_REQ_COMPLETE_HEADER,
HTTP_STATE_REQ_COMPLETE_DATA,
HTTP_STATE_RES_COMPLETE_HEADER,
HTTP_STATE_RES_COMPLETE_DATA,
HTTP_STATE_CLOSE
} esp_http_state_t;
2.2 esp_err_t esp_http_client_perform
(esp_http_client_handle_t client)
该API以阻塞或非阻塞方式执行整个请求。默认情况下,API以阻塞的方式执行请求,并在完成时返回,或者在以非阻塞的方式下遇到EAGAIN/EWOULDBLOCK/EINPROGRESS返回。在非阻塞状态下的请求可以调用这个API多次除非这个请求&应答完成或失败。使能非阻塞的方法是设置esp_http_client_config_t配置项中的is_async.这样,你就可以对同一个esp_http-client_handle_t变量进行多次调用。如果服务器允许,底层连接可以保持为打开状态,我们鼓励传输多个文件。esp_http_client重用相同的连接,可以使操作更快、CPU更少,使用的网络资源更少。
注意:永远不要同时对同一个client句柄调用这个API。要想并行运行,请调用不同的client句柄。这个函数包括如下操作:esp_http_client_open->esp_http_client_write->esp_http_client_fetch_headers->esp_http_client_read或者esp_http_client_close
esp_err_t esp_http_client_perform(esp_http_client_handle_t client)
{
esp_err_t err;
do {
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 (!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;
default:
break;
}
} while (client->process_again);
return ESP_OK;
}
注意到一点case 和case之间是没有break的,因此程序会falls through 向下执行。这点把握很重要,从当前状态开始将之后的要执行的操作按顺序昨晚。出现错误时,对于非阻塞态的会返回ESP_ERR_HTTP_EAGAIN;对于阻塞态的回返回当前的错误。
2.3 esp_err_t esp_http_client_open
(esp_http_client_handle_tclient, int write_len)
这个接口打开连接,并发送请求头并返回
esp_err_t esp_http_client_open(esp_http_client_handle_t client, int write_len)
{
client->post_len = 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, write_len)) != ESP_OK) {
return err;
}
return ESP_OK;
}
和这个函数配合使用的esp_http_client_fetch_header()和esp_http_client_read()这两个API,可以帮助我们接受HTTP服务器的响应数据。
核心是两个函数,逐一分析。
esp_http_client_connect(client)在前面esp_http_client_init(config)的时候已经初始化,添加transport,如HTTP和SSL。包含了这些的初始化,如下就是ssl客户端的初始化。
esp_transport_handle_t esp_transport_ssl_init()
{
esp_transport_handle_t t = esp_transport_init();
transport_ssl_t *ssl = calloc(1, sizeof(transport_ssl_t));
ESP_TRANSPORT_MEM_CHECK(TAG, ssl, return NULL);
esp_transport_set_context_data(t, ssl);
esp_transport_set_func(t, ssl_connect, ssl_read, ssl_write, ssl_close, ssl_poll_read, ssl_poll_write, ssl_destroy);
esp_transport_set_async_connect_func(t, ssl_connect_async);
return t;
}
这里面还要看一下ssl_connect到底分配了多大的buffer
/**
* @brief Create a new blocking TLS/SSL connection
*
* 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.
* @param[in] port Port number of the host.
* @param[in] cfg TLS configuration as esp_tls_cfg_t. If you wish to open
* non-TLS connection, keep this NULL. For TLS connection,
* a pass pointer to esp_tls_cfg_t. At a minimum, this
* structure should be zero-initialized.
* @return pointer to esp_tls_t, or NULL if connection couldn't be opened.
*/
esp_tls_t *esp_tls_conn_new(const char *hostname, int hostlen, int port, const esp_tls_cfg_t *cfg);
对于下面的调用,说明http做了什么
esp_err_t err = esp_http_client_open(client, 0);
主要看 esp_http_client_request_send(client, write_len),由于传入是参数为0,实际上做的是HTTP请求的属性头格式化后,发给服务器。-----也就是请求一个body是空的包,目的是为了获取整个图片或下载包的大小。
再看一下这个结构体
/**
* Transport layer structure, which will provide functions, basic properties for transport types
*/
struct esp_transport_item_t {
int port;
int socket; /*!< Socket to use in this transport */
char *scheme; /*!< Tag name */
void *context; /*!< Context data */
void *data; /*!< Additional transport data */
connect_func _connect; /*!< Connect function of this transport */
io_read_func _read; /*!< Read */
io_func _write; /*!< Write */
trans_func _close; /*!< Close */
poll_func _poll_read; /*!< Poll and read */
poll_func _poll_write; /*!< Poll and write */
trans_func _destroy; /*!< Destroy and free transport */
connect_async_func _connect_async; /*!< non-blocking connect function of this transport */
payload_transfer_func _parent_transfer; /*!< Function returning underlying transport layer */
STAILQ_ENTRY(esp_transport_item_t) next;
};
2.4 int esp_http_client_fetch_headers
(esp_http_client_handle_tclient)
这个函数在esp_http_client_open()之后调用,从数据流中读取,并处理所有接受到的应答HTTP头。
如果应答的HTTP头包括content-length域值,则返回这个域值或者分块的大小。
int esp_http_client_fetch_headers(esp_http_client_handle_t client)
{
if (client->state < HTTP_STATE_REQ_COMPLETE_HEADER) {
return ESP_FAIL;
}
client->state = HTTP_STATE_REQ_COMPLETE_DATA;
esp_http_buffer_t *buffer = client->response->buffer;
client->response->status_code = -1;
//执行读取buffer数据以及解析
while (client->state < HTTP_STATE_RES_COMPLETE_HEADER) {
buffer->len = esp_transport_read(client->transport, buffer->data, client->buffer_size, client->timeout_ms);
if (buffer->len <= 0) {
return ESP_FAIL;
}
http_parser_execute(client->parser, client->parser_settings, buffer->data, buffer->len);
}
ESP_LOGD(TAG, "content_length = %d", client->response->content_length);
if (client->response->content_length <= 0) {
client->response->is_chunked = true;
return 0;
}
return client->response->content_length; //返回body长度
}
2.5 int esp_http_client_read
(esp_http_client_handle_tclient, char *buffer, int len)
这个API的作用是从数据流中读取数据。
int esp_http_client_read(esp_http_client_handle_t client, char *buffer, int len)
{
esp_http_buffer_t *res_buffer = client->response->buffer;
//从client->response->buffer 最多读取len长度的数据
int rlen = ESP_FAIL, ridx = 0;
if (res_buffer->raw_len) {
int remain_len = client->response->buffer->raw_len;
if (remain_len > len) {
remain_len = len;
}
memcpy(buffer, res_buffer->raw_data, remain_len);
res_buffer->raw_len -= remain_len;
res_buffer->raw_data += remain_len;
ridx = remain_len;
}
int need_read = len - ridx;
bool is_data_remain = true;
//没有读够,可以等待接收,等接收到了再填充到我的buffer中
while (need_read > 0 && is_data_remain) {
if (client->response->is_chunked) {
is_data_remain = !client->is_chunk_complete;
} else {
is_data_remain = client->response->data_process < client->response->content_length;
}
ESP_LOGD(TAG, "is_data_remain=%d, is_chunked=%d", is_data_remain, client->response->is_chunked);
if (!is_data_remain) {
break;
}
int byte_to_read = need_read;
if (byte_to_read > client->buffer_size) {
byte_to_read = client->buffer_size;
}
rlen = esp_transport_read(client->transport, res_buffer->data, byte_to_read, client->timeout_ms);
ESP_LOGD(TAG, "need_read=%d, byte_to_read=%d, rlen=%d, ridx=%d", need_read, byte_to_read, rlen, ridx);
if (rlen <= 0) {
return ridx;
}
res_buffer->output_ptr = buffer + ridx;
http_parser_execute(client->parser, client->parser_settings, res_buffer->data, rlen);
ridx += res_buffer->raw_len;
need_read -= res_buffer->raw_len;
res_buffer->raw_len = 0; //clear
res_buffer->output_ptr = NULL;
}
//实际读取到多少数据
return ridx;
}
对于esp_http_client_read()先从client句柄的接收buffer中取数据来填充,如果没有读够且没有读完,则可以考虑接收数据并解析来填充。
3、解析过程
size_t http_parser_execute (http_parser *parser,
const http_parser_settings *settings,
const char *data,
size_t len)
{
char c, ch;
int8_t unhex_val;
const char *p = data;
const char *header_field_mark = 0;
const char *header_value_mark = 0;
const char *url_mark = 0;
const char *body_mark = 0;
const char *status_mark = 0;
enum state p_state = (enum state) parser->state;
const unsigned int lenient = parser->lenient_http_headers;
/* We're in an error state. Don't bother doing anything. */
if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
return 0;
}
if (len == 0) {
switch (CURRENT_STATE()) {
case s_body_identity_eof:
/* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if
* we got paused.
*/
CALLBACK_NOTIFY_NOADVANCE(message_complete);
return 0;
case s_dead:
case s_start_req_or_res:
case s_start_res:
case s_start_req:
return 0;
default:
SET_ERRNO(HPE_INVALID_EOF_STATE);
return 1;
}
}
if (CURRENT_STATE() == s_header_field)
header_field_mark = data;
if (CURRENT_STATE() == s_header_value)
header_value_mark = data;
switch (CURRENT_STATE()) {
case s_req_path:
case s_req_schema:
case s_req_schema_slash:
case s_req_schema_slash_slash:
case s_req_server_start:
case s_req_server:
case s_req_server_with_at:
case s_req_query_string_start:
case s_req_query_string:
case s_req_fragment_start:
case s_req_fragment:
url_mark = data;
break;
case s_res_status:
status_mark = data;
break;
default:
break;
}
for (p=data; p != data + len; p++) {
ch = *p;
if (PARSING_HEADER(CURRENT_STATE()))
COUNT_HEADER_SIZE(1);
reexecute:
switch (CURRENT_STATE()) {
case s_dead:
/* this state is used after a 'Connection: close' message
* the parser will error out if it reads another message
*/
if (LIKELY(ch == CR || ch == LF))
break;
SET_ERRNO(HPE_CLOSED_CONNECTION);
goto error;
case s_start_req_or_res:
{
if (ch == CR || ch == LF)
break;
parser->flags = 0;
parser->content_length = ULLONG_MAX;
if (ch == 'H') {
UPDATE_STATE(s_res_or_resp_H);
CALLBACK_NOTIFY(message_begin);
} else {
parser->type = HTTP_REQUEST;
UPDATE_STATE(s_start_req);
REEXECUTE();
}
break;
}
case s_res_or_resp_H:
if (ch == 'T') {
parser->type = HTTP_RESPONSE;
UPDATE_STATE(s_res_HT);
} else {
if (UNLIKELY(ch != 'E')) {
SET_ERRNO(HPE_INVALID_CONSTANT);
goto error;
}
parser->type = HTTP_REQUEST;
parser->method = HTTP_HEAD;
parser->index = 2;
UPDATE_STATE(s_req_method);
}
break;
case s_start_res:
{
parser->flags = 0;
parser->content_length = ULLONG_MAX;
switch (ch) {
case 'H':
UPDATE_STATE(s_res_H);
break;
case CR:
case LF:
break;
default:
SET_ERRNO(HPE_INVALID_CONSTANT);
goto error;
}
CALLBACK_NOTIFY(message_begin);
break;
}
case s_res_H:
STRICT_CHECK(ch != 'T');
UPDATE_STATE(s_res_HT);
break;
case s_res_HT:
STRICT_CHECK(ch != 'T');
UPDATE_STATE(s_res_HTT);
break;
case s_res_HTT:
STRICT_CHECK(ch != 'P');
UPDATE_STATE(s_res_HTTP);
break;
case s_res_HTTP:
STRICT_CHECK(ch != '/');
UPDATE_STATE(s_res_first_http_major);
break;
case s_res_first_http_major:
if (UNLIKELY(ch < '0' || ch > '9')) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
parser->http_major = ch - '0';
UPDATE_STATE(s_res_http_major);
break;
/* major HTTP version or dot */
case s_res_http_major:
{
if (ch == '.') {
UPDATE_STATE(s_res_first_http_minor);
break;
}
if (!IS_NUM(ch)) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
parser->http_major *= 10;
parser->http_major += ch - '0';
if (UNLIKELY(parser->http_major > 999)) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
break;
}
/* first digit of minor HTTP version */
case s_res_first_http_minor:
if (UNLIKELY(!IS_NUM(ch))) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
parser->http_minor = ch - '0';
UPDATE_STATE(s_res_http_minor);
break;
/* minor HTTP version or end of request line */
case s_res_http_minor:
{
if (ch == ' ') {
UPDATE_STATE(s_res_first_status_code);
break;
}
if (UNLIKELY(!IS_NUM(ch))) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
parser->http_minor *= 10;
parser->http_minor += ch - '0';
if (UNLIKELY(parser->http_minor > 999)) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
break;
}
case s_res_first_status_code:
{
if (!IS_NUM(ch)) {
if (ch == ' ') {
break;
}
SET_ERRNO(HPE_INVALID_STATUS);
goto error;
}
parser->status_code = ch - '0';
UPDATE_STATE(s_res_status_code);
break;
}
case s_res_status_code:
{
if (!IS_NUM(ch)) {
switch (ch) {
case ' ':
UPDATE_STATE(s_res_status_start);
break;
case CR:
UPDATE_STATE(s_res_line_almost_done);
break;
case LF:
UPDATE_STATE(s_header_field_start);
break;
default:
SET_ERRNO(HPE_INVALID_STATUS);
goto error;
}
break;
}
parser->status_code *= 10;
parser->status_code += ch - '0';
if (UNLIKELY(parser->status_code > 999)) {
SET_ERRNO(HPE_INVALID_STATUS);
goto error;
}
break;
}
case s_res_status_start:
{
if (ch == CR) {
UPDATE_STATE(s_res_line_almost_done);
break;
}
if (ch == LF) {
UPDATE_STATE(s_header_field_start);
break;
}
MARK(status);
UPDATE_STATE(s_res_status);
parser->index = 0;
break;
}
case s_res_status:
if (ch == CR) {
UPDATE_STATE(s_res_line_almost_done);
CALLBACK_DATA(status);
break;
}
if (ch == LF) {
UPDATE_STATE(s_header_field_start);
CALLBACK_DATA(status);
break;
}
break;
case s_res_line_almost_done:
STRICT_CHECK(ch != LF);
UPDATE_STATE(s_header_field_start);
break;
case s_start_req:
{
if (ch == CR || ch == LF)
break;
parser->flags = 0;
parser->content_length = ULLONG_MAX;
if (UNLIKELY(!IS_ALPHA(ch))) {
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
parser->method = (enum http_method) 0;
parser->index = 1;
switch (ch) {
case 'A': parser->method = HTTP_ACL; break;
case 'B': parser->method = HTTP_BIND; break;
case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break;
case 'D': parser->method = HTTP_DELETE; break;
case 'G': parser->method = HTTP_GET; break;
case 'H': parser->method = HTTP_HEAD; break;
case 'L': parser->method = HTTP_LOCK; /* or LINK */ break;
case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break;
case 'N': parser->method = HTTP_NOTIFY; break;
case 'O': parser->method = HTTP_OPTIONS; break;
case 'P': parser->method = HTTP_POST;
/* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */
break;
case 'R': parser->method = HTTP_REPORT; /* or REBIND */ break;
case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break;
case 'T': parser->method = HTTP_TRACE; break;
case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break;
default:
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
UPDATE_STATE(s_req_method);
CALLBACK_NOTIFY(message_begin);
break;
}
case s_req_method:
{
const char *matcher;
if (UNLIKELY(ch == '\0')) {
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
matcher = method_strings[parser->method];
if (ch == ' ' && matcher[parser->index] == '\0') {
UPDATE_STATE(s_req_spaces_before_url);
} else if (ch == matcher[parser->index]) {
; /* nada */
} else if (IS_ALPHA(ch)) {
switch (parser->method << 16 | parser->index << 8 | ch) {
#define XX(meth, pos, ch, new_meth) \
case (HTTP_##meth << 16 | pos << 8 | ch): \
parser->method = HTTP_##new_meth; break;
XX(POST, 1, 'U', PUT)
XX(POST, 1, 'A', PATCH)
XX(CONNECT, 1, 'H', CHECKOUT)
XX(CONNECT, 2, 'P', COPY)
XX(MKCOL, 1, 'O', MOVE)
XX(MKCOL, 1, 'E', MERGE)
XX(MKCOL, 2, 'A', MKACTIVITY)
XX(MKCOL, 3, 'A', MKCALENDAR)
XX(SUBSCRIBE, 1, 'E', SEARCH)
XX(REPORT, 2, 'B', REBIND)
XX(POST, 1, 'R', PROPFIND)
XX(PROPFIND, 4, 'P', PROPPATCH)
XX(PUT, 2, 'R', PURGE)
XX(LOCK, 1, 'I', LINK)
XX(UNLOCK, 2, 'S', UNSUBSCRIBE)
XX(UNLOCK, 2, 'B', UNBIND)
XX(UNLOCK, 3, 'I', UNLINK)
#undef XX
default:
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
} else if (ch == '-' &&
parser->index == 1 &&
parser->method == HTTP_MKCOL) {
parser->method = HTTP_MSEARCH;
} else {
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
++parser->index;
break;
}
case s_req_spaces_before_url:
{
if (ch == ' ') break;
MARK(url);
if (parser->method == HTTP_CONNECT) {
UPDATE_STATE(s_req_server_start);
}
UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
if (UNLIKELY(CURRENT_STATE() == s_dead)) {
SET_ERRNO(HPE_INVALID_URL);
goto error;
}
break;
}
case s_req_schema:
case s_req_schema_slash:
case s_req_schema_slash_slash:
case s_req_server_start:
{
switch (ch) {
/* No whitespace allowed here */
case ' ':
case CR:
case LF:
SET_ERRNO(HPE_INVALID_URL);
goto error;
default:
UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
if (UNLIKELY(CURRENT_STATE() == s_dead)) {
SET_ERRNO(HPE_INVALID_URL);
goto error;
}
}
break;
}
case s_req_server:
case s_req_server_with_at:
case s_req_path:
case s_req_query_string_start:
case s_req_query_string:
case s_req_fragment_start:
case s_req_fragment:
{
switch (ch) {
case ' ':
UPDATE_STATE(s_req_http_start);
CALLBACK_DATA(url);
break;
case CR:
case LF:
parser->http_major = 0;
parser->http_minor = 9;
UPDATE_STATE((ch == CR) ?
s_req_line_almost_done :
s_header_field_start);
CALLBACK_DATA(url);
break;
default:
UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
if (UNLIKELY(CURRENT_STATE() == s_dead)) {
SET_ERRNO(HPE_INVALID_URL);
goto error;
}
}
break;
}
case s_req_http_start:
switch (ch) {
case 'H':
UPDATE_STATE(s_req_http_H);
break;
case ' ':
break;
default:
SET_ERRNO(HPE_INVALID_CONSTANT);
goto error;
}
break;
case s_req_http_H:
STRICT_CHECK(ch != 'T');
UPDATE_STATE(s_req_http_HT);
break;
case s_req_http_HT:
STRICT_CHECK(ch != 'T');
UPDATE_STATE(s_req_http_HTT);
break;
case s_req_http_HTT:
STRICT_CHECK(ch != 'P');
UPDATE_STATE(s_req_http_HTTP);
break;
case s_req_http_HTTP:
STRICT_CHECK(ch != '/');
UPDATE_STATE(s_req_first_http_major);
break;
/* first digit of major HTTP version */
case s_req_first_http_major:
if (UNLIKELY(ch < '1' || ch > '9')) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
parser->http_major = ch - '0';
UPDATE_STATE(s_req_http_major);
break;
/* major HTTP version or dot */
case s_req_http_major:
{
if (ch == '.') {
UPDATE_STATE(s_req_first_http_minor);
break;
}
if (UNLIKELY(!IS_NUM(ch))) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
parser->http_major *= 10;
parser->http_major += ch - '0';
if (UNLIKELY(parser->http_major > 999)) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
break;
}
/* first digit of minor HTTP version */
case s_req_first_http_minor:
if (UNLIKELY(!IS_NUM(ch))) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
parser->http_minor = ch - '0';
UPDATE_STATE(s_req_http_minor);
break;
/* minor HTTP version or end of request line */
case s_req_http_minor:
{
if (ch == CR) {
UPDATE_STATE(s_req_line_almost_done);
break;
}
if (ch == LF) {
UPDATE_STATE(s_header_field_start);
break;
}
/* XXX allow spaces after digit? */
if (UNLIKELY(!IS_NUM(ch))) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
parser->http_minor *= 10;
parser->http_minor += ch - '0';
if (UNLIKELY(parser->http_minor > 999)) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
break;
}
/* end of request line */
case s_req_line_almost_done:
{
if (UNLIKELY(ch != LF)) {
SET_ERRNO(HPE_LF_EXPECTED);
goto error;
}
UPDATE_STATE(s_header_field_start);
break;
}
case s_header_field_start:
{
if (ch == CR) {
UPDATE_STATE(s_headers_almost_done);
break;
}
if (ch == LF) {
/* they might be just sending \n instead of \r\n so this would be
* the second \n to denote the end of headers*/
UPDATE_STATE(s_headers_almost_done);
REEXECUTE();
}
c = TOKEN(ch);
if (UNLIKELY(!c)) {
SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
goto error;
}
MARK(header_field);
parser->index = 0;
UPDATE_STATE(s_header_field);
switch (c) {
case 'c':
parser->header_state = h_C;
break;
case 'p':
parser->header_state = h_matching_proxy_connection;
break;
case 't':
parser->header_state = h_matching_transfer_encoding;
break;
case 'u':
parser->header_state = h_matching_upgrade;
break;
default:
parser->header_state = h_general;
break;
}
break;
}
case s_header_field:
{
const char* start = p;
for (; p != data + len; p++) {
ch = *p;
c = TOKEN(ch);
if (!c)
break;
switch (parser->header_state) {
case h_general:
break;
case h_C:
parser->index++;
parser->header_state = (c == 'o' ? h_CO : h_general);
break;
case h_CO:
parser->index++;
parser->header_state = (c == 'n' ? h_CON : h_general);
break;
case h_CON:
parser->index++;
switch (c) {
case 'n':
parser->header_state = h_matching_connection;
break;
case 't':
parser->header_state = h_matching_content_length;
break;
default:
parser->header_state = h_general;
break;
}
break;
/* connection */
case h_matching_connection:
parser->index++;
if (parser->index > sizeof(CONNECTION)-1
|| c != CONNECTION[parser->index]) {
parser->header_state = h_general;
} else if (parser->index == sizeof(CONNECTION)-2) {
parser->header_state = h_connection;
}
break;
/* proxy-connection */
case h_matching_proxy_connection:
parser->index++;
if (parser->index > sizeof(PROXY_CONNECTION)-1
|| c != PROXY_CONNECTION[parser->index]) {
parser->header_state = h_general;
} else if (parser->index == sizeof(PROXY_CONNECTION)-2) {
parser->header_state = h_connection;
}
break;
/* content-length */
case h_matching_content_length:
parser->index++;
if (parser->index > sizeof(CONTENT_LENGTH)-1
|| c != CONTENT_LENGTH[parser->index]) {
parser->header_state = h_general;
} else if (parser->index == sizeof(CONTENT_LENGTH)-2) {
if (parser->flags & F_CONTENTLENGTH) {
SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
goto error;
}
parser->header_state = h_content_length;
parser->flags |= F_CONTENTLENGTH;
}
break;
/* transfer-encoding */
case h_matching_transfer_encoding:
parser->index++;
if (parser->index > sizeof(TRANSFER_ENCODING)-1
|| c != TRANSFER_ENCODING[parser->index]) {
parser->header_state = h_general;
} else if (parser->index == sizeof(TRANSFER_ENCODING)-2) {
parser->header_state = h_transfer_encoding;
}
break;
/* upgrade */
case h_matching_upgrade:
parser->index++;
if (parser->index > sizeof(UPGRADE)-1
|| c != UPGRADE[parser->index]) {
parser->header_state = h_general;
} else if (parser->index == sizeof(UPGRADE)-2) {
parser->header_state = h_upgrade;
}
break;
case h_connection:
case h_content_length:
case h_transfer_encoding:
case h_upgrade:
if (ch != ' ') parser->header_state = h_general;
break;
default:
assert(0 && "Unknown header_state");
break;
}
}
COUNT_HEADER_SIZE(p - start);
if (p == data + len) {
--p;
break;
}
if (ch == ':') {
UPDATE_STATE(s_header_value_discard_ws);
CALLBACK_DATA(header_field);
break;
}
SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
goto error;
}
case s_header_value_discard_ws:
if (ch == ' ' || ch == '\t') break;
if (ch == CR) {
UPDATE_STATE(s_header_value_discard_ws_almost_done);
break;
}
if (ch == LF) {
UPDATE_STATE(s_header_value_discard_lws);
break;
}
/* FALLTHROUGH */
case s_header_value_start:
{
MARK(header_value);
UPDATE_STATE(s_header_value);
parser->index = 0;
c = LOWER(ch);
switch (parser->header_state) {
case h_upgrade:
parser->flags |= F_UPGRADE;
parser->header_state = h_general;
break;
case h_transfer_encoding:
/* looking for 'Transfer-Encoding: chunked' */
if ('c' == c) {
parser->header_state = h_matching_transfer_encoding_chunked;
} else {
parser->header_state = h_general;
}
break;
case h_content_length:
if (UNLIKELY(!IS_NUM(ch))) {
SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
goto error;
}
parser->content_length = ch - '0';
break;
case h_connection:
/* looking for 'Connection: keep-alive' */
if (c == 'k') {
parser->header_state = h_matching_connection_keep_alive;
/* looking for 'Connection: close' */
} else if (c == 'c') {
parser->header_state = h_matching_connection_close;
} else if (c == 'u') {
parser->header_state = h_matching_connection_upgrade;
} else {
parser->header_state = h_matching_connection_token;
}
break;
/* Multi-value `Connection` header */
case h_matching_connection_token_start:
break;
default:
parser->header_state = h_general;
break;
}
break;
}
case s_header_value:
{
const char* start = p;
enum header_states h_state = (enum header_states) parser->header_state;
for (; p != data + len; p++) {
ch = *p;
if (ch == CR) {
UPDATE_STATE(s_header_almost_done);
parser->header_state = h_state;
CALLBACK_DATA(header_value);
break;
}
if (ch == LF) {
UPDATE_STATE(s_header_almost_done);
COUNT_HEADER_SIZE(p - start);
parser->header_state = h_state;
CALLBACK_DATA_NOADVANCE(header_value);
REEXECUTE();
}
if (!lenient && !IS_HEADER_CHAR(ch)) {
SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
goto error;
}
c = LOWER(ch);
switch (h_state) {
case h_general:
{
const char* p_cr;
const char* p_lf;
size_t limit = data + len - p;
limit = MIN(limit, HTTP_MAX_HEADER_SIZE);
p_cr = (const char*) memchr(p, CR, limit);
p_lf = (const char*) memchr(p, LF, limit);
if (p_cr != NULL) {
if (p_lf != NULL && p_cr >= p_lf)
p = p_lf;
else
p = p_cr;
} else if (UNLIKELY(p_lf != NULL)) {
p = p_lf;
} else {
p = data + len;
}
--p;
break;
}
case h_connection:
case h_transfer_encoding:
assert(0 && "Shouldn't get here.");
break;
case h_content_length:
{
uint64_t t;
if (ch == ' ') break;
if (UNLIKELY(!IS_NUM(ch))) {
SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
parser->header_state = h_state;
goto error;
}
t = parser->content_length;
t *= 10;
t += ch - '0';
/* Overflow? Test against a conservative limit for simplicity. */
if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) {
SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
parser->header_state = h_state;
goto error;
}
parser->content_length = t;
break;
}
/* Transfer-Encoding: chunked */
case h_matching_transfer_encoding_chunked:
parser->index++;
if (parser->index > sizeof(CHUNKED)-1
|| c != CHUNKED[parser->index]) {
h_state = h_general;
} else if (parser->index == sizeof(CHUNKED)-2) {
h_state = h_transfer_encoding_chunked;
}
break;
case h_matching_connection_token_start:
/* looking for 'Connection: keep-alive' */
if (c == 'k') {
h_state = h_matching_connection_keep_alive;
/* looking for 'Connection: close' */
} else if (c == 'c') {
h_state = h_matching_connection_close;
} else if (c == 'u') {
h_state = h_matching_connection_upgrade;
} else if (STRICT_TOKEN(c)) {
h_state = h_matching_connection_token;
} else if (c == ' ' || c == '\t') {
/* Skip lws */
} else {
h_state = h_general;
}
break;
/* looking for 'Connection: keep-alive' */
case h_matching_connection_keep_alive:
parser->index++;
if (parser->index > sizeof(KEEP_ALIVE)-1
|| c != KEEP_ALIVE[parser->index]) {
h_state = h_matching_connection_token;
} else if (parser->index == sizeof(KEEP_ALIVE)-2) {
h_state = h_connection_keep_alive;
}
break;
/* looking for 'Connection: close' */
case h_matching_connection_close:
parser->index++;
if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) {
h_state = h_matching_connection_token;
} else if (parser->index == sizeof(CLOSE)-2) {
h_state = h_connection_close;
}
break;
/* looking for 'Connection: upgrade' */
case h_matching_connection_upgrade:
parser->index++;
if (parser->index > sizeof(UPGRADE) - 1 ||
c != UPGRADE[parser->index]) {
h_state = h_matching_connection_token;
} else if (parser->index == sizeof(UPGRADE)-2) {
h_state = h_connection_upgrade;
}
break;
case h_matching_connection_token:
if (ch == ',') {
h_state = h_matching_connection_token_start;
parser->index = 0;
}
break;
case h_transfer_encoding_chunked:
if (ch != ' ') h_state = h_general;
break;
case h_connection_keep_alive:
case h_connection_close:
case h_connection_upgrade:
if (ch == ',') {
if (h_state == h_connection_keep_alive) {
parser->flags |= F_CONNECTION_KEEP_ALIVE;
} else if (h_state == h_connection_close) {
parser->flags |= F_CONNECTION_CLOSE;
} else if (h_state == h_connection_upgrade) {
parser->flags |= F_CONNECTION_UPGRADE;
}
h_state = h_matching_connection_token_start;
parser->index = 0;
} else if (ch != ' ') {
h_state = h_matching_connection_token;
}
break;
default:
UPDATE_STATE(s_header_value);
h_state = h_general;
break;
}
}
parser->header_state = h_state;
COUNT_HEADER_SIZE(p - start);
if (p == data + len)
--p;
break;
}
case s_header_almost_done:
{
if (UNLIKELY(ch != LF)) {
SET_ERRNO(HPE_LF_EXPECTED);
goto error;
}
UPDATE_STATE(s_header_value_lws);
break;
}
case s_header_value_lws:
{
if (ch == ' ' || ch == '\t') {
UPDATE_STATE(s_header_value_start);
REEXECUTE();
}
/* finished the header */
switch (parser->header_state) {
case h_connection_keep_alive:
parser->flags |= F_CONNECTION_KEEP_ALIVE;
break;
case h_connection_close:
parser->flags |= F_CONNECTION_CLOSE;
break;
case h_transfer_encoding_chunked:
parser->flags |= F_CHUNKED;
break;
case h_connection_upgrade:
parser->flags |= F_CONNECTION_UPGRADE;
break;
default:
break;
}
UPDATE_STATE(s_header_field_start);
REEXECUTE();
}
case s_header_value_discard_ws_almost_done:
{
STRICT_CHECK(ch != LF);
UPDATE_STATE(s_header_value_discard_lws);
break;
}
case s_header_value_discard_lws:
{
if (ch == ' ' || ch == '\t') {
UPDATE_STATE(s_header_value_discard_ws);
break;
} else {
switch (parser->header_state) {
case h_connection_keep_alive:
parser->flags |= F_CONNECTION_KEEP_ALIVE;
break;
case h_connection_close:
parser->flags |= F_CONNECTION_CLOSE;
break;
case h_connection_upgrade:
parser->flags |= F_CONNECTION_UPGRADE;
break;
case h_transfer_encoding_chunked:
parser->flags |= F_CHUNKED;
break;
default:
break;
}
/* header value was empty */
MARK(header_value);
UPDATE_STATE(s_header_field_start);
CALLBACK_DATA_NOADVANCE(header_value);
REEXECUTE();
}
}
case s_headers_almost_done:
{
STRICT_CHECK(ch != LF);
if (parser->flags & F_TRAILING) {
/* End of a chunked request */
UPDATE_STATE(s_message_done);
CALLBACK_NOTIFY_NOADVANCE(chunk_complete);
REEXECUTE();
}
/* Cannot use chunked encoding and a content-length header together
per the HTTP specification. */
if ((parser->flags & F_CHUNKED) &&
(parser->flags & F_CONTENTLENGTH)) {
SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
goto error;
}
UPDATE_STATE(s_headers_done);
/* Set this here so that on_headers_complete() callbacks can see it */
parser->upgrade =
((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) ==
(F_UPGRADE | F_CONNECTION_UPGRADE) ||
parser->method == HTTP_CONNECT);
/* Here we call the headers_complete callback. This is somewhat
* different than other callbacks because if the user returns 1, we
* will interpret that as saying that this message has no body. This
* is needed for the annoying case of recieving a response to a HEAD
* request.
*
* We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so
* we have to simulate it by handling a change in errno below.
*/
if (settings->on_headers_complete) {
switch (settings->on_headers_complete(parser)) {
case 0:
break;
case 2:
parser->upgrade = 1;
/* falls through */
case 1:
parser->flags |= F_SKIPBODY;
break;
default:
SET_ERRNO(HPE_CB_headers_complete);
RETURN(p - data); /* Error */
}
}
if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
RETURN(p - data);
}
REEXECUTE();
}
case s_headers_done:
{
int hasBody;
STRICT_CHECK(ch != LF);
parser->nread = 0;
hasBody = parser->flags & F_CHUNKED ||
(parser->content_length > 0 && parser->content_length != ULLONG_MAX);
if (parser->upgrade && (parser->method == HTTP_CONNECT ||
(parser->flags & F_SKIPBODY) || !hasBody)) {
/* Exit, the rest of the message is in a different protocol. */
UPDATE_STATE(NEW_MESSAGE());
CALLBACK_NOTIFY(message_complete);
RETURN((p - data) + 1);
}
if (parser->flags & F_SKIPBODY) {
UPDATE_STATE(NEW_MESSAGE());
CALLBACK_NOTIFY(message_complete);
} else if (parser->flags & F_CHUNKED) {
/* chunked encoding - ignore Content-Length header */
UPDATE_STATE(s_chunk_size_start);
} else {
if (parser->content_length == 0) {
/* Content-Length header given but zero: Content-Length: 0\r\n */
UPDATE_STATE(NEW_MESSAGE());
CALLBACK_NOTIFY(message_complete);
} else if (parser->content_length != ULLONG_MAX) {
/* Content-Length header given and non-zero */
UPDATE_STATE(s_body_identity);
} else {
if (!http_message_needs_eof(parser)) {
/* Assume content-length 0 - read the next */
UPDATE_STATE(NEW_MESSAGE());
CALLBACK_NOTIFY(message_complete);
} else {
/* Read body until EOF */
UPDATE_STATE(s_body_identity_eof);
}
}
}
break;
}
case s_body_identity:
{
uint64_t to_read = MIN(parser->content_length,
(uint64_t) ((data + len) - p));
assert(parser->content_length != 0
&& parser->content_length != ULLONG_MAX);
/* The difference between advancing content_length and p is because
* the latter will automaticaly advance on the next loop iteration.
* Further, if content_length ends up at 0, we want to see the last
* byte again for our message complete callback.
*/
MARK(body);
parser->content_length -= to_read;
p += to_read - 1;
if (parser->content_length == 0) {
UPDATE_STATE(s_message_done);
/* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte.
*
* The alternative to doing this is to wait for the next byte to
* trigger the data callback, just as in every other case. The
* problem with this is that this makes it difficult for the test
* harness to distinguish between complete-on-EOF and
* complete-on-length. It's not clear that this distinction is
* important for applications, but let's keep it for now.
*/
CALLBACK_DATA_(body, p - body_mark + 1, p - data);
REEXECUTE();
}
break;
}
/* read until EOF */
case s_body_identity_eof:
MARK(body);
p = data + len - 1;
break;
case s_message_done:
UPDATE_STATE(NEW_MESSAGE());
CALLBACK_NOTIFY(message_complete);
if (parser->upgrade) {
/* Exit, the rest of the message is in a different protocol. */
RETURN((p - data) + 1);
}
break;
case s_chunk_size_start:
{
assert(parser->nread == 1);
assert(parser->flags & F_CHUNKED);
unhex_val = unhex[(unsigned char)ch];
if (UNLIKELY(unhex_val == -1)) {
SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
goto error;
}
parser->content_length = unhex_val;
UPDATE_STATE(s_chunk_size);
break;
}
case s_chunk_size:
{
uint64_t t;
assert(parser->flags & F_CHUNKED);
if (ch == CR) {
UPDATE_STATE(s_chunk_size_almost_done);
break;
}
unhex_val = unhex[(unsigned char)ch];
if (unhex_val == -1) {
if (ch == ';' || ch == ' ') {
UPDATE_STATE(s_chunk_parameters);
break;
}
SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
goto error;
}
t = parser->content_length;
t *= 16;
t += unhex_val;
/* Overflow? Test against a conservative limit for simplicity. */
if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) {
SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
goto error;
}
parser->content_length = t;
break;
}
case s_chunk_parameters:
{
assert(parser->flags & F_CHUNKED);
/* just ignore this shit. TODO check for overflow */
if (ch == CR) {
UPDATE_STATE(s_chunk_size_almost_done);
break;
}
break;
}
case s_chunk_size_almost_done:
{
assert(parser->flags & F_CHUNKED);
STRICT_CHECK(ch != LF);
parser->nread = 0;
if (parser->content_length == 0) {
parser->flags |= F_TRAILING;
UPDATE_STATE(s_header_field_start);
} else {
UPDATE_STATE(s_chunk_data);
}
CALLBACK_NOTIFY(chunk_header);
break;
}
case s_chunk_data:
{
uint64_t to_read = MIN(parser->content_length,
(uint64_t) ((data + len) - p));
assert(parser->flags & F_CHUNKED);
assert(parser->content_length != 0
&& parser->content_length != ULLONG_MAX);
/* See the explanation in s_body_identity for why the content
* length and data pointers are managed this way.
*/
MARK(body);
parser->content_length -= to_read;
p += to_read - 1;
if (parser->content_length == 0) {
UPDATE_STATE(s_chunk_data_almost_done);
}
break;
}
case s_chunk_data_almost_done:
assert(parser->flags & F_CHUNKED);
assert(parser->content_length == 0);
STRICT_CHECK(ch != CR);
UPDATE_STATE(s_chunk_data_done);
CALLBACK_DATA(body);
break;
case s_chunk_data_done:
assert(parser->flags & F_CHUNKED);
STRICT_CHECK(ch != LF);
parser->nread = 0;
UPDATE_STATE(s_chunk_size_start);
CALLBACK_NOTIFY(chunk_complete);
break;
default:
assert(0 && "unhandled state");
SET_ERRNO(HPE_INVALID_INTERNAL_STATE);
goto error;
}
}
/* Run callbacks for any marks that we have leftover after we ran our of
* bytes. There should be at most one of these set, so it's OK to invoke
* them in series (unset marks will not result in callbacks).
*
* We use the NOADVANCE() variety of callbacks here because 'p' has already
* overflowed 'data' and this allows us to correct for the off-by-one that
* we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p'
* value that's in-bounds).
*/
assert(((header_field_mark ? 1 : 0) +
(header_value_mark ? 1 : 0) +
(url_mark ? 1 : 0) +
(body_mark ? 1 : 0) +
(status_mark ? 1 : 0)) <= 1);
CALLBACK_DATA_NOADVANCE(header_field);
CALLBACK_DATA_NOADVANCE(header_value);
CALLBACK_DATA_NOADVANCE(url);
CALLBACK_DATA_NOADVANCE(body);
CALLBACK_DATA_NOADVANCE(status);
RETURN(len);
error:
if (HTTP_PARSER_ERRNO(parser) == HPE_OK) {
SET_ERRNO(HPE_UNKNOWN);
}
RETURN(p - data);
}
4、小节
对于
esp_http_client_perform()的接口的使用还是没有搞明白,仔细分析一下。
主要看看里面读取数据的函数
static int esp_http_client_get_data(esp_http_client_handle_t client)
{
if (client->state < HTTP_STATE_RES_COMPLETE_HEADER) {
return ESP_FAIL;
}
if (client->connection_info.method == HTTP_METHOD_HEAD) {
return 0;
}
esp_http_buffer_t *res_buffer = client->response->buffer;
ESP_LOGD(TAG, "data_process=%d, content_length=%d", client->response->data_process, client->response->content_length);
int rlen = esp_transport_read(client->transport, res_buffer->data, client->buffer_size, client->timeout_ms);
if (rlen >= 0) {
http_parser_execute(client->parser, client->parser_settings, res_buffer->data, rlen);
}
return rlen;
}
这个函数是用来读取HTTP应答的消息体BODY数据的,读取数据并解析。仔细看看这个client->response->buffer->data,其空间在初始化函数client->response->buffer->data = malloc(client->buffer_size)中分配,默认是512字节。
那它在解析过程中又是如何对他处理的
static int http_on_body(http_parser *parser, const char *at, size_t length)
{
esp_http_client_t *client = parser->data;
ESP_LOGD(TAG, "http_on_body %d", length);
client->response->buffer->raw_data = (char *)at;
if (client->response->buffer->output_ptr) {
memcpy(client->response->buffer->output_ptr, (char *)at, length);
client->response->buffer->output_ptr += length;
}
client->response->data_process += length;
client->response->buffer->raw_len += length;
http_dispatch_event(client, HTTP_EVENT_ON_DATA, (void *)at, length);
return 0;
}
利用esp_http_client_read()时将client->response->buffer->data 赋值到client->response->buffer->output_ptr指向的空间,指针指向向前移动。那么client->response->buffer->output_ptr指向的空间read函数形参剩余空间部分;
而在esp_http_client_get_data,由于client->response->buffer->output_ptr==NULL,在这里也有HTTP_EVENT_ON_DATA,且这里最关键的事情是分发了一个HTTP_EVENT_ON_DATA的事件给事件循环。因此我们可以在事件循环里完成其他操作。 good,这样就顺畅了。还是要向ESP-IDF的开发人员致敬。
再看看http_dispatch_event(client, HTTP_EVENT_ON_DATA, (void *)at, length);
static esp_err_t http_dispatch_event(esp_http_client_t *client, esp_http_client_event_id_t event_id, void *data, int len)
{
esp_http_client_event_t *event = &client->event;
if (client->event_handler) {
event->event_id = event_id;
event->user_data = client->user_data;
event->data = data;
event->data_len = len;
return client->event_handler(event);
}
return ESP_OK;
}
在这里调用了预先设置好的事件回调函数
client->event_handler(HTTP_EVENT_ON_DATA);
默认是这样的
case HTTP_EVENT_ON_DATA:
ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
if (!esp_http_client_is_chunked_response(evt->client)) {
// Write out data
// printf("%.*s", evt->data_len, (char*)evt->data); //
}break;
当我们使用esp_http_client_perform时,可以从这里获取应答的body信息体。具体自己处理。