完整教程:SOC-ESP32S3部分:25-HTTP请求
飞书文档https://x509p6c8to.feishu.cn/wiki/KL4RwxUQdipzCSkpB2lcBd03nvK
HTTP(Hyper Text Transfer Protocol) 超文本传输协议,是一种建立在 TCP 上的无状态连接,整个基本的工作流程是客户端发送一个 HTTP 请求,说明客户端想要访问的资源和请求的动作,服务端收到请求之后,服务端开始处理请求,并根据请求做出相应的动作访问服务器资源,最后通过发送 HTTP 响应把结果返回给客户端。
HTTP请求方法:
一般有 GET、POST、PUT、DELETE,含义分别是获取、修改、上传、删除.
其中 GET 方式仅仅为获取服务器资源,方式较为简单,因此在请求方式为 GET 的 HTTP 请求数据中,请求正文部分可以省略,直接将想要获取的资源添加到 URL 中。
请求头:包括一些访问的域名、用户代理、Cookie等信息。
请求正文:就是HTTP请求的数据。
如下发,就是一个简单的HTTP请求示例,在执行HTTP请求前,设备必须先进行配网,配网使用的是我们上节课讲解的代码。
HTTP部分核心逻辑如下,设备配网成功后,设置全局变量is_connect_wifi为true,然后执行http_get_request发送一次GET请求,请求发送给http://www.example.com/服务器。
主要流程如下:
- 配置http请求参数和回调处理函数
- 初始化http客户端
- 执行http请求,监听回调
- 清理HTTP客户端资源,释放句柄。
配置http请求参数和回调处理函数
esp_http_client_config_t描述: 配置ESP32 HTTP客户端的参数。字段:.method: HTTP请求方法,例如 HTTP_METHOD_GET 表示GET请求。.url: 请求的目标URL。.event_handler: 自定义事件处理函数,用于处理HTTP请求过程中的事件。.user_data: 用户自定义数据,可以传递给事件处理函数或存储响应数据。 _http_event_handler描述: 自定义的HTTP事件处理函数。用途: 处理HTTP请求过程中发生的事件(如连接建立、数据接收等)。注意: 代码中未提供具体实现,但通常需要根据事件类型执行相应的逻辑。 使用示例: char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0}; esp_http_client_config_t config = { .method = HTTP_METHOD_GET, //get请求 .url = "http://www.example.com/", //请求url .event_handler = _http_event_handler, .user_data = local_response_buffer, // Pass address of local buffer to get response }; _http_event_handler为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"); break; case HTTP_EVENT_ON_DATA: //接收数据事件 ESP_LOGI(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len); 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; case HTTP_EVENT_REDIRECT: break; } return ESP_OK;}
初始化http客户端
esp_http_client_initesp_http_client_handle_t esp_http_client_init(const esp_http_client_config_t *config);功能: 初始化HTTP客户端并返回句柄。参数:config: 指向esp_http_client_config_t结构体的指针,包含HTTP客户端的配置信息。返回值: 返回HTTP客户端的句柄。
执行http请求
esp_err_t esp_http_client_perform(esp_http_client_handle_t client);功能: 执行HTTP请求并等待响应。参数:client: HTTP客户端句柄。返回值:ESP_OK: 请求成功。其他错误码: 请求失败。
清理HTTP客户端资源,释放句柄。
void esp_http_client_cleanup(esp_http_client_handle_t client);功能: 清理HTTP客户端资源,释放句柄。参数:client: HTTP客户端句柄。
最终代码如下:
#include #include #include "freertos/FreeRTOS.h"#include "freertos/task.h"#include "freertos/event_groups.h"#include "esp_eap_client.h"#include "esp_netif.h"#include "esp_smartconfig.h"#include "esp_mac.h"#include "esp_system.h"#include "esp_wifi.h"#include "esp_event.h"#include "esp_log.h"#include "nvs_flash.h" #include "esp_http_client.h" static const char *TAG = "http_client"; static EventGroupHandle_t s_wifi_event_group; static const int CONNECTED_BIT = BIT0;static const int ESPTOUCH_DONE_BIT = BIT1;static void smartconfig_example_task(void *parm);static bool is_connect_wifi = false; #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; case HTTP_EVENT_REDIRECT: break; } return ESP_OK;} static void http_get_request(){ char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0}; esp_http_client_config_t config = { .method = HTTP_METHOD_GET, //get请求 .url = "http://www.example.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 OK"); } 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);} static void http_get_task(void *pvParameters){ while (1) { if (is_connect_wifi) { http_get_request(); } vTaskDelay(8000 / portTICK_PERIOD_MS); }} 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) { // WiFi 站点模式启动后,创建 SmartConfig 任务 xTaskCreate(smartconfig_example_task, "smartconfig_example_task", 4096, NULL, 3, NULL); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { is_connect_wifi = false; // WiFi 断开连接时,重新连接并清除连接标志位 esp_wifi_connect(); xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { // 获取到 IP 地址后,设置连接标志位 xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT); is_connect_wifi = true; } else if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE) { // SmartConfig 扫描完成事件 ESP_LOGI(TAG, "Scan done"); } else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL) { // SmartConfig 找到信道事件 ESP_LOGI(TAG, "Found channel"); } else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) { // SmartConfig 获取到 SSID 和密码事件 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}; uint8_t rvd_data[33] = {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)); 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); if (evt->type == SC_TYPE_ESPTOUCH_V2) { // 如果使用的是 ESPTouch V2,获取额外的数据 ESP_ERROR_CHECK(esp_smartconfig_get_rvd_data(rvd_data, sizeof(rvd_data))); ESP_LOGI(TAG, "RVD_DATA:"); for (int i = 0; i 0) { // 如果配置过,就直接连接wifi ESP_LOGI(TAG, "alrealy set, SSID is :%s,start connect", myconfig.sta.ssid); esp_wifi_connect(); } else { // 如果没有配置过,就进行配网操作 ESP_LOGI(TAG, "have no set, start to config"); ESP_ERROR_CHECK(esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS)); // 支持APP ESPTOUCH和微信AIRKISS smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_smartconfig_start(&cfg)); } while (1) { // 等待连接标志位或 SmartConfig 完成标志位 uxBits = xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY); if (uxBits & CONNECTED_BIT) { // 连接到 AP 后的日志 ESP_LOGI(TAG, "WiFi Connected to ap"); // 联网成功后,可以关闭线程 vTaskDelete(NULL); } if (uxBits & ESPTOUCH_DONE_BIT) { // SmartConfig 完成后的日志 ESP_LOGI(TAG, "smartconfig over"); // 停止 SmartConfig esp_smartconfig_stop(); // 删除 SmartConfig 任务 vTaskDelete(NULL); } }} void app_main(void){ // 初始化 NVS 闪存 ESP_ERROR_CHECK( nvs_flash_init()); // 初始化网络接口 ESP_ERROR_CHECK(esp_netif_init()); // 创建事件组 s_wifi_event_group = xEventGroupCreate(); // 创建默认事件循环 ESP_ERROR_CHECK(esp_event_loop_create_default()); // 创建默认的 WiFi 站点模式网络接口 esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta(); assert(sta_netif); // 初始化 WiFi 配置 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)); // 设置 WiFi 模式为站点模式并启动 WiFi ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_start()); xTaskCreate(&http_get_task, "http_get_task", 9192, NULL, 5, NULL);}
对于POST请求
只需要把esp_http_client_config_t的method设置为HTTP_METHOD_POST,另外安装服务器的参数,填写esp_http_client_set_header和esp_http_client_set_post_field即可。例如,我们需要发送JSON数据包{"field1":"value1"}给服务器http://httpbin.org/post,具体POST请求实现如下:
static void http_post(void){ esp_http_client_config_t config = { .method = HTTP_METHOD_POST, .url = "http://httpbin.org/post", .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_header(client, "Content-Type", "application/json"); esp_http_client_set_post_field(client, post_data, strlen(post_data)); esp_err_t err = esp_http_client_perform(client); if (err == ESP_OK) { ESP_LOGI(TAG, "HTTP POST OK"); } else { ESP_LOGE(TAG, "HTTP POST request failed: %s", esp_err_to_name(err)); } esp_http_client_cleanup(client);}
对于HTTPS请求
只需额外配置esp_http_client_config_t 的url为https连接,另外添加证书文件即可cert_pem,例如
esp_http_client_config_t config = { .method = HTTP_METHOD_GET, .url = "https://www.howsmyssl.com/", .event_handler = _http_event_handler, .cert_pem = howsmyssl_com_root_cert_pem_start, };
esp_http_client_config_t更多参数说明如下:可按需使用
typedef struct { const char *url; /*!< HTTP URL,URL 信息是最重要的,如果设置了此参数,它将覆盖下面的其他字段(如果有的话) */ const char *host; /*!< 以字符串形式表示的域名或 IP 地址 */ int port; /*!< 要连接的端口,默认值取决于 esp_http_client_transport_t(80 用于 HTTP,443 用于 HTTPS) */ const char *username; /*!< 用于 HTTP 认证 */ const char *password; /*!< 用于 HTTP 认证 */ esp_http_client_auth_type_t auth_type; /*!< HTTP 认证类型,请参阅 `esp_http_client_auth_type_t` */ const char *path; /*!< HTTP 路径,如果未设置,默认值为 `/` */ const char *query; /*!< HTTP 查询字符串 */ const char *cert_pem; /*!< SSL 服务器证书,以 PEM 格式表示的字符串,如果客户端需要验证服务器 */ size_t cert_len; /*!< 指向 cert_pem 的缓冲区长度。如果是 null 结尾的 PEM 字符串,可为 0 */ const char *client_cert_pem; /*!< SSL 客户端证书,以 PEM 格式表示的字符串,如果服务器需要验证客户端 */ size_t client_cert_len; /*!< 指向 client_cert_pem 的缓冲区长度。如果是 null 结尾的 PEM 字符串,可为 0 */ const char *client_key_pem; /*!< SSL 客户端密钥,以 PEM 格式表示的字符串,如果服务器需要验证客户端 */ size_t client_key_len; /*!< 指向 client_key_pem 的缓冲区长度。如果是 null 结尾的 PEM 字符串,可为 0 */ const char *client_key_password; /*!< 客户端密钥解密密码字符串 */ size_t client_key_password_len; /*!< 指向 client_key_password 的密码字符串长度 */ esp_http_client_proto_ver_t tls_version; /*!< 连接的 TLS 协议版本,例如,TLS 1.2、TLS 1.3(默认 - 无偏好) */#ifdef CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN bool use_ecdsa_peripheral; /*!< 使用 ECDSA 外设来使用私钥 */ uint8_t ecdsa_key_efuse_blk; /*!< ECDSA 密钥存储的 efuse 块 */#endif const char *user_agent; /*!< 随 HTTP 请求发送的用户代理字符串 */ esp_http_client_method_t method; /*!< HTTP 请求方法 */ int timeout_ms; /*!< 网络超时时间,单位为毫秒 */ bool disable_auto_redirect; /*!< 禁用 HTTP 自动重定向 */ int max_redirection_count; /*!< 收到 HTTP 重定向状态码时的最大重定向次数,如果为 0,则使用默认值 */ int max_authorization_retries; /*!< 收到 HTTP 未授权状态码时的最大连接重试次数,如果为 0,则使用默认值。如果为 -1,则禁用授权重试 */ http_event_handle_cb event_handler; /*!< HTTP 事件处理函数 */ esp_http_client_transport_t transport_type; /*!< HTTP 传输类型,请参阅 `esp_http_client_transport_t` */ int buffer_size; /*!< HTTP 接收缓冲区大小 */ int buffer_size_tx; /*!< HTTP 发送缓冲区大小 */ void *user_data; /*!< HTTP 用户数据上下文 */ bool is_async; /*!< 设置异步模式,目前仅支持 HTTPS */ bool use_global_ca_store; /*!< 对所有设置了此布尔值的连接使用全局 CA 证书存储 */ bool skip_cert_common_name_check; /*!< 跳过对服务器证书 CN 字段的任何验证 */ const char *common_name; /*!< 指向包含服务器证书通用名称的字符串指针。 如果不为 NULL,服务器证书的 CN 必须与此名称匹配; 如果为 NULL,服务器证书的 CN 必须与主机名匹配。 */ esp_err_t (*crt_bundle_attach)(void *conf); /*!< 指向 esp_crt_bundle_attach 的函数指针。启用使用证书包进行服务器验证,必须在菜单配置中启用 */ bool keep_alive_enable; /*!< 启用 keep-alive 超时机制 */ int keep_alive_idle; /*!< keep-alive 空闲时间。默认值为 5(秒) */ int keep_alive_interval; /*!< keep-alive 间隔时间。默认值为 5(秒) */ int keep_alive_count; /*!< keep-alive 数据包重试发送次数。默认值为 3 次 */ struct ifreq *if_name; /*!< 数据要通过的接口名称。如果未设置,则使用默认接口 */#if CONFIG_ESP_TLS_USE_SECURE_ELEMENT bool use_secure_element; /*!< 启用此选项以使用安全元件 */#endif#if CONFIG_ESP_TLS_USE_DS_PERIPHERAL void *ds_data; /*!< 数字签名外设上下文指针,更多详细信息请参阅 ESP - TLS 文档 */#endif#if CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS bool save_client_session;#endif#if CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT struct esp_transport_item_t *transport;#endif} esp_http_client_config_t;
HTTPS请求需要做服务器证书校验,所以需要添加证书,证书是由服务器提供的,这里用到两个服务器进行测试,所以需要用到两个证书,拷贝idf证书到工程main下方
esp-idf/examples/protocols/esp_http_client/main/howsmyssl_com_root_cert.pem
esp-idf/examples/protocols/esp_http_client/main/postman_root_cert.pem
拷贝证书到工程路径后,还需要添加到CMakeLists.txt中,保证烧录到设备中
demo/main/CMakeLists.txt
idf_component_register( SRCS "main.c" INCLUDE_DIRS "." EMBED_TXTFILES howsmyssl_com_root_cert.pem postman_root_cert.pem )
最后请求代码如下:
//howsmyssl服务器证书位置extern const char howsmyssl_com_root_cert_pem_start[] asm("_binary_howsmyssl_com_root_cert_pem_start");extern const char howsmyssl_com_root_cert_pem_end[] asm("_binary_howsmyssl_com_root_cert_pem_end");//postman服务器证书位置extern const char postman_root_cert_pem_start[] asm("_binary_postman_root_cert_pem_start");extern const char postman_root_cert_pem_end[] asm("_binary_postman_root_cert_pem_end"); static void https_get_request(void){ esp_http_client_config_t config = { .method = HTTP_METHOD_GET, .url = "https://www.howsmyssl.com/", .event_handler = _http_event_handler, .cert_pem = howsmyssl_com_root_cert_pem_start, }; esp_http_client_handle_t client = esp_http_client_init(&config); esp_err_t err = esp_http_client_perform(client); if (err == ESP_OK) { ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %"PRId64, esp_http_client_get_status_code(client), esp_http_client_get_content_length(client)); } else { ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err)); } esp_http_client_cleanup(client);} static void https_post(){ esp_http_client_config_t config = { .method = HTTP_METHOD_POST, .url = "https://www.postman-echo.com/post", .event_handler = _http_event_handler, .cert_pem = postman_root_cert_pem_start, .is_async = true, .timeout_ms = 5000, }; esp_http_client_handle_t client = esp_http_client_init(&config); esp_err_t err; const char *post_data = "this is xiaozhi post data"; esp_http_client_set_post_field(client, post_data, strlen(post_data)); while (1) { err = esp_http_client_perform(client); if (err != ESP_ERR_HTTP_EAGAIN) { break; } } if (err == ESP_OK) { ESP_LOGI(TAG, "HTTPS Status = %d, content_length = %"PRId64, esp_http_client_get_status_code(client), esp_http_client_get_content_length(client)); } else { ESP_LOGE(TAG, "Error perform http request %s", esp_err_to_name(err)); } esp_http_client_cleanup(client);}
这个实验需要先完成WiFi配网哦,具体看WiFi章节24-WiFi配网