ESP32网络入门-HTTP协议(客户端)

本文最后更新于:3 个月前

说明:

  1. 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
  2. 本文档介绍ESP32HTTP客户端程序

修订历史:

文档名称 版本 作者 时间 备注
ESP32网络入门-HTTP协议(客户端) v1.0.0 DuRuofu 2024-03-24 首次建立

ESP32网络入门-HTTP协议

一、概述

1.1 HTTP协议介绍

HTTP协议(超文本传输协议HyperText Transfer Protocol),它是基于TCP协议的应用层传输协议,简单来说就是客户端和服务端进行数据传输的一种规则。

特点:

  • 支持客户/服务器模式
  • 简单快速:客户向服务器请求服务时,只需传送请求方法和路径,请求方法常用GET,HEAD,POST。每种方法规定了客户与服务器联系的类型不同,由于HTTP协议简单,是的HTTP服务器的程序规模小,因而通信速度很快。
  • 灵活:HTTP允许传输任意类型的数据对象,正在传输的类型有Content-Type加以标记。
  • 无连接:无连接的含义是限制每次连接只处理一个请求,服务器处理完客户的请求,并受到客户的应答后,即断开连接,采用这种方式可以节省传输时间。
  • 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可以导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

1.2 HTTP内容

关于HTTP内容比较简单,这里建议直接阅读计算机网络有关部分,在此不再赘述。

HTTP协议使用客户端/服务器模型。客户端向服务器发送HTTP请求,服务器接收到请求后,根据请求的内容进行一定的处理,并将处理结果封装在HTTP响应中返回给客户端。HTTP请求和响应都有一定的结构,通常包含三部分:起始行、首部和主体。

可以参考:

前后端交互之 HTTP 协议

我们在本节只需要知道发送HTTP请求的格式和接收的格式即可:

发送格式:

HTTP的起始行包含了请求方法或响应状态码等信息,如GET、POST等请求方法,200 OK、404 Not Found等响应状态码。HTTP的首部包含了请求或响应的各项属性,如Accept、Content-Type、Set-Cookie等。HTTP的主体则包含了请求或响应的具体内容,如HTML、JSON等文本数据或二进制数据。

接收格式:

URL格式:

URL(Uniform Resource Locator)是用于定位互联网上资源的地址,常见的资源包括网页、图片、视频等。URL由多个部分组成,包括协议、主机名、端口号、路径和查询字符串等。

一个标准的URL格式如下:

复制代码

1
<协议>://<主机名>:<端口>/<路径>?<查询字符串>

其中,协议表示访问资源所采用的协议,如HTTP、HTTPS、FTP等;主机名表示资源所在的主机名或IP地址;端口号表示与主机通信的端口号,默认情况下使用协议默认的端口;路径表示请求的资源路径,可以是一个具体的文件路径,也可以是一个文件夹路径;查询字符串表示请求参数,以问号(?)开头,多个参数之间用&符号分隔。

例如,以下是一个标准的URL格式:

复制代码

1
https://www.example.com:80/index.html?id=123&name=test

其中,协议为HTTPS,主机名为www.example.com,端口号为80(默认端口号可省略),路径为/index.html,查询字符串为id=123&name=test。

请确保明白上面的内容,再开始学习ESP32的HTTP协议使用。

二、使用

下面我们使用ESP32充当浏览器,向HTTP服务器发送Request请求。

2.1 整体介绍

esp_http_client 提供了一组 API,用于从 ESP-IDF 应用程序中发起 HTTP/S 请求,具体的使用步骤如下:

  • 首先调用 esp_http_client_init(),创建一个 esp_http_client_handle_t 实例,即基于给定的 esp_http_client_config_t 配置创建 HTTP 客户端句柄。此函数必须第一个被调用。若用户未明确定义参数的配置值,则使用默认值。

  • 其次调用 esp_http_client_perform(),执行 esp_http_client 的所有操作,包括打开连接、交换数据、关闭连接(如需要),同时在当前任务完成前阻塞该任务。所有相关的事件(在 esp_http_client_config_t 中指定)将通过事件处理程序被调用。

  • 最后调用 esp_http_client_cleanup() 来关闭连接(如有),并释放所有分配给 HTTP 客户端实例的内存。此函数必须在操作完成后最后一个被调用。

2.2 详细步骤

首先我们要初始化WIFI,连接WIFI,这是编写HTTP程序的基础,连接WIFI在此不再赘述。

后面的部分,默认已经连接好网络.

2.2.1 创建esp_http_client 实例

这里要使用esp_http_client_init()创建HTTP客户端句柄实例,这个函数是 ESP32/ESP-IDF 中用于初始化 HTTP 客户端会话的函数。它接受一个指向 esp_http_client_config_t 结构的指针作为参数,该结构包含了 HTTP 客户端的配置信息。函数返回一个 esp_http_client_handle_t 类型的句柄,需要将这个句柄作为其他接口函数的输入参数来使用。在调用这个函数之后,必须在操作完成后调用 esp_http_client_cleanup 函数来清理资源。

