ESP32学习笔记(13)——HTTP客户端

Posted Leung_ManWah

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ESP32学习笔记(13)——HTTP客户端相关的知识,希望对你有一定的参考价值。

一、HTTP简介

HTTP(Hyper Text Transfer Protocol) 超文本传输协议,是一种建立在 TCP 上的无状态连接,整个基本的工作流程是客户端发送一个 HTTP 请求,说明客户端想要访问的资源和请求的动作,服务端收到请求之后,服务端开始处理请求,并根据请求做出相应的动作访问服务器资源,最后通过发送 HTTP 响应把结果返回给客户端。其中一个请求的开始到一个响应的结束称为事务,当一个事务结束后还会在服务端添加一条日志条目。

1.1 HTTP请求

HTTP 请求是客户端往服务端发送请求动作,告知服务器自己的要求。其中信息由三部分组成:

  • 请求方法(状态行):一般有 GETPOSTPUTDELETE,含义分别是获取、修改、上传、删除,其中 GET 方式仅仅为获取服务器资源,方式较为简单,因此在请求方式为 GET 的 HTTP 请求数据中,请求正文部分可以省略,直接将想要获取的资源添加到 URL 中。
  • 请求头:包括一些访问的域名、用户代理、Cookie等信息。
  • 请求正文:就是HTTP请求的数据。

1.2 HTTP响应

服务器收到了客户端发来的 HTTP 请求后,根据 HTTP 请求中的动作要求,服务端做出具体的动作,将结果回应给客户端,称为 HTTP 响应。数据主要由三部分组成:

  • 协议状态(状态行):包括协议版本 Version、状态码 Status Code、回应短语。

    常见状态码的含义:
    200—OK/请求已经正常处理完毕
    301—/请求永久重定向
    302—/请求临时重定向
    304—/请求被重定向到客户端本地缓存
    400—/客户端请求存在语法错误
    401—/客户端请求没有经过授权
    403—/客户端的请求被服务器拒绝,一般为客户端没有访问权限
    404—/客户端请求的URL在服务端不存在
    500—/服务端永久错误
    503—/服务端发生临时错误

  • 响应头:包括搭建服务器的软件,发送响应的时间,回应数据的格式等信息。

  • 响应正文:就是响应的具体数据。

二、API说明

ESP-IDF 编程指南——ESP HTTP Client

以下 HTTP 客户端接口位于 esp_http_client/include/esp_http_client.h

2.1 esp_http_client_init

2.2 esp_http_client_perform

2.3 esp_http_client_set_url

2.4 esp_http_client_set_method

2.5 esp_http_client_set_header

2.6 esp_http_client_set_post_field

2.7 esp_http_client_get_content_length

2.8 esp_http_client_get_status_code

2.9 esp_http_client_cleanup

三、核心代码

3.1 HTTP事件回调函数

esp_err_t _http_event_handler(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;
}

3.2 Get请求

  • esp_http_client_init():要使用HTTP客户端,我们必须做的第一件事就是esp_http_client通过esp_http_client_config_t配置在此函数中创建一个pass 。我们未定义哪些配置值,该库将使用默认值。

  • esp_http_client_perform()esp_http_client需要使用init函数创建的参数。此函数执行esp_http_client的所有操作,从打开连接,发送数据,下载数据和关闭连接(如有必要)。所有相关事件都将在event_handle(由定义esp_http_client_config_t)中调用。此功能执行其工作并阻止当前任务,直到完成

  • esp_http_client_cleanup():完成esp_http_client的任务后,这是最后一个要调用的函数。它将关闭连接(如果有)并释放分配给HTTP客户端的所有内存

#define MAX_HTTP_OUTPUT_BUFFER 2048

static void http_rest_with_url(void)
{
    char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
    /**
     * NOTE: All the configuration parameters for http_client must be spefied either in URL or as host and path parameters.
     * If host and path parameters are not set, query parameter will be ignored. In such cases,
     * query parameter should be specified in URL.
     *
     * If URL as well as host and path parameters are specified, values of host and path will be considered.
     */
    esp_http_client_config_t config = {
        .host = "httpbin.org",
        .path = "/get",
        .query = "esp",
        .event_handler = _http_event_handler,
        .user_data = local_response_buffer,        // Pass address of local buffer to get response
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);

    // GET
    esp_err_t err = esp_http_client_perform(client);
    if (err == ESP_OK) {
        ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
                esp_http_client_get_status_code(client),
                esp_http_client_get_content_length(client));
    } else {
        ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err));
    }
    ESP_LOG_BUFFER_HEX(TAG, local_response_buffer, strlen(local_response_buffer));

    esp_http_client_cleanup(client);
}

3.3 Post请求

  • esp_http_client_perform()esp_http_client需要使用init函数创建的参数。此函数执行esp_http_client的所有操作,从打开连接,发送数据,下载数据和关闭连接(如有必要)。所有相关事件都将在event_handle(由定义esp_http_client_config_t)中调用。此功能执行其工作并阻止当前任务,直到完成

  • esp_http_client_cleanup():完成esp_http_client的任务后,这是最后一个要调用的函数。它将关闭连接(如果有)并释放分配给HTTP客户端的所有内存

static void http_rest_with_url(void)
{
    /**
     * NOTE: All the configuration parameters for http_client must be spefied either in URL or as host and path parameters.
     * If host and path parameters are not set, query parameter will be ignored. In such cases,
     * query parameter should be specified in URL.
     *
     * If URL as well as host and path parameters are specified, values of host and path will be considered.
     */
    esp_http_client_config_t config = {
        .event_handler = _http_event_handler,
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);

    // POST
    const char *post_data = "{\\"field1\\":\\"value1\\"}";
    esp_http_client_set_url(client, "http://httpbin.org/post");
    esp_http_client_set_method(client, HTTP_METHOD_POST);
    esp_http_client_set_header(client, "Content-Type", "application/json");
    esp_http_client_set_post_field(client, post_data, strlen(post_data));
    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));
    } else {
        ESP_LOGE(TAG, "HTTP POST request failed: %s", esp_err_to_name(err));
    }

    esp_http_client_cleanup(client);
}

