ESP32 的esp_http_client详解

2023-11-18

说明:我使用的是esp-idf-V3.1.3 ,官方给我们封装好了 HTTP,使用起来还是很方便

一、wifi连接

在main函数里面主要是做了wifi连接的初始化和HTTP任务的创建,如下是main的全部内容
 

void app_main()
{
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);
    app_wifi_initialise();   //wifi连接部分

    xTaskCreate(&http_test_task, "http_test_task", 8192*2, NULL, 5, NULL);//创建HTTP任务
}

进入wifi初始化函数app_wifi_initialise(void),里面主要是设置连接的wifi,及启动wifi连接。

void app_wifi_initialise(void)
{
    tcpip_adapter_init();//tcp协议初始化
    wifi_event_group = xEventGroupCreate();//创建一个事件组
    ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));//wifi 事件
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = "TPLINK",
            .password = "12345678",//设置要连接的wifi
        },
    };
    ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));//设置模式
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));//设置连接wifi的参数
    ESP_ERROR_CHECK(esp_wifi_start());//启动wifi

}

启动wifi之后就会产生一个SYSTEM_EVENT_STA_START事件,在里面开始连接wifi,连接成功之后就会产生一个事件给创建的HTTP, http是一直在等待wifi连接成功才往下执行,具体可以看下代码

static esp_err_t event_handler(void *ctx, system_event_t *event)
{
    switch (event->event_id) {
        case SYSTEM_EVENT_STA_START:
            esp_wifi_connect();
            break;
        case SYSTEM_EVENT_STA_GOT_IP:
            xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
            break;
        case SYSTEM_EVENT_STA_DISCONNECTED:
            /* This is a workaround as ESP32 WiFi libs don't currently
               auto-reassociate. */
            esp_wifi_connect();
            xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
            break;
        default:
            break;
    }
    return ESP_OK;
}

二、HTTP 任务解析

1、http任务里面我就保留了一个http_rest();,为了自己看调试数据方便

static void http_test_task(void *pvParameters)
{
    app_wifi_wait_connected();
    ESP_LOGI(TAG, "Connected to AP, begin http example");
    http_rest();
    /* http_auth_basic();
    http_auth_basic_redirect();
    http_auth_digest();
    http_relative_redirect();
    http_absolute_redirect();
    https();
    http_redirect_to_https();
    http_download_chunk();
    http_perform_as_stream_reader();
    ESP_LOGI(TAG, "Finish http example");*/
    vTaskDelete(NULL);
}

2、http_rest函数,里面我只保留了POST ,而且做了修改连接到自己的服务器,代码如下

static void http_rest()
{
   esp_http_client_config_t config = {
        .url = "http://wjabc.wjabc.top",

        .event_handler = _http_event_handler,
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);

    char post_data1[1024] = {0};

    Get_CurrentData(post_data1);//得到JSON数据 用如 post_data

    esp_http_client_set_url(client, "http://wjabc.wjabc.top/m/userAction!connect");
//  esp_http_client_set_url(client, "http://wjabc.wjabc.top/m/userAction!deviceSave");
    esp_http_client_set_method(client, HTTP_METHOD_POST);
    esp_http_client_set_post_field(client, post_data1, strlen(post_data1));
    esp_err_t err  = esp_http_client_perform(client);
    if (err == ESP_OK) {
        ESP_LOGI(TAG, "HTTP POST Status = %d, content_length = %d",
                esp_http_client_get_status_code(client),
                esp_http_client_get_content_length(client));
        int len =  esp_http_client_get_content_length(client);
        int read_len = 0;
        char buf[1024] = {0};
        read_len = esp_http_client_read(client, buf, len);
        printf("\nrecv data len:%d %d %s\n",read_len,len,buf);
    } else {
        ESP_LOGE(TAG, "HTTP POST request failed: %s", esp_err_to_name(err));
    }
}

这里是esp32 HTTP client 主要部分,下面详细讲解这里,因为我也是小白,写的不好或者不对的地方,还希望大家指出来