关于esp_http_client_config_t 结构的参数如下:

成员 描述
url HTTP URL,如果设置了该字段,则会覆盖其他字段
host 域名或 IP 地址,以字符串表示
port 连接的端口,默认取决于 esp_http_client_transport_t(80 或 443)
username HTTP 身份验证的用户名
password HTTP 身份验证的密码
auth_type HTTP 身份验证类型,参见 esp_http_client_auth_type_t
path HTTP 路径,默认为 / 如果未设置
query HTTP 查询参数
cert_pem SSL 服务器证书,PEM 格式的字符串,如果客户端需要验证服务器
cert_len cert_pem 缓冲区的长度。对于以 null 结尾的 PEM,可能为 0
client_cert_pem SSL 客户端证书,PEM 格式的字符串,如果服务器需要验证客户端
client_cert_len client_cert_pem 缓冲区的长度。对于以 null 结尾的 PEM,可能为 0
client_key_pem SSL 客户端私钥,PEM 格式的字符串,如果服务器需要验证客户端
client_key_len client_key_pem 缓冲区的长度。对于以 null 结尾的 PEM,可能为 0
client_key_password 客户端密钥解密密码字符串
client_key_password_len client_key_password 字符串的长度
tls_version 连接的 TLS 协议版本,例如 TLS 1.2、TLS 1.3(默认 - 无偏好)
user_agent 发送 HTTP 请求时的用户代理字符串
method HTTP 方法
timeout_ms 网络超时时间(毫秒)
disable_auto_redirect 禁用 HTTP 自动重定向
max_redirection_count 在接收到 HTTP 重定向状态码时的最大重定向次数,如果为零,则使用默认值
max_authorization_retries 在接收到 HTTP 未经授权状态码时的最大连接重试次数,如果为零,则使用默认值。如果为 -1,则禁用授权重试
event_handler HTTP 事件处理器
transport_type HTTP 传输类型,参见 esp_http_client_transport_t
buffer_size HTTP 接收缓冲区大小
buffer_size_tx HTTP 发送缓冲区大小
user_data HTTP 用户数据上下文
is_async 设置异步模式,目前仅支持 HTTPS
use_global_ca_store 使用全局 CA 存储
skip_cert_common_name_check 跳过对服务器证书 CN 字段的验证
common_name 指向包含服务器证书公共名称的字符串的指针。如果非 NULL,则服务器证书 CN 必须匹配此名称;如果为 NULL,则服务器证书 CN 必须匹配主机名
crt_bundle_attach 指向 esp_crt_bundle_attach 函数的函数指针。启用证书包以进行服务器验证,必须在 menuconfig 中启用
keep_alive_enable 启用 keep-alive 超时
keep_alive_idle keep-alive 空闲时间,默认为 5 秒
keep_alive_interval keep-alive 间隔时间,默认为 5 秒
keep_alive_count keep-alive 数据包重试发送计数,默认为 3 次
if_name 数据通过的接口名称。如果不设置,则使用默认接口

示例代码:

1
2
3
4
5
6
7
8
9
10
11
// 初始化HTTP客户端
char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER + 1] = {0};
esp_http_client_config_t config = {
.host = CONFIG_EXAMPLE_HTTP_ENDPOINT,
.path = "/get",
.query = "esp",
.event_handler = _http_event_handler,
.user_data = local_response_buffer, // Pass address of local buffer to get response
.disable_auto_redirect = true,
};
esp_http_client_handle_t client = esp_http_client_init(&config);

解释:

  1. char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER + 1] = {0};:这一行定义了一个字符数组 local_response_buffer,用来存储 HTTP 响应内容。MAX_HTTP_OUTPUT_BUFFER 是预先定义的常量,表示了 buffer 的最大大小。

  2. esp_http_client_config_t config = { ... };:这里创建了一个 esp_http_client_config_t 结构体变量 config,并使用大括号内的初始化列表对其进行初始化。初始化列表中的字段包括:

    • host:设置为宏 CONFIG_EXAMPLE_HTTP_ENDPOINT,表示 HTTP 请求的目标主机。
    • path:设置为 "/get",表示 HTTP 请求的路径。
    • query:设置为 "esp",表示 HTTP 请求的查询参数。
    • event_handler:设置为 _http_event_handler,这是一个处理 HTTP 事件的回调函数。
    • user_data:设置为 local_response_buffer 的地址,以便在 HTTP 请求完成后将响应内容存储到 local_response_buffer 中。
    • disable_auto_redirect:设置为 true,禁用 HTTP 的自动重定向功能。
  3. esp_http_client_handle_t client = esp_http_client_init(&config);:这一行调用了 esp_http_client_init 函数来初始化一个 HTTP 客户端,并将上述配置传递给该函数。函数返回一个 esp_http_client_handle_t 类型的客户端句柄,以供后续的 HTTP 请求使用。

2.2.2 执行HTTP客户端的各种操作

具体使用就很简单了:

GET请求

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

1
2
3
4
5
6
7
8
9
10
11
12
13
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK)
{
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %" PRId64,
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));

