ESP-C3入门12. HTTPS请求、堆内存使用及JSON处理
- 一、创建HTTPS请求
- 1. 基本流程
- 2. ESP32 使用https证书的方式
- (1) 内置证书
- (2) ESP32管理证书
- 3. 开发环境配置
- (1) 引用 esp-tls 库
- (2) 启用 `CONFIG_MBEDTLS_CERTIFICATE_BUNDLE`
- 二、堆内存的使用
- 1. 堆内存和栈内存
- 2. 堆内存的使用
- (1) 内存分配
- (2) 内存释放
- 三、使用cJSON库
- 1. 加载cJSON库
- 2. 最基本的用法
- 四、https请求示例
- 1. 项目结构
- 2. 自定义组件的CMakeLists.txt设置
- 3. http_request.h
- 4. http_request.c
- 5. main.c
一、创建HTTPS请求
1. 基本流程
本文主要内容接上节《创建最基本http请求》的文章。
ESP32 IDF创建http请求的基本流程:
- 使用
esp_http_client_config_t
创建http客户端; esp_http_client_init
初始化http客户端;esp_http_client_set_method
设置http请求方式;- 设置http请求头
esp_http_client_set_header
; - 设置 http 请求体
esp_http_client_set_post_field
; - 执行http请求
esp_http_client_perform
; - 处理http响应;
- 释放http客户端
esp_http_client_cleanup
;
在https请求中,有一些进行一些额外的步骤,包括 证书的验证和捆绑。
首先要获取远程服务器的证书;
如果ESP32 IDF无法验证证书,则需要使用esp_http_client_set_cert_info函数将服务器证书的SHA-1指纹添加到ESP32 IDF的证书信任列表中。
如果ESP32 IDF无法连接到远程服务器,则可能需要设置代理服务器。
2. ESP32 使用https证书的方式
(1) 内置证书
证书已经内置在ESP32的固件中,无需单独管理证书,可以直接使用。这种方式比较简单,适用于使用不频繁的HTTPS请求。
(2) ESP32管理证书
使用esp32管理证书的方式,需要用户自己管理证书。首先需要在ESP32上安装证书,然后在代码中指定证书的路径和密码。这种方式需要用户自己管理证书的更新和安装,但是可以在需要时更新证书,从而提高了安全性,适用于使用频繁的HTTPS请求场景。
总结:使用内置证书的方式更加简单,适用于简单的HTTPS请求场景;
使用esp32管理证书的方式更加灵活,适用于复杂的HTTPS请求场景。
3. 开发环境配置
(1) 引用 esp-tls 库
CMakeLists.txt里添加:
idf_component_register(SRCS "main.c" "network/wifi.c" "network/tcp_server.c" "network/tcp_client.c" "network/http_request.c"
INCLUDE_DIRS "network/include"
REQUIRES "tcpip_adapter" "nvs_flash" "esp_http_client" "esp-tls"
)
(2) 启用 CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
在menuconfig中设置启用ESP-IDF TLS 库中的受信任证书捆绑包(一般已经默认选中)。
在使用 TLS 时,需要验证对等方证书以确保其是可信的。可信任的证书可以是根证书或中间证书,或者可以通过一组根证书建立信任关系。证书的验证需要证书链和 CA 证书的列表。如果 ESP-IDF TLS 库没有找到可用的证书链或 CA 证书,将不会建立安全连接。
受信任的证书捆绑包是一组根证书,其中包括常见 CA 证书,以及其他根证书和中间证书。启用此选项可以将 ESP-IDF TLS 库的可信 CA 列表扩展到受信任的证书捆绑包中的证书,从而增加连接对等方的成功率。
二、堆内存的使用
1. 堆内存和栈内存
一般情况下声明的变量是栈内存,是一种后进后出的数据结构,用于存储局部变量、函数参数和返回地址等。它的内存空间由编译器在程序运行时自动分配和释放,空间大小通常是有限制的。
堆内存是由程序员在运行时手动分配和释放的内存,它的内存空间通常比栈内存更大,也更灵活,可以根据需要动态调整内存大小。 堆内存通常用来存储动态分配的数据结构,如链表、树、图等。 但使用中要避免内存泄露。
在嵌入式系统中,由于内存资源有限,使用堆内存可以更好地进行内存管理。
2. 堆内存的使用
(1) 内存分配
在ESP32开发中,堆空间是动态分配的,它的大小是由可用RAM的大小限制的。ESP-IDF中提供了几个API用于堆内存管理,其中之一是heap_caps_malloc函数。heap_caps_malloc是ESP32的堆空间动态内存分配API,允许用户从堆空间中分配内存。
函数原型:
void *heap_caps_malloc(size_t size, uint32_t caps);
其中:
- size参数是要分配的内存块的大小
- caps参数是分配内存的策略,这是一个32位的标志位,用于设置内存的要求和限制。
标志位值列表:
- MALLOC_CAP_8BIT: 分配8位宽的内存;
- MALLOC_CAP_32BIT: 分配32位宽的内存;
- MALLOC_CAP_64BIT: 分配64位宽的内存;
- MALLOC_CAP_SPIRAM: 分配SPI RAM内存;
- MALLOC_CAP_DMA: 分配DMA内存;
- MALLOC_CAP_EXEC: 分配可执行内存;
- MALLOC_CAP_DEFAULT: 分配默认类型的内存;
- MALLOC_CAP_INVALID: 分配无效类型的内存。
(2) 内存释放
heap_caps_free
进行内存释放。
本文中的响应数据使用变量 char* local_response_buffer ,使用下面的代码把变量放在了堆上:
local_response_buffer = heap_caps_malloc(MAX_HTTP_OUTPUT_BUFFER, MALLOC_CAP_8BIT);
if (local_response_buffer == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for HTTP output buffer");
abort();
}
上述代码分配了一个大小为MAX_HTTP_OUTPUT_BUFFER的8位宽的内存块。
三、使用cJSON库
1. 加载cJSON库
在 CMakeLists.txt中添加 json:
idf_component_register(SRCS "main.c" "network/wifi.c" "network/tcp_server.c" "network/tcp_client.c" "network/http_request.c"
INCLUDE_DIRS "network/include"
REQUIRES "tcpip_adapter" "nvs_flash" "esp_http_client" "esp-tls" "json"
)
2. 最基本的用法
void parse_json(const char *json_string) {
// 解析 JSON 字符串
cJSON *root = cJSON_Parse(json_string);
if (root == NULL) {
printf("Failed to parse JSON string\n");
return;
}
// 获取 "name" 属性的值
cJSON *name = cJSON_GetObjectItemCaseSensitive(root, "body");
if (cJSON_IsString(name) && (name->valuestring != NULL)) {
printf("body: %s\n", name->valuestring);
} else {
printf("Failed to get name property\n");
}
cJSON_Delete(root);
}
更多用法可参考:点击这里
四、https请求示例
1. 项目结构
2. 自定义组件的CMakeLists.txt设置
idf_component_register(SRCS "main.c" "network/wifi.c" "network/tcp_server.c" "network/tcp_client.c" "network/http_request.c"
INCLUDE_DIRS "network/include"
REQUIRES "tcpip_adapter" "nvs_flash" "esp_http_client" "esp-tls" "json"
)
# 这将把当前组件的路径添加到编译器的头文件搜索路径中,否则提示找不到string.h等头文件
target_include_directories(${COMPONENT_LIB} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_include_directories(${COMPONENT_LIB} PUBLIC "network/include"
)
3. http_request.h
#ifndef HTTP_REQUEST_H
#define HTTP_REQUEST_H
#include "esp_http_client.h"
esp_err_t http_event_handler(esp_http_client_event_t *evt);
void request(const char* url);
#endif
4. http_request.c
#include <esp_err.h>
#include <esp_log.h>
#include "network/include/http_request.h"
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
#include "esp_crt_bundle.h"
#endif
static const char *TAG = "HTTP_REQUEST";
#define MAX_HTTP_OUTPUT_BUFFER 4096
#include "cJSON.h"
char* local_response_buffer ;
// HTTP 请求的处理函数
esp_err_t http_event_handler(esp_http_client_event_t *evt)
{
// 缓存http响应的buffer
static char *output_buffer;
// 已经读取的字节数
static int output_len;
local_response_buffer = (char *) evt->user_data;
switch(evt->event_id) {
case HTTP_EVENT_ERROR:
ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
break;
case HTTP_EVENT_ON_CONNECTED:
ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
break;
case HTTP_EVENT_HEADER_SENT:
ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
break;
case HTTP_EVENT_ON_HEADER:
ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
break;
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)) {
// 如果配置了user_data buffer,则把响应复制到该buffer中
if (local_response_buffer) {
memcpy(local_response_buffer + output_len, evt->data, evt->data_len);
} else {
if (output_buffer == NULL) {
output_buffer = (char *) malloc(esp_http_client_get_content_length(evt->client));
output_len = 0;
if (output_buffer == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for output buffer");
return ESP_FAIL;
}
}
memcpy(output_buffer + output_len, evt->data, evt->data_len);
}
output_len += evt->data_len;
}
break;
case HTTP_EVENT_ON_FINISH:
ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
if (output_buffer != NULL) {
free(output_buffer);
output_buffer = NULL;
}
output_len = 0;
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
if (output_buffer != NULL) {
free(output_buffer);
output_buffer = NULL;
}
output_len = 0;
break;
}
return ESP_OK;
}
void parse_json(const char *json_string) {
// 解析 JSON 字符串
cJSON *root = cJSON_Parse(json_string);
if (root == NULL) {
printf("Failed to parse JSON string\n");
return;
}
// 获取 "name" 属性的值
cJSON *name = cJSON_GetObjectItemCaseSensitive(root, "body");
if (cJSON_IsString(name) && (name->valuestring != NULL)) {
printf("body: %s\n", name->valuestring);
} else {
printf("Failed to get name property\n");
}
cJSON_Delete(root);
}
void request(const char *url) {
// 响应结果放在这里
local_response_buffer = heap_caps_malloc(MAX_HTTP_OUTPUT_BUFFER, MALLOC_CAP_8BIT);
if (local_response_buffer == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for HTTP output buffer");
abort();
}
// 创建一个 HTTP 客户端配置
esp_http_client_config_t config = {
.url = url,
.event_handler = http_event_handler,
.user_data = local_response_buffer,
.crt_bundle_attach = esp_crt_bundle_attach,
};
// 创建一个 HTTP 客户端并执行 GET 请求
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_err_t err = esp_http_client_perform(client);
// 检查请求是否成功
if (err == ESP_OK) {
int len = esp_http_client_get_content_length(client);
ESP_LOGI(TAG, "Status = %d, content_length = %d",
esp_http_client_get_status_code(client),//状态码
len);//数据长度
// 解析json
parse_json(local_response_buffer);
} else {
printf("HTTP GET request failed: %s\n", esp_err_to_name(err));
}
printf("Response: %.*s\n", strlen(local_response_buffer), local_response_buffer);
if (local_response_buffer != NULL) {
heap_caps_free(local_response_buffer);
local_response_buffer = NULL;
}
//断开并释放资源
esp_http_client_cleanup(client);
}
5. main.c
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <nvs_flash.h>
#include "network/include/wifi.h"
#include "network/include/http_request.h"
static const char *TAG = "wifi connection";
#define HTTP_URL "https://jsonplaceholder.typicode.com/posts/1"
void app_main()
{
ESP_LOGE(TAG, "app_main");
// 初始化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);
// Wi-Fi初始化
ESP_LOGI(TAG, "Wi-Fi initialization");
wifi_initialize();
// Wi-Fi Station初始化
wifi_station_initialize();
vTaskDelay(pdMS_TO_TICKS(1000*10));
ESP_LOGI(TAG, "request 1");
request(HTTP_URL);
vTaskDelay(pdMS_TO_TICKS(2000));
ESP_LOGI(TAG, "request again");
request(HTTP_URL);
while (1) {
vTaskDelay(pdMS_TO_TICKS(500));
}
}