1、 esp_http_client_config_t 结构,这里主要是初始化了 URL 和http的一个事件

 esp_http_client_config_t config = {
        .url = "http://wjabc.wjabc.top",

        .event_handler = _http_event_handler,
    };

2、esp_http_client_handle_t client = esp_http_client_init(&config)函数,

作用:分配空间,初始化等

进入函数里面看看函数的全部

esp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *config)
{

    esp_http_client_handle_t client;
    transport_handle_t tcp;
    bool _success;
    //第一部分分配空间
    _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;
    }
     //第二部分
    _success = (
                   (client->transport_list = transport_list_init()) && //分配空间
                   (tcp = transport_tcp_init()) && //分配TCP空间及初始化TCP 函数
                   (transport_set_default_port(tcp, DEFAULT_HTTP_PORT) == ESP_OK) &&//设置默认端口为80
                   (transport_list_add(client->transport_list, tcp, "http") == ESP_OK)//设置默认协议为http
               );
    if (!_success) {
        ESP_LOGE(TAG, "Error initialize transport");
        esp_http_client_cleanup(client);
        return NULL;
    }


    if (_set_config(client, config) != ESP_OK) {
        ESP_LOGE(TAG, "Error set configurations");
        esp_http_client_cleanup(client);
        return NULL;
    }
    //第三部分 给请求和应答数据分配空间
    _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;
    }
    //第四部分 设置url和用户代理 host
    _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;
    }
    //第五部分设置各种函数指针的值
    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;

    client->state = HTTP_STATE_INIT;
    return client;
}

这个里面主要重点介绍下第二部分的transport_tcp_init()函数

 

transport_handle_t transport_tcp_init()
{
    transport_handle_t t = transport_init();
    transport_tcp_t *tcp = calloc(1, sizeof(transport_tcp_t));
    HTTP_MEM_CHECK(TAG, tcp, return NULL);
    tcp->sock = -1;
    transport_set_func(t, tcp_connect, tcp_read, tcp_write, tcp_close, tcp_poll_read, tcp_poll_write, tcp_destroy);
    transport_set_context_data(t, tcp);

    return t;
}

这个函数里面可以分成两部分

a、给TCP分配空间

b、设置TCP的回调函数

 tcp_connect, tcp_read, tcp_write, tcp_close, tcp_poll_read, tcp_poll_write, tcp_destroy

 tcp_connect函数,这个函数可以分成4部分

a、resolve_dns(host, &remote_ip)                                     域名解析到IP

b、tcp->sock = socket(PF_INET, SOCK_STREAM, 0);   创建socket套接字

c、setsockopt(tcp->sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); 设置接收数据超时时间

d、connect(tcp->sock, (struct sockaddr *)(&remote_ip), sizeof(struct sockaddr)) != 0 连接TCP到服务器


static int tcp_connect(transport_handle_t t, const char *host, int port, int timeout_ms)
{
    struct sockaddr_in remote_ip;
    struct timeval tv;
    transport_tcp_t *tcp = transport_get_context_data(t);

    bzero(&remote_ip, sizeof(struct sockaddr_in));

    //if stream_host is not ip address, resolve it AF_INET,servername,&serveraddr.sin_addr
    if (inet_pton(AF_INET, host, &remote_ip.sin_addr) != 1) {
        if (resolve_dns(host, &remote_ip) < 0) {//域名解析
            return -1;
        }
    }

    tcp->sock = socket(PF_INET, SOCK_STREAM, 0);//创建socket 

    if (tcp->sock < 0) {
        ESP_LOGE(TAG, "Error create socket");
        return -1;
    }

    remote_ip.sin_family = AF_INET;
    remote_ip.sin_port = htons(port);

    http_utils_ms_to_timeval(timeout_ms, &tv);

    setsockopt(tcp->sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));//设置接收超时

    ESP_LOGD(TAG, "[sock=%d],connecting to server IP:%s,Port:%d...",
             tcp->sock, ipaddr_ntoa((const ip_addr_t*)&remote_ip.sin_addr.s_addr), port);
    //连接TCP
    if (connect(tcp->sock, (struct sockaddr *)(&remote_ip), sizeof(struct sockaddr)) != 0) {
        close(tcp->sock);
        tcp->sock = -1;
        return -1;
    }
    return tcp->sock;
}