上面这段代码执行了一个HTTP GET请求,并根据执行结果打印相应的日志信息。

POST请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// POST
const char *post_data = "{\"field1\":\"value1\"}";
esp_http_client_set_url(client, "http://" CONFIG_EXAMPLE_HTTP_ENDPOINT "/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 = %" PRId64,
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));
}
2.2.3 关闭链接,并释放系统资源
1
esp_http_client_cleanup(client);

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

三、案例

使用HTTP get请求获取我的博客首页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203

#include <string.h>
#include <sys/param.h>
#include <stdlib.h>
#include <ctype.h>
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_tls.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_mac.h"
#include <sys/socket.h>
#include "esp_http_client.h"

// 要连接的WIFI
#define ESP_WIFI_STA_SSID "duruofu_win10"
#define ESP_WIFI_STA_PASSWD "1234567890"
static const char *TAG = "main";

// 最大http输出缓冲区
#define MAX_HTTP_OUTPUT_BUFFER 2048

// WIFI事件回调
void WIFI_CallBack(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
static uint8_t connect_count = 0;
// WIFI 启动成功
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_START");
ESP_ERROR_CHECK(esp_wifi_connect());
}
// WIFI 连接失败
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED");
connect_count++;
if (connect_count < 6)
{
vTaskDelay(1000 / portTICK_PERIOD_MS);
ESP_ERROR_CHECK(esp_wifi_connect());
}
else
{
ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_DISCONNECTED 10 times");
}
}
// WIFI 连接成功(获取到了IP)
if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
ESP_LOGI("WIFI_EVENT", "WIFI_EVENT_STA_GOT_IP");
ip_event_got_ip_t *info = (ip_event_got_ip_t *)event_data;
ESP_LOGI("WIFI_EVENT", "got ip:" IPSTR "", IP2STR(&info->ip_info.ip));
}
}

// 连接WIFI
static void wifi_sta_init(void)
{
ESP_ERROR_CHECK(esp_netif_init());

// 注册事件(wifi启动成功)
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_START, WIFI_CallBack, NULL, NULL));
// 注册事件(wifi连接失败)
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, WIFI_CallBack, NULL, NULL));
// 注册事件(wifi连接失败)
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, WIFI_CallBack, NULL, NULL));

// 初始化STA设备
esp_netif_create_default_wifi_sta();

/*Initialize WiFi */
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
// WIFI_INIT_CONFIG_DEFAULT 是一个默认配置的宏

ESP_ERROR_CHECK(esp_wifi_init(&cfg));

//----------------配置阶段-------------------
// 初始化WIFI设备( 为 WiFi 驱动初始化 WiFi 分配资源,如 WiFi 控制结构、RX/TX 缓冲区、WiFi NVS 结构等,这个 WiFi 也启动 WiFi 任务。必须先调用此API,然后才能调用所有其他WiFi API)
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));

// STA详细配置
wifi_config_t sta_config = {
.sta = {
.ssid = ESP_WIFI_STA_SSID,
.password = ESP_WIFI_STA_PASSWD,
.bssid_set = false,
},
};
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_config));

//----------------启动阶段-------------------
ESP_ERROR_CHECK(esp_wifi_start());

//----------------配置省电模式-------------------
// 不省电(数据传输会更快)
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
}


// 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;
case HTTP_EVENT_REDIRECT:
ESP_LOGD(TAG, "HTTP_EVENT_REDIRECT");
}
return ESP_OK;
}


// HTTP客户端任务
static void http_client_task(void)
{
// 等待wifi连接成功(暂时这样处理)
vTaskDelay(5000 / portTICK_PERIOD_MS);
ESP_LOGI("http_client_task", "http_client_task start");

// 初始化HTTP客户端
char local_response_buffer[MAX_HTTP_OUTPUT_BUFFER + 1] = {0};

// 配置HTTP客户端目标
esp_http_client_config_t config = {
//.method = HTTP_METHOD_GET, // get请求
.url = "http://www.duruofu.top", // 请求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_http_client_set_method(client, HTTP_METHOD_GET);
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK)
{
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %" PRId64,
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);

vTaskDelete(NULL);
}

void app_main(void)
{
// Initialize NVS
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);

// 创建默认事件循环
ESP_ERROR_CHECK(esp_event_loop_create_default());

// 配置启动WIFI
wifi_sta_init();

// 创建HTTP客户端任务
xTaskCreate(http_client_task, "http_client_task", 1024*8, NULL, 5, NULL);
}


效果:
成功获取到了博客网站的内容:

内容一致:

参考链接

  1. https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/protocols/esp_http_client.html#_CPPv424esp_http_client_config_t

ESP32网络入门-HTTP协议(客户端)
https://www.duruofu.xyz/2024/03/25/4.硬件相关/MCU/ESP32/05.ESP32WIFI入门/5.4-ESP32网络入门-HTTP协议/ESP32网络入门-HTTP协议(客户端)/ESP32网络入门-HTTP协议(客户端)/
作者
DuRuofu
发布于
2024年3月25日
更新于
2024年3月25日
许可协议