一、建立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));
}
}