剩下的几个大家自己看下源码,就不介绍了

3、Get_CurrentData(post_data1); 这个是我自己写的,里面就是打包json数据,esp32自带cJSON,直接使用就可以,详细代码

void Get_CurrentData(char *dat){
    cJSON *root;

    root = cJSON_CreateObject();
    cJSON_AddStringToObject(root,"HardwareVersion","V1.0");
    cJSON_AddStringToObject(root,"SoftwareVersion","V1.0");
    cJSON_AddStringToObject(root,"OnlyID","112233445566");
    cJSON_AddStringToObject(root,"Status","true");
    char *out = cJSON_Print(root);
    memcpy(dat,out,strlen(out));
    cJSON_Delete(root);
    free(out);
}

4、esp_http_client_set_url(client, "http://wjabc.wjabc.top/m/userAction!connect"); 函数,这个函数主要是解析url ,然后得到各个字段,下面是这里详细介绍了URL的组成

URL详解

  URL(Uniform Resource Locator) 地址用于描述一个网络上的资源, 基本格式如下

schema://host[:port#]/path/.../[;url-params][?query-string][#anchor]

  scheme 指定低层使用的协议(例如:http, https, ftp)

  host HTTP服务器的IP地址或者域名

  port# HTTP服务器的默认端口是80,这种情况下端口号可以省略。如果使用了别的端口,必须指明,例如 http://www.cnblogs.com:8080/

  path 访问资源的路径

  url-params

  query-string 发送给http服务器的数据

  anchor- 锚

  URL 的一个例子

http://www.mywebsite.com/sj/test;id=8079?name=sviergn&x=true#stuff

Schema: http

host: www.mywebsite.com

path: /sj/test

URL params: id=8079

Query String: name=sviergn&x=true

Anchor: stuff