四、例子:SmartConfig连接AP后Get请求百度首页

/* Esptouch example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_wpa2.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_smartconfig.h"

#include "esp_http_client.h"

/* FreeRTOS event group to signal when we are connected & ready to make a request */
static EventGroupHandle_t s_wifi_event_group;

/* The event group allows multiple bits for each event,
   but we only care about one event - are we connected
   to the AP with an IP? */
static const int CONNECTED_BIT = BIT0;
static const int ESPTOUCH_DONE_BIT = BIT1;
static const char *TAG = "smartconfig_example";

static void smartconfig_example_task(void * parm);

#define MAX_HTTP_OUTPUT_BUFFER 2048

esp_err_t _http_event_handler(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;
}

static void http_test_task(void *pvParameters)
{
    while(1)
    {
        char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
        /**
         * NOTE: All the configuration parameters for http_client must be spefied either in URL or as host and path parameters.
         * If host and path parameters are not set, query parameter will be ignored. In such cases,
         * query parameter should be specified in URL.
         *
         * If URL as well as host and path parameters are specified, values of host and path will be considered.
         */
        esp_http_client_config_t config = {
            .method = HTTP_METHOD_GET,           //get请求
            .url = "https://www.baidu.com/",     //请求url
            .event_handler = _http_event_handler,
            .user_data = local_response_buffer,        // Pass address of local buffer to get response
        };
        esp_http_client_handle_t client = esp_http_client_init(&config);

        // GET
        esp_err_t err = esp_http_client_perform(client);
        if (err == ESP_OK) {
            ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
                    esp_http_client_get_status_code(client),
                    esp_http_client_get_content_length(client));
        } else {
            ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err));
        }
        ESP_LOGI(TAG, "%s\\n", local_response_buffer);    

        esp_http_client_cleanup(client);
    }

    vTaskDelay(1000);
}

static void event_handler(void* arg, esp_event_base_t event_base, 
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        xTaskCreate(smartconfig_example_task, "smartconfig_example_task", 4096, NULL, 3, NULL);
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        esp_wifi_connect();
        xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT);
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT);
    } else if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE) {
        ESP_LOGI(TAG, "Scan done");
    } else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL) {
        ESP_LOGI(TAG, "Found channel");
    } else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) {
        ESP_LOGI(TAG, "Got SSID and password");

        smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t *)event_data;
        wifi_config_t wifi_config;
        uint8_t ssid[33] = { 0 };
        uint8_t password[65] = { 0 };

        bzero(&wifi_config, sizeof(wifi_config_t));
        memcpy(wifi_config.sta.ssid, evt->ssid, sizeof(wifi_config.sta.ssid));
        memcpy(wifi_config.sta.password, evt->password, sizeof(wifi_config.sta.password));
        wifi_config.sta.bssid_set = evt->bssid_set;
        if (wifi_config.sta.bssid_set == true) {
            memcpy(wifi_config.sta.bssid, evt->bssid, sizeof(wifi_config.sta.bssid));
        }

        memcpy(ssid, evt->ssid, sizeof(evt->ssid));
        memcpy(password, evt->password, sizeof(evt->password));
        ESP_LOGI(TAG, "SSID:%s", ssid);
        ESP_LOGI(TAG, "PASSWORD:%s", password);

        ESP_ERROR_CHECK( esp_wifi_disconnect() );
        ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
        ESP_ERROR_CHECK( esp_wifi_connect() );
    } else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE) {
        xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT);
    }
}

static void initialise_wifi(void)
{
    ESP_ERROR_CHECK(esp_netif_init());
    s_wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
    assert(sta_netif);

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK( esp_wifi_init(&cfg) );

    ESP_ERROR_CHECK( esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL) );
    ESP_ERROR_CHECK( esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL) );
    ESP_ERROR_CHECK( esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL) );

    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK( esp_wifi_start() );
}

static void smartconfig_example_task(void * parm)
{
    EventBits_t uxBits;
    ESP_ERROR_CHECK( esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS) );
    smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
    ESP_ERROR_CHECK( esp_smartconfig_start(&cfg) );
    while (1) {
        uxBits = xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY); 
        if(uxBits & CONNECTED_BIT) {
            ESP_LOGI(TAG, "WiFi Connected to ap");
            xTaskCreate(&http_test_task, "http_test_task", 8192, NULL, 5, NULL);
        }
        if(uxBits & ESPTOUCH_DONE_BIT) {
            ESP_LOGI(TAG, "smartconfig over");
            esp_smartconfig_stop();
            vTaskDelete(NULL);
        }
    }
}

void app_main(void)
{
    ESP_ERROR_CHECK( nvs_flash_init() );
    initialise_wifi();
}

查看打印:


• 由 Leung 写于 2021 年 5 月 8 日

• 参

以上是关于ESP32学习笔记(13)——HTTP客户端的主要内容,如果未能解决你的问题,请参考以下文章

ESP32 单片机学习笔记 - 08 - WebSocket客户端

ESP32 单片机学习笔记 - 04 - ADC和定时器

ESP32学习笔记(46)——MQTT客户端

ESP32 单片机学习笔记 - 07 - TCP连接

ESP32-C3学习笔记:ESP32 C3 IIC总线驱动光照强度传感器(基于ESP-IDF Eclipse)

ESP32学习笔记(33)——BLE GATT客户端发现服务和读写特征值