esp_err_t esp_http_client_set_url(esp_http_client_handle_t client, const char *url)
{
    char *old_host = NULL;
    struct http_parser_url purl;
    int old_port;

    if (client == NULL || url == NULL) {
        ESP_LOGE(TAG, "client or url must not NULL");
        return ESP_ERR_INVALID_ARG;
    }

    http_parser_url_init(&purl);
    printf("----------------------http_client_set_url--------------------------\n");
    int parser_status = http_parser_parse_url(url, strlen(url), 0, &purl);

    if (parser_status != 0) {
        ESP_LOGE(TAG, "Error parse url %s", url);
        return ESP_ERR_INVALID_ARG;
    }
    old_host = client->connection_info.host;
    old_port = client->connection_info.port;
    if(old_host != old_host){
    	printf("old_host:%s\n",old_host);
    }else{
    	printf("old_host:%s\n","NULL");
    }

    printf("old_port:%d\n",old_port);
    if (purl.field_data[UF_HOST].len) {
        http_utils_assign_string(&client->connection_info.host, url + purl.field_data[UF_HOST].off, purl.field_data[UF_HOST].len);
        HTTP_MEM_CHECK(TAG, client->connection_info.host, return ESP_ERR_NO_MEM);
    }
    printf("host:%s\n",client->connection_info.host);
    // Close the connection if host was changed
    if (old_host && client->connection_info.host
            && strcasecmp(old_host, (const void *)client->connection_info.host) != 0) {
        ESP_LOGD(TAG, "New host assign = %s", client->connection_info.host);
        if (esp_http_client_set_header(client, "Host", client->connection_info.host) != ESP_OK) {
            return ESP_ERR_NO_MEM;
        }
        esp_http_client_close(client);
    }
    printf("host:%s\n",client->connection_info.host);
    if (purl.field_data[UF_SCHEMA].len) {
        http_utils_assign_string(&client->connection_info.scheme, url + purl.field_data[UF_SCHEMA].off, purl.field_data[UF_SCHEMA].len);
        HTTP_MEM_CHECK(TAG, client->connection_info.scheme, return ESP_ERR_NO_MEM);

        if (strcasecmp(client->connection_info.scheme, "http") == 0) {
            client->connection_info.port = DEFAULT_HTTP_PORT;
        } else if (strcasecmp(client->connection_info.scheme, "https") == 0) {
            client->connection_info.port = DEFAULT_HTTPS_PORT;
        }
    }
    printf("scheme:%s\n",client->connection_info.scheme);
    printf("port:%d\n",client->connection_info.port);
    if (purl.field_data[UF_PORT].len) {
        client->connection_info.port = strtol((const char*)(url + purl.field_data[UF_PORT].off), NULL, 10);
    }

    if (old_port != client->connection_info.port) {
        esp_http_client_close(client);
    }

    if (purl.field_data[UF_USERINFO].len) {
        char *user_info = NULL;
        http_utils_assign_string(&user_info, url + purl.field_data[UF_USERINFO].off, purl.field_data[UF_USERINFO].len);
        if (user_info) {
            char *username = user_info;
            char *password = strchr(user_info, ':');
            if (password) {
                *password = 0;
                password ++;
                http_utils_assign_string(&client->connection_info.password, password, 0);
                HTTP_MEM_CHECK(TAG, client->connection_info.password, return ESP_ERR_NO_MEM);
            }
            http_utils_assign_string(&client->connection_info.username, username, 0);
            HTTP_MEM_CHECK(TAG, client->connection_info.username, return ESP_ERR_NO_MEM);
            free(user_info);
        } else {
            return ESP_ERR_NO_MEM;
        }
    } else {
        free(client->connection_info.username);
        free(client->connection_info.password);
        client->connection_info.username = NULL;
        client->connection_info.password = NULL;
    }
    if(client->connection_info.username != NULL){
    	printf("username:%s\n",client->connection_info.username);
    }else{
    	printf("username:%s\n","NULL");
    }
    if(client->connection_info.password != NULL){
    	printf("password:%s\n",client->connection_info.password);
    }else{
    	printf("password:%s\n","NULL");
    }

    //Reset path and query if there are no information
    if (purl.field_data[UF_PATH].len) {
        http_utils_assign_string(&client->connection_info.path, url + purl.field_data[UF_PATH].off, purl.field_data[UF_PATH].len);
    } else {
        http_utils_assign_string(&client->connection_info.path, "/", 0);
    }
    printf("path:%s\n",client->connection_info.path);
    HTTP_MEM_CHECK(TAG, client->connection_info.path, return ESP_ERR_NO_MEM);

    if (purl.field_data[UF_QUERY].len) {
        http_utils_assign_string(&client->connection_info.query, url + purl.field_data[UF_QUERY].off, purl.field_data[UF_QUERY].len);
        HTTP_MEM_CHECK(TAG, client->connection_info.query, return ESP_ERR_NO_MEM);
    } else if (client->connection_info.query) {
        free(client->connection_info.query);
        client->connection_info.query = NULL;
    }
    if(client->connection_info.query != NULL){
    	printf("query:%s\n",client->connection_info.query);
    }else{
    	printf("query:%s\n","NULL");
    }

    return ESP_OK;
}

esp_http_client_set_url 源码比较多,大家自己仔细看看,我用url = http://wjabc.wjabc.top/m/userAction!connect 作为参数

解析得到的数据:

old_host:NULL
old_port:80
host:wjabc.wjabc.top
host:wjabc.wjabc.top
scheme:http
port:80
username:NULL
password:NULL
path:/m/userAction!connect
query:NULL

这些数据都是赋值给了connection_info_t结构体的成员

typedef struct {
    char                         *url;
    char                         *scheme;
    char                         *host;
    int                          port;
    char                         *username;
    char                         *password;
    char                         *path;
    char                         *query;
    char                         *cert_pem;
    esp_http_client_method_t     method;
    esp_http_client_auth_type_t  auth_type;
    esp_http_client_transport_t  transport_type;
    int                          max_store_header_size;
} connection_info_t;

5、esp_http_client_set_method(client, HTTP_METHOD_POST);函数,这个函数就比较简单了

作用:设置请求方式为POST

esp_err_t esp_http_client_set_method(esp_http_client_handle_t client, esp_http_client_method_t method)
{
    client->connection_info.method = method;
    return ESP_OK;
}

可以看到 也是给上面的结构体复制,

6、esp_http_client_set_post_field(client, post_data1, strlen(post_data1));函数,

作用:设置url,解析参数

esp_err_t esp_http_client_set_post_field(esp_http_client_handle_t client, const char *data, int len)
{
    esp_err_t err = ESP_OK;
    client->post_data = (char *)data;
    client->post_len = len;
    ESP_LOGD(TAG, "set post file length = %d", len);
    if (client->post_data) {
    	err = esp_http_client_set_header(client, "Content-Type", "application/json");
       // err = esp_http_client_set_header(client, "Content-Type", "application/x-www-form-urlencoded");
    } else {
        client->post_len = 0;
        err = esp_http_client_set_header(client, "Content-Type", NULL);
    }
    return err;
}

这个函数首先设置了HTTP请求的数据body,这个数据是第二步得到的JSON数据,然后下面设置了Content-Type内容编码,我们这里是cJSON,所以设置为application/json,注释掉原来的了。

7、esp_http_client_perform(client); 这个函数是重点,完成TCP发送和接收,主要说下三个部分

esp_err_t esp_http_client_perform(esp_http_client_handle_t client)
{
    esp_err_t err;
    do {  //第一部分
        if ((err = esp_http_client_open(client, client->post_len)) != ESP_OK) {
            return err;
        }
        if (client->post_data && client->post_len) {
        	printf("\r\nclient_open post_data len:%d %s\r\n\n",client->post_len,client->post_data);
//第二部分
            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;
            }
        }
第三大部分
        int lenth = esp_http_client_fetch_headers(client);
        if (lenth < 0) {
            return ESP_ERR_HTTP_FETCH_HEADER;
        }
        printf("\n 12recv Data len:%d %s\n\n",client->response->buffer->len,client->response->buffer->data);

        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");
                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");
                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;
}

a、esp_http_client_open(client, client->post_len)) 函数

作用:连接TCP,并发送HTTP头

esp_err_t esp_http_client_open(esp_http_client_handle_t client, int write_len)
{
    esp_err_t err;
    if (client->state == HTTP_STATE_UNINIT) {
        ESP_LOGE(TAG, "Client has not been initialized");
        return ESP_ERR_INVALID_STATE;
    }

    if ((err = esp_http_client_prepare(client)) != ESP_OK) {
        ESP_LOGE(TAG, "Failed to initialize request data");
        esp_http_client_close(client);
        return err;
    }

    if (client->state < HTTP_STATE_CONNECTED) {
        ESP_LOGD(TAG, "Begin connect to: %s://%s:%d", client->connection_info.scheme, client->connection_info.host, client->connection_info.port);
        client->transport = transport_list_get_transport(client->transport_list, client->connection_info.scheme);
        if (client->transport == NULL) {
            ESP_LOGE(TAG, "No transport found");
            return ESP_ERR_HTTP_INVALID_TRANSPORT;
        }
//第一部分连接TCP
        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;
        }
        http_dispatch_event(client, HTTP_EVENT_ON_CONNECTED, NULL, 0);//产生一个连接成功事件
        client->state = HTTP_STATE_CONNECTED;
    }

    if (write_len >= 0) {
        http_header_set_format(client->request->headers, "Content-Length", "%d", write_len);
    } else if (write_len < 0) {
        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) {
        ESP_LOGE(TAG, "Out of buffer");
        return ESP_ERR_HTTP_CONNECT;
    }
    printf("\n---->:%s\n",client->request->buffer->data);
    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) {
            ESP_LOGE(TAG, "Out of buffer");
            return ESP_ERR_HTTP_CONNECT;
        }
    }
    printf("\n---->:%s\n",client->request->buffer->data);
    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) {
        ESP_LOGE(TAG, "Out of buffer");
        return ESP_ERR_HTTP_CONNECT;
    }
    wlen -= first_line;

    while ((header_index = http_header_generate_string(client->request->headers, header_index, client->request->buffer->data + first_line, &wlen))) {
        if (wlen <= 0) {
            break;
        }
        if (first_line) {
            wlen += first_line;
            first_line = 0;
        }
        client->request->buffer->data[wlen] = 0;
        ESP_LOGD(TAG, "Write header[%d]: %s", header_index, client->request->buffer->data);
        printf("\r\nclient_open Write Data len:%d %s\r\n\n",wlen,client->request->buffer->data);
//第二部分 发送http头
        if (transport_write(client->transport, client->request->buffer->data, wlen, client->timeout_ms) <= 0) {
            ESP_LOGE(TAG, "Error write request");
            esp_http_client_close(client);
            return ESP_ERR_HTTP_WRITE_DATA;
        }
        wlen = client->buffer_size;
    }
    client->state = HTTP_STATE_REQ_COMPLETE_HEADER;
    return ESP_OK;
}

函数源码比较长,这个里面主要可以分成两部分,其它基本是组包,组成HTTP头

1、transport_connect(client->transport, client->connection_info.host, client->connection_info.port, client->timeout_ms)

到这里才调用TCP 连接服务器,连接成功之后通过http_dispatch_event(client, HTTP_EVENT_ON_CONNECTED, NULL, 0)发送一个事件给HTTP的事件函数

2、transport_write(client->transport, client->request->buffer->data, wlen, client->timeout_ms)

到这是连接成功之后发送http头数据,esp32是 http头和body分开发送的

b、esp_http_client_write(client, client->post_data, client->post_len)

这个函数是发送body的函数,详细见下图,上面部分是esp_http_client_open函数里面发送的,下面的是esp_http_client_write发送的。

c、int esp_http_client_fetch_headers(esp_http_client_handle_t client)

http发送之后,调用esp_http_client_fetch_headers等待接收数据,

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;

    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) {
            return ESP_FAIL;
        }
        http_parser_execute(client->parser, client->parser_settings, buffer->data, buffer->len);
    }
    buffer->data[buffer->len] = 0;
    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;
}

这个函数比较简单,就是在里面一直等待http返回应答数据,里面设置了接收超时,接收到数据就退出,下面图就是接收到的http应答

8、 esp_http_client_get_status_code(client)

作用:判断http应答状态 正常是200

9、esp_http_client_get_content_length(client))

作用:得到body的数据长度

10、esp_http_client_read(client, buf, len);

作用:获取HTTP 返回的body数据

下面就是得到的数据

11、 esp_http_client_cleanup(client);

作用:用的最后一个函数。它将关闭连接(如果有的话)并释放分配给HTTP客户端的所有内存

 

到这里基本结束了,写了2个多小时,深夜2.17

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ESP32 的esp_http_client详解 的相关文章

随机推荐

  • HTML基础标签及其语义

    一 HTML语法规范 1 1 基本语法表述 标签通常都是成对出现 双标签 开始结束标签 br 单标签 1 2 标签关系 包含关系与并列关系 包含关系 父标签 子标签 并列关系 1 3 HTML基本结构标签 骨架标签 页面内容也是在这些基本标
  • 陷波器设计

    中心频率 f c H z f c rm Hz fc Hz 3dB陷波器带宽 f
  • emplace_back与push_back异同

    vector的emplace back与push back 文章目录 vector的emplace back与push back 前言 1 区别总览 2 push back 支持右值引用 不支持传入多个构造参数 总是会进行拷贝构造 3 em
  • C++学习笔记——随机数

    利用rand 函数生成随机数如何随机是根据随机数种子来生成 一个程序的随机数种子一般是固定的 所以是伪随机数 若想生成真随机数 则用电脑的时间来初始化这个随机数种子 include
  • LLM 赋能的研发效能:如何探索软件开发新工序?

    上周末 我们 我和我的同事谢保龙 在 QCon 广州 2023 上分享了一个 AI 结合研发效能的话题 探索软件开发新工序 LLM 赋能研发效能提升 我们分享了 Thoughtworks 在过去的两个月里对于 LLM 大语言模型 结合软件开
  • 高防CDN和加速CDN有什么区别?

    高防CDN和加速CDN有什么区别 随着互联网技术的不断发展 CDN Content Delivery Network 已经成为了网络加速和安全保障的重要手段 在CDN的领域中 高防CDN和加速CDN是两种不同的CDN服务 它们有不同的特点和
  • 重积分的计算与理解

    主要分为二重积分和三重积分 二重积分 二重积分的基本思想是变成两次积分 物理意义已知面密度f 算质量 即首先把y方向的每一根线段计算出质量 相当于把y的线捏起来了 然后算x 主要方法如下 计算 D f x
  • 数据结构之链表:单向链表、单向循环链表、双向链表及基本操作

    目录 一 链表 1 1 单向链表 1 1 1 单链表的操作 1 2 单向循环链表 1 3 双向链表 了解 二 链表与顺序表的对比 一 链表 链表 将元素存放在通过链接构造起来的一系列存储块中 在每一个节点 数据存储单元 里存放下一个节点的位
  • 2020美赛建模F题思路和理解

    2020 MCM ICM 美国大学生数学建模竞赛 MCM ICM F题 2020 ICM Weekend 2 Problem F The Place I Called Home 思路和理解 问题中心 设计模型研究海平面上升对相关国家的人口
  • 报错:Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

    这两天自己搭spingmvc 总是报错 找不到自动注册的bean Could not autowire field private lf service UserService lf controllers UserController u
  • java实现队列_java实现队列

    队列的定义 队列的特点是节点的排队次序和出队次序按入队时间先后确定 即先入队者先出队 后入队者后出队 即我们常说的FIFO first in first out 先进先出 顺序队列定义及相关操作 顺序存储结构存储的队列称为顺序队列 内部使用
  • 使用 pad_sequence

    pad sequence 是用来干嘛的 首先 pad sequence 是用来对对tensor做padding 的 先看官方示例 文档地址https pytorch org docs stable generated torch nn ut
  • seaborn分类数据的绘制

    转载 seaborn分类数据的绘制https zhuanlan zhihu com p 27683042 总结很全的资料
  • json工具类ObjectMapper的详细使用记录

    1 用于json与其他对象之间转化的工具类 public class JsonUtil private static final ObjectMapper MAPPER new ObjectMapper private static fin
  • Java面向对象三大特性:继承、封装、多态

    面向对象编程 一 继承 1 表现形式 A extends B 2 子类继承了父类的什么 BAT 面试 3 this 和 super 关键字的区别 面试 4 Java 中访问权限修饰符 5 重写 与 重载的区别 面试 6 final 的用法
  • Qt学习笔记(四)ui界面通过样式表添加图片、背景、字体颜色等

    1 创建工程 添加ui 添加qrc资源文件 将图片导入 2 进入ui 可对背景界面 可对label pushbutton等构件右键改变样式表 进入样式表 看到添加资源下有三个选项 background image board image i
  • 【密码学】古代、古典密码

    古代密码 数据的保密基于加密算法的保密 Scytale密码 使用一条纸袋作为载体 环绕在一根固定半径的圆柱上 加密 在绕好的纸带上写上明文 解开缠绕后 就是加密好的 无序的密文 圆柱的半径就是密钥 解密 找到相同大小的圆柱 将纸带缠绕在援助
  • 随机数函数(一):均匀分布的随机数函数

    随机数是随机产生的数 可以分为两种 真随机数和伪随机数 计算机所使用的都是伪随机数 并不能像投硬币或者投骰子那样产生真正随机的 事前不能确定结果的数值 事实上 计算机在模拟产生随机数前就会按照某种算法产生一个特定的序列 也就意味着每个随机数
  • 主流WEB漏洞扫描器种类及其指纹特征分析

    福利 网络安全重磅福利 入门 进阶全套282G学习资源包免费分享 0x01 Web 漏洞扫描 器 国内 绿盟 WVSS https www nsfocus com cn html 2019 206 0911 8 html 安恒 明鉴 htt
  • ESP32 的esp_http_client详解

    说明 我使用的是esp idf V3 1 3 官方给我们封装好了 HTTP 使用起来还是很方便 一 wifi连接 在main函数里面主要是做了wifi连接的初始化和HTTP任务的创建 如下是main的全部内容 void app main e